Validate an IP address in C#
The .NET base class library parses and partly classifies IP addresses on its own; here is where it stops and how to fill the private and bogon gap.
Contents
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.
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:
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:
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.
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:
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:
{
"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 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 hits the same contract from JavaScript.
The short version
- Reject malformed input?
IPAddress.TryParseis 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_performedtoday. 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.