# IPNetwork: CIDR arithmetic for .NET, with a native command line tool

> IPNetwork2 is a BSD-2-clause C# library and command line program that parses IPv4 and IPv6 networks and answers the questions a subnet plan raises, including netmask, broadcast, usable range, containment and overlap, with a light API and full unit test coverage. It suits a .NET developer working with address plans, and the CLI ships as a native AOT binary with no runtime required.

**lduchosal/ipnetwork** — IPNetwork command line and C# library take care of complex network, IP, IPv4, IPv6, netmask, CIDR, subnet, subnetting, supernet, and supernetting calculation for .NET developers. It works with IPv4 as well as IPv6, is written in C#, has a light and clean API, and is fully unit-tested

- Repository: https://github.com/lduchosal/ipnetwork
- Stars: 525 · Forks: 98
- Language: C#
- License: BSD-2-Clause
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/lduchosal-ipnetwork

## What it computes, and how the API is shaped

IPNetwork is a command line tool and a C# library that take care of complex network, IP, IPv4, IPv6, netmask, CIDR, subnet, subnetting, supernet and supernetting calculation for .NET developers. The documentation repeats the same description with a little more detail, adding that it works with IPv4 as well as IPv6, is written in C#, has a light and clean API, and is fully unit-tested with 100% code coverage.

The API shape is the interesting part, and the first example shows it. You parse one string and everything else is a property:

```C#
IPNetwork2 ipnetwork = IPNetwork2.Parse("192.168.168.100/24");

Console.WriteLine("Network : {0}", ipnetwork.Network);
Console.WriteLine("Cidr : {0}", ipnetwork.Cidr);
Console.WriteLine("Netmask : {0}", ipnetwork.Netmask);
Console.WriteLine("Broadcast : {0}", ipnetwork.Broadcast);

Console.WriteLine("FirstUsable : {0}", ipnetwork.FirstUsable);
Console.WriteLine("LastUsable : {0}", ipnetwork.LastUsable);
Console.WriteLine("Usable : {0}", ipnetwork.Usable);

Console.WriteLine("First : {0}", ipnetwork.First);
Console.WriteLine("Last : {0}", ipnetwork.Last);
Console.WriteLine("Total : {0}", ipnetwork.Total);
```

Note what the input is. The string carries a host address, 192.168.168.100, and a prefix length of 24, and the parsed object reports the network as 192.168.168.0 rather than rejecting the host part. That is the behaviour a subnet planner wants, because the address being asked about is usually a host inside the range and the question is which range it falls in.

The properties fall into two groups, and the distinction is the thing the library exists to get right. The first group is the address arithmetic: Network, Cidr, Netmask and Broadcast. The second group is the host range: First, Last and Total for the whole block, and FirstUsable, LastUsable and Usable for the addresses a host can actually be assigned.

For an IPv4 /24 the two groups differ by exactly two addresses, and the documentation output shows it:

```MD
Network : 192.168.168.0
Cidr : 24
Netmask : 255.255.255.0
Broadcast : 192.168.168.255

FirstUsable : 192.168.168.1
LastUsable : 192.168.168.254
Usable : 254

First : 192.168.168.0
Last : 192.168.168.255
Total : 256
```

So the library is not only doing bit arithmetic, it is also applying the convention that the network address and the broadcast address are not assignable to hosts, which is a convention rather than a fact about IP and one of the things that has to be consistent across every tool in an address plan.

## IPv6, where the arithmetic stops matching the intuition

The second example is the same code against an IPv6 prefix, and the output is worth reading closely because it is where the IPv4 intuitions break.

```C#
IPNetwork2 ipnetwork = IPNetwork2.Parse("2001:0db8::/64");
```

gives:

```JS
Network : 2001:db8::
Netmask : ffff:ffff:ffff:ffff::
Broadcast :
FirstUsable : 2001:db8::
LastUsable : 2001:db8::ffff:ffff:ffff:ffff
Usable : 18446744073709551616
Cidr : 64
```

Three things are different from the IPv4 case, and each of them is a real property of IPv6 rather than a quirk of the library.

The broadcast address is empty, because IPv6 has no broadcast. The netmask is printed as a prefix rather than as four bytes of 255, because the address space is 128 bits wide. And the usable count is 18446744073709551616, which is two to the power of 64, because a 64-bit prefix on a 128-bit address is half the entire address space, and that many addresses is not a number a 32-bit integer could hold, which is a fair hint about the types the library has to use internally.

The usable range also starts at the network address itself, 2001:db8::, rather than one past it, because there is no network address to reserve in the IPv6 sense.

## Containment and overlap, the questions a subnet plan actually asks

The third example turns to the questions that matter when two plans overlap, and it is the part of the API that earns the library its place in a network management tool.

```C#
IPNetwork2 ipnetwork = IPNetwork2.Parse("2001:0db8::/64");

IPAddress ipaddress = IPAddress.Parse("2001:0db8::1");
IPAddress ipaddress2 = IPAddress.Parse("2001:0db9::1");

IPNetwork2 ipnetwork2 = IPNetwork2.Parse("2001:0db8::1/128");
IPNetwork2 ipnetwork3 = IPNetwork2.Parse("2001:0db9::1/64");

bool contains1 = ipnetwork.Contains(ipaddress);
bool contains2 = ipnetwork.Contains(ipaddress2);
bool contains3 = ipnetwork.Contains(ipnetwork2);
bool contains4 = ipnetwork.Contains(ipnetwork3);
```

The Contains method has an overload that takes an address and one that takes another network, and the distinction is the point, because a single address inside a prefix is a different question from a whole prefix inside another prefix. The first two calls ask whether an address falls in the range, and the second two ask whether a network does, and a tool that treats the two the same will eventually report that a route covers a block it does not.

The Overlap method answers the remaining case, whether two ranges intersect without one containing the other. That is the question a collision check has to ask, and it is the one that has no answer in a simple prefix comparison, because two ranges can share addresses while neither is a prefix of the other.

For an IPv4 library the interesting part is usually the same arithmetic expressed with fewer lines. Here the value is in having one implementation of the conventions, and in having it behave identically for the two address families, which is the part that is easy to get wrong when each team writes its own helper.

## Installing the CLI without a .NET runtime

The command line tool is distributed as a Native AOT binary, which means no .NET runtime is required on the machine that runs it, and that is the point of the distribution list.

On macOS and Linux through Homebrew:

```bash
brew install lduchosal/ipnetwork/ipnetwork
```

On Windows through Chocolatey:

```powershell
choco install ipnetwork
```

And on Linux by hand, with a separate archive for each architecture:

```bash
# x64
curl -sL https://github.com/lduchosal/ipnetwork/releases/latest/download/ipnetwork-linux-x64.tar.gz | tar xz -C /usr/local/bin

# ARM64
curl -sL https://github.com/lduchosal/ipnetwork/releases/latest/download/ipnetwork-linux-arm64.tar.gz | tar xz -C /usr/local/bin
```

The library itself is a NuGet package, installed the usual way:

```
PM> nuget install IPNetwork2
```

The naming difference is worth flagging, because it is the kind of detail that costs an afternoon. The repository is ipnetwork, the library package on NuGet is IPNetwork2, the Chocolatey package is ipnetwork, and the Homebrew formula comes from a personal tap rather than from homebrew-core. So the command line tool and the library have different names, and the command line tool is not in the main Homebrew repository.

The AOT decision has a practical consequence beyond the download size. A native AOT binary has no JIT, so it starts almost instantly and does not need a runtime on the target machine, which is what makes it usable on a server, in a container or on a machine where installing a .NET runtime is a decision somebody has to justify. The cost of that choice is that the binary is platform specific, which is why there are separate archives for x64 and ARM64 rather than one portable artefact.

The homebrew repository for the formula is maintained by the same author, and the fact that it is a personal tap rather than a core formula is a small signal about the project's scale.

## The quality signals, and what the repository carries

The badge wall at the top of the readme is unusually long, and it is worth reading as a description of how the project is maintained rather than as decoration.

There is the NuGet badge and the download count, the Chocolatey badge, the Homebrew badge, an AppVeyor build status, a FOSSA licence and scanning status, a Coveralls coverage badge, a GitHub last commit badge, a CodeFactor rating, a sponsor badge, and then a large block of SonarCloud badges: lines of code, maintainability rating, coverage, technical debt, quality gate status, code smells, reliability rating, duplicated lines percentage, vulnerabilities, bugs and security rating.

The SonarCloud block is the part that says something specific. A quality gate status and a maintainability rating are continuous measures that fail a build when they move, and publishing a duplicated-lines percentage is a level of transparency that most libraries do not offer. The project also carries a .sonarlint/ directory in the tree, which is the editor-side configuration for the same analysis.

The rest of the tree is consistent with a long-lived .NET library. There is a src/ directory, a doc/ directory, a nant/ directory for the older build script, a chocolatey/ directory holding the package definition, an appveyor.yml for the Windows build, a .coveralls.yml, a .mergify.yml for the merge queue, a .config/ directory for the dotnet tooling, a CODE_OF_CONDUCT.md, a DONATE.md, and an llms.txt file.

That last file deserves a mention, because it is a new convention. An llms.txt is a plain-text index of a project's documentation written for language models rather than for people, and having one at the root of a library means an assistant pointed at this repository gets a structured route into the documentation instead of a directory listing. It is a small thing and it signals that the maintainer is thinking about how the library is consumed by tools, not only by developers.

The recent releases are 4.3.0 on 2026-05-17, 4.2.0 on 2026-04-05 and 4.1.1 on 2026-04-04, and the 4.3.0 release note is short and specific, that it returns netstandard2.0, which reads as a compatibility change rather than a feature. The last push to the repository was on 2026-10-15, and the default branch is master with a BSD-2-Clause licence.

## Conclusion

IPNetwork2 is worth adding to a .NET project that reasons about address ranges, because the calculations it performs are the ones everybody gets wrong by hand, including the usable range excluding the network and broadcast addresses and the difference between a network's address range and the addresses a host can actually use, and because the same library is available as a native command line tool, so a shell script and a C# service can agree on the same answers. It is the wrong tool if you only need to test whether one address is inside one range. Before you depend on it, check three things: which package you are installing, because the library is IPNetwork2 on NuGet while the command line tool is published under the ipnetwork name on Chocolatey and through a personal Homebrew tap, so the two artefacts do not share a name; which target frameworks you need, because the most recent release is titled for returning netstandard2.0, which suggests the modern build has been dropping older target support; and that the semantics of the usable range are what you expect, since the IPv6 example in the documentation reports a usable count of 18446744073709551616 for a 64-bit prefix and leaves the broadcast address empty, which is correct for IPv6 but differs from the IPv4 behaviour of excluding both ends.

## FAQ

### How do I install the IPNetwork library and the command line tool?

The library is the IPNetwork2 package on NuGet, installed with nuget install IPNetwork2, and the command line tool is a native AOT binary that needs no .NET runtime. It is available through Homebrew with brew install lduchosal/ipnetwork/ipnetwork, through Chocolatey on Windows with choco install ipnetwork, or as a manual tarball for Linux x64 and ARM64 extracted into the path.

### What does IPNetwork2 give me for a parsed network?

A parsed network reports the network address, the CIDR prefix, the netmask and the broadcast address, and separately the first and last address of the block with the total count, plus the first and last usable address and the usable count. For an IPv4 /24 the usable range is 254 addresses because the network and broadcast addresses are excluded, while for IPv6 the broadcast is empty and the usable count for a /64 is 18446744073709551616.

### Can IPNetwork2 tell me whether two networks overlap?

Yes. The third example in the documentation parses a prefix and two addresses and two further networks, and calls Contains on both an IPAddress and an IPNetwork2, showing that there is an overload for a single address and one for a whole network. There is also an Overlap method for the case where two ranges intersect without one containing the other.

## Sources

- [Issues](https://github.com/lduchosal/ipnetwork/issues)
- [lduchosal/ipnetwork on GitHub](https://github.com/lduchosal/ipnetwork)
- [License: BSD-2-Clause](https://github.com/lduchosal/ipnetwork/blob/master/LICENSE)
- [README](https://github.com/lduchosal/ipnetwork/blob/master/README.md)
- [Releases](https://github.com/lduchosal/ipnetwork/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lduchosal-ipnetwork
