# Validate an IP address in C#

_2026-09-09 · Boundstone (https://boundstone.io/blog/validate-ip-csharp)_


If you need to validate an IP address in C#, start with the base class library — it does more of the work than most tutorials admit. `System.Net.IPAddress.TryParse` decides whether a string is a real IPv4 or IPv6 literal and hands you a parsed `IPAddress`; from there the class itself will even classify a few special ranges with no dependency. Where it stops is specific and worth knowing: it won't tell you whether an address is private, a bogon, or otherwise unroutable. This post walks the built-in path first, then covers the gap.

## What .NET already does: format and version

`IPAddress.TryParse` is the honest starting point. It returns `true` for a well-formed literal and hands you a parsed `IPAddress` through the `out` parameter — no exceptions to catch, no regex to maintain.

```csharp
using System.Net;
using System.Net.Sockets;

if (!IPAddress.TryParse(input, out IPAddress? addr))
{
    Console.WriteLine("Not a valid IP literal.");
    return;
}

string version = addr.AddressFamily switch
{
    AddressFamily.InterNetwork   => "IPv4",
    AddressFamily.InterNetworkV6 => "IPv6",
    _                            => "unknown"
};

Console.WriteLine($"Valid {version}: {addr}");
```

`addr.AddressFamily` gives you the version for free: `AddressFamily.InterNetwork` is IPv4, `AddressFamily.InterNetworkV6` is IPv6. Two things to be clear about. `TryParse` validates the literal, not the reachability — it does not ping anything, and a parseable address can be entirely dead. And its rules have tightened across .NET versions, so the shorthand and leading-zero forms that older .NET Framework accepted may be rejected in modern .NET. Parse against the runtime you actually ship.

If rejecting malformed input is all you need, stop here. This is the whole job, and it needs no library.

## The classification .NET ships — and the gap

.NET also classifies a handful of special ranges without any help:

```csharp
bool loopback    = IPAddress.IsLoopback(addr); // 127.0.0.0/8 and ::1
bool linkLocalV6 = addr.IsIPv6LinkLocal;        // fe80::/10
bool multicastV6 = addr.IsIPv6Multicast;        // ff00::/8
```

`IPAddress.IsLoopback` is a static method covering both families. `IsIPv6LinkLocal` and `IsIPv6Multicast` are IPv6-specific properties — they simply return `false` on an IPv4 address, which is safe but means IPv4 link-local and multicast get no built-in helper.

Here is the gap: there is no `IsPrivate` in the base class library. RFC 1918 detection — the 10/8, 172.16/12, and 192.168/16 ranges — is on you. So is IPv4 multicast, bogon detection, and the longer tail of special-purpose blocks. .NET parses the address; it does not have an opinion about whether you should route to it.

## Writing the private-range check by hand

The RFC 1918 check is short. Pull the octets with `GetAddressBytes` and compare:

```csharp
static bool IsPrivateV4(IPAddress addr)
{
    if (addr.AddressFamily != AddressFamily.InterNetwork)
        return false;

    byte[] b = addr.GetAddressBytes();
    return b[0] == 10                                // 10.0.0.0/8
        || (b[0] == 172 && b[1] >= 16 && b[1] <= 31) // 172.16.0.0/12
        || (b[0] == 192 && b[1] == 168);             // 192.168.0.0/16
}
```

That is genuinely all it takes, and for many services it is enough. If you want the background on why these three blocks are set aside — and why IPv6 unique-local (`fc00::/7`) is its own separate byte check that .NET's deprecated `IsIPv6SiteLocal` does not cover — see [what is a private IP address](/blog/what-is-a-private-ip).

## Where the standard library stops

The trouble starts when "a few ranges" becomes "all of them." Do it fully and you are hand-maintaining a table: shared CGNAT space (`100.64.0.0/10`), documentation and TEST-NET blocks, benchmarking, 6to4, the IPv4-mapped-IPv6 forms, and a bogon list that occasionally changes. Then you keep that table consistent across every service that touches an IP, and consistent again if any of them isn't written in C#. That is the point where a single classification endpoint starts to pay for itself — one answer, one contract, everywhere.

## Full classification over HTTP with Boundstone

Boundstone's IP endpoint returns the full public/private/bogon picture over HTTP, so you don't carry the range table yourself:

```csharp
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Http.Headers;

record Checks(string[] performed, string[] not_performed);
record IpResult(bool valid, int version, string normalized,
                string classification, bool is_public, bool is_bogon, Checks checks);

var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "bs_live_YOUR_KEY");

var res = await http.PostAsJsonAsync(
    "https://api.boundstone.io/v1/verify/ip",
    new { ip = "8.8.8.8" });

var result = await res.Content.ReadFromJsonAsync<IpResult>();
Console.WriteLine($"{result!.classification} · public={result.is_public}");
```

The response spells out exactly what it did and did not check:

```json
{
  "valid": true,
  "version": 4,
  "normalized": "8.8.8.8",
  "classification": "public",
  "is_public": true,
  "is_bogon": false,
  "checks": {
    "performed": ["format", "version", "range_classification"],
    "not_performed": ["geolocation", "asn", "hosting_datacenter", "proxy_vpn_tor", "reputation"]
  }
}
```

Read the `not_performed` array plainly, because it is the point. Boundstone does **not** do IP geolocation, ASN lookup, hosting/datacenter detection, proxy/VPN/Tor detection, or reputation — that intelligence needs licensed data and is not shipped today. So a `valid: true` here means the address parsed and was classified by range, and nothing more. If you need to know where an address is or whether it hides a VPN, reach for a licensed IP-intelligence provider; this endpoint won't pretend.

No key handy? [The keyless IP validator](/tools/ip-validator) runs the same classification in the browser. The free tier is 250 credits a month, no card, and credits never expire, which covers a lot of validation before you decide anything. For a file of addresses, `POST /v1/bulk/ip` takes a raw CSV and returns a job. And if your stack isn't .NET everywhere, [validating an IP address in Node.js](/blog/validate-ip-nodejs) hits the same contract from JavaScript.

## The short version

- **Reject malformed input?** `IPAddress.TryParse` is the whole answer. Ship it.
- **IPv4 vs IPv6?** `addr.AddressFamily`. Done.
- **Loopback, link-local, multicast?** Built in — `IsLoopback`, `IsIPv6LinkLocal`, `IsIPv6Multicast`.
- **Private (RFC 1918)?** A four-line byte check. Write it yourself.
- **Full public/private/bogon, the same answer across services, or callers outside .NET?** That's the HTTP call.
- **Geolocation or VPN detection?** Not here — those are `not_performed` today. Use a licensed data provider.

Reach for the network only when the standard library actually runs out. For most of what people mean by "validate an IP address in C#," it doesn't.
