IPv4 and IPv6 network types for Go: (addr, mask) pairs with
first-class non-contiguous masks and full set algebra.
// A mask net/netip cannot express — and every operation stays correct on it.
n := xnetip.MustParseNetwork4("10.0.0.0/255.0.255.0")
n.ContainsAddr(netip.MustParseAddr("10.7.0.9")) // true
n.ContainsAddr(netip.MustParseAddr("10.7.1.9")) // false
fmt.Println(n.LastAddr()) // 10.255.0.255go get github.com/yanet-platform/xnetipRequires Go 1.24. The runtime code depends only on the standard library —
the test dependencies in go.mod (testify, rapid) are never pulled into
your build.
-
Three network types
Use
Network4orNetwork6when the family is known. Their method signatures prevent mixing IPv4 and IPv6 networks. UseNetworkwhen one value must hold either family. All three store normalized(addr, mask)pairs with arbitrary bit masks, giving each masked set a canonical representation. -
Map keys
Network types and guarantee wrappers are immutable, comparable values. Use them directly as
mapkeys for indexing, deduplication and map-backed sets without converting networks to strings. -
Valid zero values
Network4{}represents0.0.0.0/0.Network6{}andNetwork{}represent::/0. Guarantee wrappers preserve those universe values, so zero-initialized fields need no constructor or validity check. -
Set algebra on any mask
Contains,ContainsAddr,Intersection,Intersects,IsDisjoint,IsAdjacent,Merge,SupernetForandDifferencemanipulate masked sets exactly. They avoid enumerating individual members or approximating a non-contiguous set with CIDRs. -
CIDR guarantee
Contiguous[T]proves that the mask is a leading run of ones. Use it at boundaries that require CIDRs. Once constructed,PrefixLen() intandPrefix() netip.Prefixare total,IntersectionandDifferencestay closed over the class, and parsers reject non-contiguous input withErrNonContiguousMask. -
Collection algorithms
Aggregate4andAggregate6fold slices in place using non-contiguous merges.AggregateContiguouscomputes a minimal CIDR cover.RangeToNetworks4andRangeToNetworks6turn an arbitrary IP range into the minimal list of CIDR blocks. Use them to compact network tables or prepare ranges for CIDR-only APIs. -
Lazy iteration
AddrsandAddrsBackwardstream members asiter.Seq[netip.Addr], so callers can stop early without materializing a slice.NumHostBitscarries the exact member count as its base-two exponent, even when the count does not fit an integer type. -
Canonical text
String,AppendTo,MarshalTextandUnmarshalTextprovide round-trippable forms for logs, configuration and encoding. Contiguous networks print asaddr/prefix, while non-contiguous networks print asaddr/mask.Compactrenders host routes as a bareaddr. Strict parsing rejects leading zeros in the prefix length and zones at the input boundary. -
net/netipinteropPlain zone-free
netip.Addris the value type throughout the public API.Contiguousconverts to and fromnetip.Prefix. Existing stdlib callers need no additional IP type, while analogue operations keepnet/netipnames and semantics.
net/netip models a network as netip.Prefix — an addr plus a prefix
length — which can only express a mask that is a run of leading ones, and
stops at membership tests. xnetip complements it where that is not
enough:
net/netip |
xnetip |
|
|---|---|---|
| Network model | addr + prefix length, contiguous masks only |
(addr, mask), any mask |
| Notation | 10.0.0.0/8 |
10.0.0.0/8 and 10.0.0.0/255.0.255.0 |
| Set algebra | Contains, Overlaps |
containment, intersection, difference, merge, adjacency, supernet |
| Slice operations | — | aggregation, range → CIDR list |
| Addr iteration | Addr.Next/Prev |
iter.Seq[netip.Addr], both directions |
Addr range → minimal CIDR list.
first := netip.MustParseAddr("10.0.0.1")
last := netip.MustParseAddr("10.0.0.30")
for block := range xnetip.RangeToNetworks4(first, last) {
fmt.Println(block)
}
// 10.0.0.1/32
// 10.0.0.2/31
// 10.0.0.4/30
// 10.0.0.8/29
// 10.0.0.16/29
// 10.0.0.24/30
// 10.0.0.28/31
// 10.0.0.30/32Aggregation beyond CIDR. Two /24 networks separated by a gap cannot
share one CIDR without including extra members. Aggregate4 can still
represent their exact union by clearing the differing bit in the mask:
nets := []xnetip.Network4{
xnetip.MustParseNetwork4("10.0.0.0/24"),
xnetip.MustParseNetwork4("10.0.2.0/24"),
}
nets = xnetip.Aggregate4(nets) // in place, no allocation
fmt.Println(nets)
// [10.0.0.0/255.255.253.0]The result contains 10.0.0.0/24 and 10.0.2.0/24, but not the
10.0.1.0/24 gap.
Exclude a network from the IPv4 universe. Difference splits the
remainder into pairwise-disjoint CIDRs. Together, they cover the entire IPv4
space except 10.0.0.0/8:
universe := xnetip.MustParseContiguous4("0.0.0.0/0")
excluded := xnetip.MustParseContiguous4("10.0.0.0/8")
for block := range universe.Difference(excluded) {
fmt.Println(block)
}
// 128.0.0.0/1
// 64.0.0.0/2
// 32.0.0.0/3
// 16.0.0.0/4
// 0.0.0.0/5
// 12.0.0.0/6
// 8.0.0.0/7
// 11.0.0.0/8The full API is documented on pkg.go.dev.
- Operations do not allocate — the exceptions are
String/MarshalTextresults and error construction — and hot paths are pinned bytesting.AllocsPerRuntests. - No
unsafe, no cgo, no reflection. - IPv4 and IPv6 take the same algorithm through every operation. Behavior never diverges between families.
- Tested with property-based checks (differential against
net/netipwhere an oracle exists) and fuzzed parsers.
Apache 2.0, see LICENSE.