subnet

package
v0.31.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: CC0-1.0 Imports: 9 Imported by: 0

Documentation

Overview

Package subnet provides utilities for working with IP subnets, including well-known CIDR ranges, IP address arithmetic, and network manipulation. It supports both IPv4 and IPv6 operations such as splitting, extending, narrowing, and enumerating subnets.

Index

Constants

This section is empty.

Variables

View Source
var (
	CIDRIPv4ThisNetwork = MustCIDR(
		"0.0.0.0/8",
	) // "This network"; not usable as ordinary host/network space.
	CIDRIPv4Loopback  = MustCIDR("127.0.0.0/8") // Loopback.
	CIDRIPv4LinkLocal = MustCIDR(
		"169.254.0.0/16",
	) // IPv4 link-local/APIPA.
	CIDRIPv4Multicast = MustCIDR("224.0.0.0/4") // IPv4 multicast.
	CIDRIPv4Reserved  = MustCIDR(
		"240.0.0.0/4",
	) // Reserved by protocol.
	CIDRIPv4LimitedBroadcast = MustCIDR("255.255.255.255/32")
	CIDRIPv4SharedAddress    = MustCIDR(
		"100.64.0.0/10",
	) // Shared Address Space / CGNAT; also used by Tailscale.
	CIDRIPv4Benchmarking = MustCIDR(
		"198.18.0.0/15",
	) // Benchmarking, not RFC1918 private-use.
	CIDRIPv4Documentation1 = MustCIDR("192.0.2.0/24")    // TEST-NET-1.
	CIDRIPv4Documentation2 = MustCIDR("198.51.100.0/24") // TEST-NET-2.
	CIDRIPv4Documentation3 = MustCIDR("203.0.113.0/24")  // TEST-NET-3.

	CIDRRouterDefault1921680 = MustCIDR("192.168.0.0/24")
	CIDRRouterDefault1921681 = MustCIDR("192.168.1.0/24")
	CIDRRouterDefault1921682 = MustCIDR("192.168.2.0/24")
	CIDRRouterDefault1921683 = MustCIDR(
		"192.168.3.0/24",
	) // Huawei and others.
	CIDRRouterDefault1921684 = MustCIDR(
		"192.168.4.0/24",
	) // Zyxel and others.
	CIDRRouterDefault1921688 = MustCIDR(
		"192.168.8.0/24",
	) // Huawei / GL.iNet-style defaults.
	CIDRRouterDefault19216810 = MustCIDR(
		"192.168.10.0/24",
	) // Zyxel/Motorola/etc.; replaces duplicate Zyxel2/Motorola vars.
	CIDRRouterDefault19216831 = MustCIDR(
		"192.168.31.0/24",
	) // Xiaomi/MiWiFi-style default.
	CIDRRouterDefault19216850 = MustCIDR(
		"192.168.50.0/24",
	) // TP-Link/ASUS/Xiaomi-style defaults.
	CIDRRouterDefault19216868 = MustCIDR(
		"192.168.68.0/24",
	) // TP-Link Deco-style default.
	CIDRRouterDefault19216886 = MustCIDR(
		"192.168.86.0/24",
	) // Google/Nest Wifi.
	CIDRRouterDefault19216888 = MustCIDR(
		"192.168.88.0/24",
	) // MikroTik default.
	CIDRRouterDefault192168100 = MustCIDR(
		"192.168.100.0/24",
	) // Cable modem / Huawei-style management LAN.
	CIDRRouterDefault192168178 = MustCIDR("192.168.178.0/24") // FRITZ!Box.
	CIDRRouterDefault192168188 = MustCIDR(
		"192.168.188.0/24",
	) // FRITZ!Box/repeater-style defaults.

	CIDRVirtualBoxHostOnly = MustCIDR(
		"192.168.56.0/24",
	) // VirtualBox host-only default.
	CIDRLibvirtDefault = MustCIDR(
		"192.168.122.0/24",
	) // libvirt default NAT network.
	CIDRDockerBridge = MustCIDR(
		"172.17.0.0/16",
	) // Classic docker0 bridge.
	CIDRDockerDesktopMac = MustCIDR(
		"192.168.65.0/24",
	) // Common Docker Desktop internal subnet; treat as heuristic.
	CIDRPodmanDefault = MustCIDR(
		"10.88.0.0/16",
	) // Podman root bridge default.

	// Docker's documented default address pools cover these shapes.
	// If you ever allocate from 172.16.0.0/12, it is often simplest to
	// avoid all of 172.17.0.0/16 through 172.31.0.0/16 or avoid the whole
	// 172.16.0.0/12 parent entirely.
	CIDRDockerPool17217 = MustCIDR("172.17.0.0/16")
	CIDRDockerPool17218 = MustCIDR("172.18.0.0/15")
	CIDRDockerPool17220 = MustCIDR("172.20.0.0/14")
	CIDRDockerPool17224 = MustCIDR("172.24.0.0/14")
	CIDRDockerPool17228 = MustCIDR("172.28.0.0/14")
	CIDRDockerPool192   = MustCIDR(
		"192.168.0.0/16",
	) // Too broad for default banning if parent is 192.168/16.

	CIDRKubeAdmServices = MustCIDR(
		"10.96.0.0/12",
	) // kubeadm default Service CIDR.
	CIDRK3sPods     = MustCIDR("10.42.0.0/16") // k3s default pod CIDR.
	CIDRK3sServices = MustCIDR("10.43.0.0/16") // k3s default service CIDR.
	CIDRK3s         = MustCIDR(
		"10.42.0.0/15",
	) // Aggregate of k3s pods+services.
	CIDRKindFlannelPods = MustCIDR(
		"10.244.0.0/16",
	) // kind/flannel-style pod CIDR.
	CIDRMinikubeDocker = MustCIDR("192.168.49.0/24")
	CIDRMinikubeVM     = MustCIDR("192.168.59.0/24")
	CIDRMinikubeKVM2   = MustCIDR("192.168.39.0/24")
	CIDRCalicoDefault  = MustCIDR(
		"192.168.0.0/16",
	) // Calico default pool in many manifests; too broad for default banning.

	CIDROpenVPNDefault = MustCIDR(
		"10.8.0.0/24",
	) // OpenVPN examples commonly use 10.8.0.0/24.
	CIDROpenVPNBroad = MustCIDR(
		"10.8.0.0/16",
	) // Optional broader exclusion.
	CIDRTailscaleCGNAT = MustCIDR(
		"100.64.0.0/10",
	) // Same as Shared Address Space; Tailscale uses this by default.

	CIDRCommon10LANs = MustCIDR(
		"10.0.0.0/15",
	) // Covers very common 10.0.0.0/16 and 10.1.0.0/16 LAN/VNet defaults.
	CIDRAzureDefault = MustCIDR(
		"10.0.0.0/16",
	) // Azure examples/default CLI VNet address space; already covered above.
	CIDRAWSDefaultVPC = MustCIDR("172.31.0.0/16") // AWS default VPC.
	CIDRGCPAutoMode   = MustCIDR(
		"10.128.0.0/9",
	) // GCP auto-mode VPC range; large/aggressive optional exclusion.

	CIDRIPv6Unspecified    = MustCIDR("::/128")
	CIDRIPv6Loopback       = MustCIDR("::1/128")
	CIDRIPv6IPv4Mapped     = MustCIDR("::ffff:0:0/96")
	CIDRIPv6NAT64WellKnown = MustCIDR("64:ff9b::/96")
	CIDRIPv6NAT64Local     = MustCIDR("64:ff9b:1::/48")
	CIDRIPv6DiscardOnly    = MustCIDR("100::/64")
	CIDRIPv6Dummy          = MustCIDR("100:0:0:1::/64")
	CIDRIPv6Teredo         = MustCIDR("2001::/32")
	CIDRIPv6Benchmarking   = MustCIDR("2001:2::/48")
	CIDRIPv6Documentation  = MustCIDR("2001:db8::/32")
	CIDRIPv6Documentation2 = MustCIDR("3fff::/20")
	CIDRIPv66to4           = MustCIDR("2002::/16")
	CIDRIPv6SRv6SIDs       = MustCIDR("5f00::/16")
	CIDRIPv6LinkLocal      = MustCIDR("fe80::/10")
	CIDRIPv6Multicast      = MustCIDR("ff00::/8")

	// ULA handling:
	//   fc00::/7 is the whole ULA block.
	//   fd00::/8 is the normal locally-assigned half used for random ULA /48s.
	//   fc00::/8 is not normally used by RFC4193 local generation, but cjdns/
	//   Hyperboria-style overlays use it.
	//
	// Do NOT ban fd00::/8 if your allocator's IPv6 parent is a random ULA /48.
	CIDRIPv6ULA         = MustCIDR("fc00::/7")
	CIDRIPv6ULALocal    = MustCIDR("fd00::/8")
	CIDRHyperboriaCJDNS = MustCIDR(
		"fc00::/8",
	) // cjdns/Hyperboria overlay usage.

	CIDRYggdrasilNetwork = MustCIDR(
		"200::/7",
	) // Yggdrasil; includes 200::/8 node addrs and 300::/8 routed prefixes.
	CIDRMycelium = MustCIDR("400::/7") // Mycelium overlay network.

	CIDRKindIPv6Services = MustCIDR(
		"fd00:10:96::/112",
	) // kind IPv6 service subnet default.
)

Some well-known CIDRs that are likely to collide with local, container, VM, VPN, Kubernetes, cloud, or overlay-network usage.

Major sources:

  • IANA IPv4/IPv6 Special-Purpose Address Registries.
  • RFC1918 / RFC4193.
  • Docker, Kubernetes, k3s, Podman, minikube docs.
  • Common router, VM, VPN, and overlay-network defaults.

Functions

func Capacity

func Capacity(network *net.IPNet) big.Int

Capacity returns the total number of IP addresses in the network, including the network address and broadcast address.

func Contains

func Contains(parent, child *net.IPNet) bool

Contains reports whether child is a subnet of parent (or equal to parent). It checks that child's network is entirely contained within parent's network.

func CopyIPNet added in v0.22.0

func CopyIPNet(n *net.IPNet) *net.IPNet

copyIPNet creates a deep copy of a net.IPNet.

func Extend

func Extend(network *net.IPNet, bits int, num *big.Int) (net.IPNet, error)

Extend creates a supernet of the given network by reducing the prefix length by the specified number of bits. The num parameter selects which supernet to return (0 for the one containing the original network, 1 for the next, etc.). For example, Extend(192.168.1.0/24, 1, 0) returns 192.168.0.0/23, and Extend(192.168.1.0/24, 1, 1) returns 192.168.2.0/23. Returns an error if bits is negative, num is negative, or the resulting prefix length would be less than 0.

func ExtendIPNet added in v0.22.0

func ExtendIPNet(base *net.IPNet, numBits int, index int) (*net.IPNet, error)

extendIPNet creates a subnet by extending the given network to a more specific prefix. numBits is how many additional bits to add, and index is which subnet to return (0 to 2^numBits-1).

func FromRange

func FromRange(first, last net.IP) (net.IPNet, error)

FromRange constructs a network from its first and last IP addresses. Returns an error if the range doesn't represent a valid CIDR block (i.e., if the range isn't aligned to a power-of-2 boundary).

func IPIndex

func IPIndex(network *net.IPNet, i *big.Int) (net.IP, error)

IPIndex returns the IP address at the given index within the network. Index 0 corresponds to the network address, and the maximum valid index is 2^(host_bits) - 1, where host_bits = total_bits - prefix_length. Returns an error if the index is negative or out of range.

func MustCIDR

func MustCIDR(s string) net.IPNet

func Narrow

func Narrow(network *net.IPNet, bits int, num *big.Int) (net.IPNet, error)

Narrow creates a subnet of the given network by increasing the prefix length by the specified number of bits. The num parameter selects which subnet to return (0 for the first subnet, 1 for the second, etc.). For example, Narrow(192.168.1.0/24, 1, 0) returns 192.168.1.0/25, and Narrow(192.168.1.0/24, 1, 1) returns 192.168.1.128/25. Returns an error if bits is negative, num is negative, the resulting prefix length would exceed the maximum (32 for IPv4, 128 for IPv6), or num is out of range for the specified bits.

func Next

func Next(ip net.IP) net.IP

Next returns the next IP address after ip, wrapping around to all zeros if ip is the maximum address (all 0xFF bytes).

func Overlap

func Overlap(nets []*net.IPNet) bool

Overlap reports whether any of the provided networks overlap with each other. Returns false for empty list or single network.

func Prev

func Prev(ip net.IP) net.IP

Prev returns the previous IP address before ip, wrapping around to all 0xFF bytes if ip is the minimum address (all zeros).

func Range

func Range(network *net.IPNet) (net.IP, net.IP)

Range returns the first (network) and last (broadcast) IP addresses in the network.

func Split

func Split(network *net.IPNet) (*net.IPNet, *net.IPNet, error)

Split divides a network into two equal halves by incrementing the prefix length by one. Returns an error if the network cannot be split further (e.g., /32 for IPv4 or /128 for IPv6).

Types

type CombinedAllocator added in v0.22.0

type CombinedAllocator struct {
	// contains filtered or unexported fields
}

CombinedAllocator allocates both subnets and IP addresses from optional IPv4 and IPv6 parent networks.

Subnet allocation is close to NewSubnetAllocator: allocated and reserved subnets are reference-counted, allocations start at a pseudo-random subnet inside the parent and then scan forward with wraparound, and the optional subnet filter can reject otherwise-free candidates. In addition, combined subnet allocation also treats every reserved, allocated, or banned IP address as a one-address blocker, so a subnet containing one of those addresses is never returned.

IP allocation is backed by a pool of subnets that were allocated through the same subnet allocator state. An IP allocation first tries existing pool subnets with free usable addresses. If none can satisfy the request, it allocates one more pool subnet using poolPrefix4 or poolPrefix6 and then allocates an address from that subnet. The first and last address of every pool subnet are skipped, matching NewIPAllocator behavior for both IPv4 and IPv6.

ReserveIP accepts any valid IPv4 or IPv6 address, including addresses outside the configured parents and outside the current pool. Those reservations still block subnet allocations if they fall inside a candidate subnet. ReserveSubnet likewise accepts any valid subnet, including outside supernets that overlap a parent.

Banned addresses and subnets are permanent blockers installed by the constructor. FreeIP, FreeSubnet, FreeAllIP, and FreeAllSubnets do not remove bans. IP allocation examines at most 1000 candidate addresses per call. Subnet allocation examines at most 1000 candidates that reach the filter, matching the simpler subnet allocator's blocker-skipping behavior.

func NewCombinedAllocator added in v0.22.0

func NewCombinedAllocator(
	ipv4 *net.IPNet,
	ipv6 *net.IPNet,
	poolPrefix4 int,
	poolPrefix6 int,
	ipFilter IPFilter,
	subnetFilter SubnetFilter,
	bannedIPs []net.IP,
	bannedSubnets []*net.IPNet,
	rng *rand.Rand,
) *CombinedAllocator

NewCombinedAllocator creates an allocator with shared IP and subnet reservation state.

ipv4 and ipv6 are optional parent networks. Each valid parent enables allocations for that address family; nil or malformed parents simply disable that family. poolPrefix4 and poolPrefix6 are the subnet prefix lengths used when IP allocation needs to grow its backing subnet pool.

ipFilter and subnetFilter behave like the filters accepted by NewIPAllocator and NewSubnetAllocator. bannedIPs and bannedSubnets may be nil. Bans are copied, normalized, and treated as permanent reservations.

rng selects the first candidate subnet examined by each subnet allocation, including the internal subnet allocations used to grow the IP allocation pools. If rng is nil, a deterministic generator with a constant seed is used so tests and callers that do not need custom randomness remain reproducible. The allocator serializes its own use of rng, but callers must not use the same *rand.Rand concurrently elsewhere without their own synchronization.

Filters are called while the allocator lock is held. They should be fast and must not call methods on the same allocator.

func NewDefaultAllocator added in v0.22.0

func NewDefaultAllocator(config DefaultAllocatorConfig) *CombinedAllocator

NewDefaultAllocator creates a CombinedAllocator with opinionated private networking defaults.

The allocator owns 10.0.0.0/8 for IPv4 allocations and a single random fdxx:xxxx:xxxx::/48 ULA for IPv6 allocations. The IPv6 parent is generated once during construction from config.Rng; the same generator is then passed to NewCombinedAllocator for subsequent randomized subnet allocation. A nil Rng uses a deterministic constant source, matching NewCombinedAllocator.

IP allocations grow from /24 IPv4 pools and /64 IPv6 pools. Built-in banned subnets are merged with config.BannedSubnets and include common 10/8 local, container, VPN, and Kubernetes defaults from subnet.go, plus the kind IPv6 service example that could overlap a random ULA parent. Caller-supplied bans are copied by NewCombinedAllocator and remain permanent until the allocator is discarded.

func (*CombinedAllocator) AllocIP4 added in v0.22.0

func (a *CombinedAllocator) AllocIP4() (net.IP, *net.IPNet)

func (*CombinedAllocator) AllocIP6 added in v0.22.0

func (a *CombinedAllocator) AllocIP6() (net.IP, *net.IPNet)

func (*CombinedAllocator) AllocSubnet4 added in v0.22.0

func (a *CombinedAllocator) AllocSubnet4(prefix int) *net.IPNet

func (*CombinedAllocator) AllocSubnet6 added in v0.22.0

func (a *CombinedAllocator) AllocSubnet6(prefix int) *net.IPNet

func (*CombinedAllocator) FreeAllIP added in v0.22.0

func (a *CombinedAllocator) FreeAllIP()

func (*CombinedAllocator) FreeAllSubnets added in v0.22.0

func (a *CombinedAllocator) FreeAllSubnets()

func (*CombinedAllocator) FreeIP added in v0.22.0

func (a *CombinedAllocator) FreeIP(ip net.IP)

func (*CombinedAllocator) FreeSubnet added in v0.22.0

func (a *CombinedAllocator) FreeSubnet(subnet *net.IPNet)

func (*CombinedAllocator) ReserveIP added in v0.22.0

func (a *CombinedAllocator) ReserveIP(ip net.IP)

func (*CombinedAllocator) ReserveSubnet added in v0.22.0

func (a *CombinedAllocator) ReserveSubnet(subnet *net.IPNet)

type DefaultAllocatorConfig added in v0.22.0

type DefaultAllocatorConfig struct {
	// IPFilter is an optional callback used to reject candidate IP addresses.
	// It has the same locking and reentrancy constraints as NewCombinedAllocator.
	IPFilter IPFilter

	// SubnetFilter is an optional callback used to reject candidate subnets.
	// It has the same locking and reentrancy constraints as NewCombinedAllocator.
	SubnetFilter SubnetFilter

	// BannedIPs are permanent IP blockers merged into the allocator.
	BannedIPs []net.IP

	// BannedSubnets are permanent subnet blockers. They are merged with the
	// built-in defaults for common local, container, VPN, and cluster CIDRs
	// that can collide with the default 10.0.0.0/8 parent or random ULA parent.
	BannedSubnets []*net.IPNet

	// Rng generates the IPv6 ULA parent and selects randomized subnet starts.
	// If nil, a deterministic generator with the same constant seed used by
	// NewCombinedAllocator is used.
	Rng *rand.Rand
}

DefaultAllocatorConfig holds optional policy inputs for NewDefaultAllocator.

NewDefaultAllocator intentionally does not expose parent networks or pool prefix lengths. It always allocates IPv4 space from 10.0.0.0/8 using /24 IP pools, and IPv6 space from one random fd00::/48 ULA generated at construction time using Rng, with /64 IP pools.

type IPAllocator

type IPAllocator interface {
	// ReserveIP marks an IP address as reserved. If the same address is
	// reserved multiple times, the same number of FreeIP calls is required
	// before it can be allocated again.
	ReserveIP(ip net.IP)

	// AllocIP4 returns a free IPv4 address, a copy of the subnet it was
	// allocated from, and marks the address as reserved.
	// It returns nil IP and subnet values if this allocator does not own an IPv4
	// subnet or there are no free IPv4 addresses available.
	AllocIP4() (net.IP, *net.IPNet)

	// AllocIP6 returns a free IPv6 address, a copy of the subnet it was
	// allocated from, and marks the address as reserved.
	// It returns nil IP and subnet values if this allocator does not own an IPv6
	// subnet or there are no free IPv6 addresses available.
	AllocIP6() (net.IP, *net.IPNet)

	// FreeIP decrements reserve counter for provided IP.
	// If this IP was not reserved, FreeIP is a no-op.
	FreeIP(ip net.IP)

	// FreeAll removes all reserved marks, making all previously allocated IPs
	// available again.
	FreeAllIP()
}

IPAllocator reserves, allocates, and frees IP addresses from owned subnets. All methods are safe for concurrent use.

func NewIPAllocator

func NewIPAllocator(network *net.IPNet, filter IPFilter) IPAllocator

NewIPAllocator creates an IPAllocator that owns a copy of network.

The allocator only returns addresses from the provided subnet and never returns the first or last address in the range. For IPv4 these are the network and broadcast addresses. The same rule is intentionally applied to IPv6 so callers can rely on consistent reservation behavior.

nil and malformed networks create an empty allocator whose allocation methods return nil IP and subnet values.

If filter is non-nil, allocation calls evaluate each otherwise available candidate with the filter before reserving it. Rejected candidates are skipped without being reserved. To avoid unbounded scans when the filter rejects every candidate, each allocation call tries at most 1000 filtered candidates before returning nil.

Filters are called while the allocator lock is held. They should be fast and must not call methods on the same allocator.

type IPFilter added in v0.22.0

type IPFilter func(net.IP) bool

IPFilter decides whether an IP address may be allocated. It returns true to allow the candidate and false to make the allocator skip it and try another address.

type RandomAllocatorConfig

type RandomAllocatorConfig struct {
	// Filter is an optional callback to validate randomly generated subnets.
	// If Filter returns false, the subnet is rejected and another random
	// subnet will be tried. This is useful for avoiding subnets already
	// in use by the system.
	Filter func(*net.IPNet) bool
}

RandomAllocatorConfig holds configuration for NewRandomAllocator.

type SubnetAllocator

type SubnetAllocator interface {
	// ReserveSubnet marks a subnet as reserved. If subnet was reserved multiple
	// times same count of FreeSubnet calls should be needed to free it.
	//
	// Valid reserved subnets are tracked even when they are outside of the
	// allocator's parent network. Outside reservations can still block future
	// allocations if they overlap the parent, for example when reserving a
	// supernet that contains the parent.
	ReserveSubnet(subnet *net.IPNet)

	// AllocSubnet4 returns a free subnet with the provided prefix length and
	// marks it as reserved.
	// It returns nil if it is not possible to allocate subnet with this prefix.
	AllocSubnet4(prefix int) *net.IPNet

	// AllocSubnet6 returns a free subnet with the provided prefix length and
	// marks it as reserved.
	// It returns nil if it is not possible to allocate subnet with this prefix.
	AllocSubnet6(prefix int) *net.IPNet

	// FreeSubnet decrements reserve counter for provided subnet.
	// If the subnet was not reserved, FreeSubnet is a no-op.
	FreeSubnet(subnet *net.IPNet)

	// FreeAllSubnets removes all reserved marks, making all previously allocated
	// and reserved subnets available again.
	FreeAllSubnets()
}

SubnetAllocator reserves, allocates, and frees subnets from one owned parent network. All methods are safe for concurrent use.

func NewSubnetAllocator added in v0.22.0

func NewSubnetAllocator(
	parent *net.IPNet,
	filter SubnetFilter,
) SubnetAllocator

NewSubnetAllocator creates a SubnetAllocator that owns a copy of parent.

The allocator only returns subnets inside parent. ReserveSubnet accepts any valid IPv4 or IPv6 CIDR and keeps it in the reservation table even when it is outside parent, because an outside subnet can still overlap parent by being a broader supernet. nil and malformed parents create an empty allocator whose allocation methods return nil.

If filter is non-nil, allocation calls evaluate each otherwise available candidate with the filter before reserving it. Rejected candidates are skipped without being reserved. To avoid unbounded scans when the filter rejects every candidate, each allocation call tries at most 1000 filtered candidates before returning nil.

Filters are called while the allocator lock is held. They should be fast and must not call methods on the same allocator.

type SubnetFilter added in v0.22.0

type SubnetFilter func(*net.IPNet) bool

SubnetFilter decides whether a subnet may be allocated. It returns true to allow the candidate and false to make the allocator skip it and try another subnet.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL