ipx

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 29, 2026 License: MIT Imports: 15 Imported by: 0

README

ipx

ci Go Reference · Website

Dependency-free IP networking toolkit for Go, built on net/netip.

go get github.com/bakhod1r/ipx
go install github.com/bakhod1r/ipx/cmd/ipx@latest

Features

Area API
Parsing / validation ParseAddr, ParsePrefix, ParsePrefixStrict, ParseNetwork, ParseRange, ParseIPSet, ParseAddrs, Normalize, NormalizePrefix
Classification (IANA registries) IsPrivate, IsPublic, IsLoopback, IsLinkLocal, IsShared, IsDocumentation, IsBenchmarking, IsReserved, IsSpecial, Special, IsIPv4Mapped, IsIPv4Compatible
Arithmetic (128-bit) Next, Prev, Add, AddBig, Distance, Offset, Nth, ToBig, FromBig, IPv4ToUint32, ToHex, ExpandIPv6
CIDR First, Last, Broadcast, FirstUsable, LastUsable, Size, UsableHosts, Netmask, Hostmask, PrefixFromMask, Parent, Children, Sibling, Supernet, Adjacent, CommonPrefix, CommonAncestor, IntersectPrefix
Subnetting Split (lazy iterator), SplitN, SubnetCount, SubnetIndex, Hosts, PrefixLenForHosts, PlanSubnets (VLSM)
Ranges Range: Contains, Overlaps, Intersect, Merge, Subtract, Prefixes, All, Walk(ctx), SplitRange, MergeRanges
Sets immutable IPSet: Union, Intersect, Difference, SymmetricDifference, Complement, Prefixes; Aggregate, SubtractPrefixes
Longest-prefix match Table[V] trie, SyncTable[V]
ACL ACL with LongestPrefix / FirstMatch, priorities, groups; Allowlist, Denylist
Allocation Allocator: sequential, reverse, random, Reserve, Claim, Release, exclusions
DNS ReverseName, ParseReverseName, ReverseZones
Endpoints ParseEndpoint, ParsePort, SplitHostPort, ParseHost, FormatEndpoint
Security (SSRF) IsInternal, IsCloudMetadata, EmbeddedIPv4 (mapped / compatible / NAT64 / 6to4), SafeDialer (rebinding-safe)
Inspection InspectAddr, InspectPrefix
HTTP (ipx/httpip) trusted-proxy client IP, context middleware, ACL Restrict

Examples

ipx.Aggregate([]netip.Prefix{P("10.0.0.0/25"), P("10.0.0.128/25")}) // [10.0.0.0/24]
ipx.SubtractPrefixes([]netip.Prefix{P("10.0.0.0/24")}, []netip.Prefix{P("10.0.0.0/25")}) // [10.0.0.128/25]

var rt ipx.Table[string]
rt.Insert(P("10.0.0.0/8"), "a"); rt.Insert(P("10.10.10.0/24"), "c")
p, v, _ := rt.Lookup(A("10.10.10.50")) // 10.10.10.0/24 "c"

ipx.IsInternal(A("::ffff:169.254.169.254")) // true — mapped form cannot bypass
ipx split 192.168.1.0/24 26
ipx diff 10.0.0.0/24 10.0.0.0/25
ipx info 2001:db8::1
ipx plan 10.0.0.0/24 hosts:100 hosts:50 27   # VLSM
ipx alloc 10.0.0.0/24 5 --reserve 10.0.0.1
ipx --json cidr 10.0.0.0/22                  # machine-readable output

Guarantees

  • No panics on user input (Must* helpers excepted); errors match with errors.Is.
  • IPv4-mapped IPv6 is unmapped before classification, set membership and ACL checks.
  • IPSet, Range immutable; ACL, Allocator, SyncTable goroutine-safe; Table single-writer.
  • IPv4 /31 and /32 follow RFC 3021 (all addresses usable).

Not included (by design)

Framework middleware (Gin/Echo/Fiber), PostgreSQL/MySQL scanners and Kubernetes helpers pull in dependencies; they belong in separate modules.

License

MIT — see LICENSE.

Documentation

Overview

Package ipx is a dependency-free IP networking toolkit built on net/netip.

It covers parsing and validation, classification against the IANA special-purpose registries, 128-bit address arithmetic, CIDR math (split, aggregate, subtract, supernet), address ranges, immutable IP sets with set algebra, a longest-prefix-match table, ACLs, an address allocator, reverse DNS names and SSRF-oriented security checks.

Design rules:

  • Every operation on user input returns an error or a bool; nothing panics.
  • IPv4-mapped IPv6 addresses (::ffff:a.b.c.d) are unmapped before classification, set membership and ACL matching, so an attacker cannot bypass an IPv4 rule by writing the address in IPv6 form.
  • Values (Range, IPSet) are immutable and safe for concurrent reads. Mutable structures (Table, ACL, Allocator) document their own locking.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidAddr     = errors.New("ipx: invalid IP address")
	ErrInvalidPrefix   = errors.New("ipx: invalid CIDR prefix")
	ErrInvalidRange    = errors.New("ipx: invalid IP range")
	ErrInvalidPort     = errors.New("ipx: invalid port")
	ErrInvalidEndpoint = errors.New("ipx: invalid endpoint")
	ErrInvalidReverse  = errors.New("ipx: invalid reverse DNS name")
	ErrHostBitsSet     = errors.New("ipx: prefix has host bits set")
	ErrFamilyMismatch  = errors.New("ipx: IPv4/IPv6 family mismatch")
	ErrOverflow        = errors.New("ipx: address arithmetic overflow")
	ErrExhausted       = errors.New("ipx: address pool exhausted")
	ErrNotInPool       = errors.New("ipx: address outside pool")
	ErrInUse           = errors.New("ipx: address already allocated or reserved")
	ErrBlockedAddr     = errors.New("ipx: destination address blocked")
)

Sentinel errors. Match with errors.Is.

View Source
var CloudMetadata = []netip.Prefix{
	MustParsePrefix("169.254.169.254/32"),
	MustParsePrefix("169.254.170.2/32"),
	MustParsePrefix("100.100.100.200/32"),
	MustParsePrefix("fd00:ec2::254/128"),
}

CloudMetadata lists well-known cloud instance-metadata endpoints.

View Source
var SpecialBlocks = []SpecialBlock{
	{MustParsePrefix("0.0.0.0/8"), "This network", "RFC 791", CatThisNetwork, false},
	{MustParsePrefix("10.0.0.0/8"), "Private-Use", "RFC 1918", CatPrivate, false},
	{MustParsePrefix("100.64.0.0/10"), "Shared Address Space", "RFC 6598", CatShared, false},
	{MustParsePrefix("127.0.0.0/8"), "Loopback", "RFC 1122", CatLoopback, false},
	{MustParsePrefix("169.254.0.0/16"), "Link Local", "RFC 3927", CatLinkLocal, false},
	{MustParsePrefix("172.16.0.0/12"), "Private-Use", "RFC 1918", CatPrivate, false},
	{MustParsePrefix("192.0.0.0/24"), "IETF Protocol Assignments", "RFC 6890", CatProtocol, false},
	{MustParsePrefix("192.0.2.0/24"), "Documentation (TEST-NET-1)", "RFC 5737", CatDocumentation, false},
	{MustParsePrefix("192.88.99.0/24"), "6to4 Relay Anycast (deprecated)", "RFC 7526", CatTranslation, false},
	{MustParsePrefix("192.168.0.0/16"), "Private-Use", "RFC 1918", CatPrivate, false},
	{MustParsePrefix("198.18.0.0/15"), "Benchmarking", "RFC 2544", CatBenchmarking, false},
	{MustParsePrefix("198.51.100.0/24"), "Documentation (TEST-NET-2)", "RFC 5737", CatDocumentation, false},
	{MustParsePrefix("203.0.113.0/24"), "Documentation (TEST-NET-3)", "RFC 5737", CatDocumentation, false},
	{MustParsePrefix("224.0.0.0/4"), "Multicast", "RFC 5771", CatMulticast, false},
	{MustParsePrefix("240.0.0.0/4"), "Reserved", "RFC 1112", CatReserved, false},
	{MustParsePrefix("255.255.255.255/32"), "Limited Broadcast", "RFC 919", CatBroadcast, false},

	{MustParsePrefix("::/128"), "Unspecified", "RFC 4291", CatUnspecified, false},
	{MustParsePrefix("::1/128"), "Loopback", "RFC 4291", CatLoopback, false},
	{MustParsePrefix("::ffff:0:0/96"), "IPv4-mapped", "RFC 4291", CatIPv4Mapped, false},
	{MustParsePrefix("64:ff9b::/96"), "IPv4-IPv6 Translation", "RFC 6052", CatTranslation, true},
	{MustParsePrefix("64:ff9b:1::/48"), "Local-use IPv4/IPv6 Translation", "RFC 8215", CatTranslation, false},
	{MustParsePrefix("100::/64"), "Discard-Only", "RFC 6666", CatDiscard, false},
	{MustParsePrefix("2001::/23"), "IETF Protocol Assignments", "RFC 2928", CatProtocol, false},
	{MustParsePrefix("2001:2::/48"), "Benchmarking", "RFC 5180", CatBenchmarking, false},
	{MustParsePrefix("2001:db8::/32"), "Documentation", "RFC 3849", CatDocumentation, false},
	{MustParsePrefix("2002::/16"), "6to4", "RFC 3056", CatTranslation, false},
	{MustParsePrefix("3fff::/20"), "Documentation", "RFC 9637", CatDocumentation, false},
	{MustParsePrefix("fc00::/7"), "Unique-Local", "RFC 4193", CatUniqueLocal, false},
	{MustParsePrefix("fe80::/10"), "Link-Local Unicast", "RFC 4291", CatLinkLocal, false},
	{MustParsePrefix("ff00::/8"), "Multicast", "RFC 4291", CatMulticast, false},
}

SpecialBlocks lists the IANA IPv4/IPv6 special-purpose address blocks. Treat it as read-only.

Functions

func Add

func Add(a netip.Addr, n int64) (netip.Addr, error)

Add returns a+n within a's family, or ErrOverflow if the result leaves it.

func AddBig

func AddBig(a netip.Addr, n *big.Int) (netip.Addr, error)

AddBig returns a+n for arbitrarily large (possibly negative) n.

func Adjacent

func Adjacent(a, b netip.Prefix) bool

Adjacent reports whether a and b are disjoint and touch end-to-start.

func Aggregate

func Aggregate(ps []netip.Prefix) []netip.Prefix

Aggregate returns the minimal CIDR list covering all input prefixes: merges adjacent blocks, removes nested/duplicate ones and sorts.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	fmt.Println(ipx.Aggregate([]netip.Prefix{
		netip.MustParsePrefix("10.0.0.0/25"),
		netip.MustParsePrefix("10.0.0.128/25"),
		netip.MustParsePrefix("10.0.1.0/24"),
	}))
}
Output:
[10.0.0.0/23]

func AggregateAddrs

func AggregateAddrs(as []netip.Addr) []netip.Prefix

AggregateAddrs returns the minimal CIDR list covering the addresses.

func Broadcast

func Broadcast(p netip.Prefix) (netip.Addr, bool)

Broadcast is Last for IPv4 prefixes; IPv6 has no broadcast so ok is false.

func Children

func Children(p netip.Prefix) (lo, hi netip.Prefix, ok bool)

Children returns the two halves of p; false for a host prefix.

func CommonAncestor

func CommonAncestor(ps ...netip.Prefix) (netip.Prefix, error)

CommonAncestor returns the longest prefix containing every input.

func CommonPrefix

func CommonPrefix(a, b netip.Addr) (netip.Prefix, error)

CommonPrefix returns the longest prefix containing both addresses.

func CompareAddr

func CompareAddr(a, b netip.Addr) int

CompareAddr orders addresses: IPv4 before IPv6, then numerically.

func CompareEndpoint

func CompareEndpoint(a, b netip.AddrPort) int

CompareEndpoint orders by address, then port.

func ComparePrefix

func ComparePrefix(a, b netip.Prefix) int

ComparePrefix orders prefixes: IPv4 first, then by network address, then shorter prefix first. Suitable for slices.SortFunc.

func CompareRange

func CompareRange(a, b Range) int

CompareRange orders by start, then end.

func ContainsPrefix

func ContainsPrefix(outer, inner netip.Prefix) bool

ContainsPrefix reports whether outer fully contains inner.

func DedupAddrs

func DedupAddrs(as []netip.Addr) []netip.Addr

DedupAddrs normalizes (unmaps, drops zone), sorts and removes duplicates, returning a new slice.

func Distance

func Distance(a, b netip.Addr) (*big.Int, error)

Distance returns b-a (negative when b < a). Both must share a family.

func EmbeddedIPv4

func EmbeddedIPv4(a netip.Addr) (netip.Addr, bool)

EmbeddedIPv4 extracts an IPv4 address carried inside an IPv6 address via IPv4-mapped, IPv4-compatible, NAT64 (64:ff9b::/96) or 6to4 (2002::/16).

func ExpandIPv6

func ExpandIPv6(a netip.Addr) string

ExpandIPv6 returns the fully expanded form (2001:0db8:0000:…:0001). IPv4 addresses are returned in dotted form unchanged.

func Family

func Family(a netip.Addr) int

Family returns 4 or 6 (after unmapping), or 0 for an invalid address.

func First

func First(p netip.Prefix) netip.Addr

First returns the network address of p.

func FirstUsable

func FirstUsable(p netip.Prefix) netip.Addr

FirstUsable returns the first assignable host address.

func FormatEndpoint

func FormatEndpoint(a netip.Addr, port uint16) string

FormatEndpoint formats a host and port, bracketing IPv6.

func FromBig

func FromBig(n *big.Int, ipv6 bool) (netip.Addr, error)

FromBig converts an integer to an address; ipv6 selects the family.

func FromHex

func FromHex(s string) (netip.Addr, error)

FromHex parses 8 (IPv4) or 32 (IPv6) hex digits, with optional 0x prefix.

func FromIPNet

func FromIPNet(n *net.IPNet) (netip.Prefix, bool)

FromIPNet converts a *net.IPNet to a prefix.

func FromStdIP

func FromStdIP(ip net.IP) (netip.Addr, bool)

FromStdIP converts a net.IP, unmapping 16-byte IPv4 forms.

func Hostmask

func Hostmask(p netip.Prefix) netip.Addr

Hostmask returns the wildcard mask (0.0.0.255 for /24).

func Hosts

func Hosts(p netip.Prefix) iter.Seq[netip.Addr]

Hosts lazily yields every usable host of p (see FirstUsable).

func IPv4ToMapped

func IPv4ToMapped(a netip.Addr) (netip.Addr, error)

IPv4ToMapped returns ::ffff:a.b.c.d for an IPv4 address.

func IPv4ToUint32

func IPv4ToUint32(a netip.Addr) (uint32, error)

IPv4ToUint32 converts an IPv4 (or IPv4-mapped) address to uint32.

func IntersectPrefix

func IntersectPrefix(a, b netip.Prefix) (netip.Prefix, bool)

IntersectPrefix returns a ∩ b as a prefix (the longer one) if they overlap.

func IsBenchmarking

func IsBenchmarking(a netip.Addr) bool

func IsCloudMetadata

func IsCloudMetadata(a netip.Addr) bool

IsCloudMetadata reports whether a is a known cloud metadata endpoint, including when embedded in IPv4-mapped/compatible/NAT64/6to4 form.

func IsDocumentation

func IsDocumentation(a netip.Addr) bool

func IsGlobalUnicast

func IsGlobalUnicast(a netip.Addr) bool

func IsIPv4

func IsIPv4(a netip.Addr) bool

IsIPv4 reports whether a is IPv4 or IPv4-mapped IPv6.

func IsIPv4Compatible

func IsIPv4Compatible(a netip.Addr) bool

IsIPv4Compatible reports deprecated IPv4-compatible IPv6 (::a.b.c.d), excluding :: and ::1.

func IsIPv4Mapped

func IsIPv4Mapped(a netip.Addr) bool

IsIPv4Mapped reports ::ffff:a.b.c.d.

func IsIPv6

func IsIPv6(a netip.Addr) bool

IsIPv6 reports whether a is a native (non-mapped) IPv6 address.

func IsInterfaceLocal

func IsInterfaceLocal(a netip.Addr) bool

func IsInternal

func IsInternal(a netip.Addr) bool

IsInternal reports whether a must not be reached from a server fetching user-supplied URLs (SSRF guard). True for invalid, loopback, private, link-local, shared, reserved, multicast, unspecified, documentation, benchmarking and cloud metadata addresses — also when hidden inside IPv4-mapped, IPv4-compatible, NAT64 or 6to4 IPv6 forms.

Resolve hostnames first and check every resolved address, then connect to the checked address (not the hostname) to avoid DNS rebinding.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	for _, s := range []string{"8.8.8.8", "::ffff:169.254.169.254", "64:ff9b::7f00:1"} {
		fmt.Println(s, ipx.IsInternal(netip.MustParseAddr(s)))
	}
}
Output:
8.8.8.8 false
::ffff:169.254.169.254 true
64:ff9b::7f00:1 true

func IsLinkLocal

func IsLinkLocal(a netip.Addr) bool

IsLinkLocal covers unicast and multicast link-local scopes.

func IsLoopback

func IsLoopback(a netip.Addr) bool

func IsMulticast

func IsMulticast(a netip.Addr) bool

func IsPrivate

func IsPrivate(a netip.Addr) bool

IsPrivate reports RFC 1918 IPv4 and RFC 4193 IPv6 unique-local addresses.

func IsPublic

func IsPublic(a netip.Addr) bool

IsPublic reports whether a is a globally reachable unicast address: global unicast and not inside any special block marked non-global.

func IsReserved

func IsReserved(a netip.Addr) bool

func IsSafeTarget

func IsSafeTarget(a netip.Addr) bool

IsSafeTarget is !IsInternal.

func IsShared

func IsShared(a netip.Addr) bool

func IsSpecial

func IsSpecial(a netip.Addr) bool

IsSpecial reports whether a falls in any IANA special-purpose block.

func IsUnicast

func IsUnicast(a netip.Addr) bool

IsUnicast reports a valid address that is not multicast, broadcast or unspecified.

func IsUnspecified

func IsUnspecified(a netip.Addr) bool

func IsValidAddr

func IsValidAddr(s string) bool

IsValidAddr reports whether s is a valid IP address.

func IsValidPrefix

func IsValidPrefix(s string) bool

IsValidPrefix reports whether s is a valid CIDR prefix.

func Last

func Last(p netip.Prefix) netip.Addr

Last returns the last address of p (the IPv4 broadcast address).

func LastUsable

func LastUsable(p netip.Prefix) netip.Addr

LastUsable returns the last assignable host address.

func MaxAddr

func MaxAddr(as []netip.Addr) (netip.Addr, bool)

MaxAddr returns the largest valid address, or false if none.

func MinAddr

func MinAddr(as []netip.Addr) (netip.Addr, bool)

MinAddr returns the smallest valid address, or false if none.

func MustParseAddr

func MustParseAddr(s string) netip.Addr

MustParseAddr is ParseAddr for trusted constants; it panics on error.

func MustParsePrefix

func MustParsePrefix(s string) netip.Prefix

MustParsePrefix is ParsePrefix for trusted constants; it panics on error.

func Netmask

func Netmask(p netip.Prefix) netip.Addr

Netmask returns the subnet mask as an address (255.255.255.0 for /24).

func Next

func Next(a netip.Addr) (netip.Addr, error)

Next returns a+1, or ErrOverflow at 255.255.255.255 / ffff:…:ffff.

func Normalize

func Normalize(a netip.Addr) netip.Addr

Normalize unmaps IPv4-mapped IPv6 addresses (::ffff:1.2.3.4 → 1.2.3.4) and drops the zone. Use it before any security comparison.

func NormalizeEndpoint

func NormalizeEndpoint(ap netip.AddrPort) netip.AddrPort

NormalizeEndpoint unmaps the address.

func NormalizePrefix

func NormalizePrefix(p netip.Prefix) netip.Prefix

NormalizePrefix masks host bits and unmaps IPv4-mapped IPv6 prefixes (::ffff:10.0.0.0/104 → 10.0.0.0/8). An invalid prefix is returned unchanged.

func Nth

func Nth(p netip.Prefix, n *big.Int) (netip.Addr, error)

Nth returns the address at offset n inside p.

func Offset

func Offset(p netip.Prefix, a netip.Addr) (*big.Int, error)

Offset returns the position of a inside p (0 for the network address).

func Parent

func Parent(p netip.Prefix) (netip.Prefix, bool)

Parent returns the enclosing prefix one bit shorter; false for /0.

func ParseAddr

func ParseAddr(s string) (netip.Addr, error)

ParseAddr parses an IPv4 or IPv6 address. It is strict: leading zeros in IPv4 octets, surrounding whitespace and brackets are rejected.

func ParseAddrs

func ParseAddrs(ss []string) ([]netip.Addr, error)

ParseAddrs parses every string and returns all failures joined.

func ParseEndpoint

func ParseEndpoint(s string) (netip.AddrPort, error)

ParseEndpoint parses "ip:port" or "[ipv6]:port" (zone allowed). The address is normalized (IPv4-mapped unmapped).

func ParseHost

func ParseHost(s string) (netip.Addr, error)

ParseHost extracts and normalizes the IP from any of the SplitHostPort forms. Hostnames are rejected.

func ParseNetwork

func ParseNetwork(s string) (netip.Prefix, error)

ParseNetwork accepts either a CIDR or a bare address (treated as /32 or /128) and returns the masked prefix.

func ParsePort

func ParsePort(s string) (uint16, error)

ParsePort parses a decimal port 1-65535. Leading zeros and signs are rejected.

func ParsePrefix

func ParsePrefix(s string) (netip.Prefix, error)

ParsePrefix parses a CIDR prefix. Host bits are allowed and preserved (192.168.1.10/24 is valid); use ParsePrefixStrict to reject them.

func ParsePrefixStrict

func ParsePrefixStrict(s string) (netip.Prefix, error)

ParsePrefixStrict parses a CIDR prefix and rejects host bits (192.168.1.10/24 fails, 192.168.1.0/24 succeeds).

func ParsePrefixes

func ParsePrefixes(ss []string) ([]netip.Prefix, error)

ParsePrefixes parses every string (CIDR or bare address) and returns all failures joined.

func ParseReverseName

func ParseReverseName(s string) (netip.Addr, error)

ParseReverseName parses a full PTR name (trailing dot optional, case-insensitive) back to an address.

func PlanSubnets

func PlanSubnets(parent netip.Prefix, lengths []int) ([]netip.Prefix, error)

PlanSubnets carves variable-size subnets (VLSM) out of parent. lengths are the requested prefix lengths; the result is in the same order. Largest blocks are placed first so alignment waste is minimal.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	plan, _ := ipx.PlanSubnets(netip.MustParsePrefix("10.0.0.0/24"), []int{26, 25, 27})
	fmt.Println(plan)
}
Output:
[10.0.0.128/26 10.0.0.0/25 10.0.0.192/27]

func PrefixFromMask

func PrefixFromMask(a, mask netip.Addr) (netip.Prefix, error)

PrefixFromMask builds a prefix from an address and a dotted netmask such as 255.255.255.0. Non-contiguous masks are rejected.

func PrefixLenForHosts

func PrefixLenForHosts(hosts uint64, ipv6 bool) (int, error)

PrefixLenForHosts returns the longest prefix length whose usable host count is at least hosts. ipv6 selects the family.

func Prev

func Prev(a netip.Addr) (netip.Addr, error)

Prev returns a-1, or ErrOverflow at 0.0.0.0 / ::.

func ReverseName

func ReverseName(a netip.Addr) (string, error)

ReverseName returns the PTR owner name, with trailing dot: 8.8.8.8 → "8.8.8.8.in-addr.arpa.", 2001:db8::1 → "1.0.0.…ip6.arpa.".

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	n, _ := ipx.ReverseName(netip.MustParseAddr("8.8.4.4"))
	fmt.Println(n)
}
Output:
4.4.8.8.in-addr.arpa.

func ReverseZones

func ReverseZones(p netip.Prefix) ([]string, error)

ReverseZones returns the reverse zone names delegating p. The prefix is rounded to the next octet (IPv4) or nibble (IPv6) boundary, so 10.0.0.0/23 yields two /24 zones. RFC 2317 classless delegation is not generated.

func Sibling

func Sibling(p netip.Prefix) (netip.Prefix, bool)

Sibling returns the other half of p's parent; false for /0.

func Size

func Size(p netip.Prefix) *big.Int

Size returns the number of addresses in p (2^(width-bits)).

func SortAddrs

func SortAddrs(as []netip.Addr)

SortAddrs sorts in place.

func SortPrefixes

func SortPrefixes(ps []netip.Prefix)

SortPrefixes sorts in place with ComparePrefix.

func Split

func Split(p netip.Prefix, newBits int) (iter.Seq[netip.Prefix], error)

Split lazily yields every /newBits subnet of p in order. Splitting 2001:db8::/32 into /64 yields 2^32 values, so stop early when needed.

func SplitHostPort

func SplitHostPort(s string) (host string, port uint16, err error)

SplitHostPort splits "host:port", "[v6]:port", "v4", "v6" or "[v6]". Port is 0 when absent. Host is returned without brackets.

func SplitN

func SplitN(p netip.Prefix, newBits, limit int) ([]netip.Prefix, error)

SplitN splits p into /newBits subnets and returns at most limit of them (limit ≤ 0 means no limit — use carefully on IPv6).

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	subs, _ := ipx.SplitN(netip.MustParsePrefix("192.168.1.0/24"), 26, 0)
	fmt.Println(subs)
}
Output:
[192.168.1.0/26 192.168.1.64/26 192.168.1.128/26 192.168.1.192/26]

func SubnetCount

func SubnetCount(p netip.Prefix, newBits int) (*big.Int, error)

SubnetCount returns how many /newBits subnets fit in p.

func SubnetIndex

func SubnetIndex(p netip.Prefix, newBits int, a netip.Addr) (*big.Int, error)

SubnetIndex returns which /newBits subnet of p contains a (0-based).

func SubtractPrefixes

func SubtractPrefixes(from, remove []netip.Prefix) []netip.Prefix

SubtractPrefixes returns the minimal CIDR list for from \ remove.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	fmt.Println(ipx.SubtractPrefixes(
		[]netip.Prefix{netip.MustParsePrefix("10.0.0.0/24")},
		[]netip.Prefix{netip.MustParsePrefix("10.0.0.0/25")},
	))
}
Output:
[10.0.0.128/25]

func Supernet

func Supernet(p netip.Prefix, bits int) (netip.Prefix, error)

Supernet returns the enclosing prefix of the given length.

func ToBig

func ToBig(a netip.Addr) *big.Int

ToBig returns the integer value of a.

func ToHex

func ToHex(a netip.Addr) string

ToHex returns the address bytes in lowercase hex (c0a80001 for 192.168.0.1).

func ToIPNet

func ToIPNet(p netip.Prefix) *net.IPNet

ToIPNet converts a prefix to *net.IPNet (masked).

func ToStdIP

func ToStdIP(a netip.Addr) net.IP

ToStdIP converts to net.IP (4 bytes for IPv4). Zones are dropped.

func Uint32ToIPv4

func Uint32ToIPv4(n uint32) netip.Addr

Uint32ToIPv4 converts n to an IPv4 address.

func UsableHosts

func UsableHosts(p netip.Prefix) *big.Int

UsableHosts returns the number of assignable hosts (Size minus network and broadcast for IPv4 prefixes shorter than /31).

Types

type ACL

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

ACL is an allow/deny list. Safe for concurrent use.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	acl := ipx.NewACL(ipx.LongestPrefix, ipx.Deny)
	acl.Add(ipx.Rule{Prefix: netip.MustParsePrefix("10.0.0.0/8"), Action: ipx.Allow})
	acl.Add(ipx.Rule{Prefix: netip.MustParsePrefix("10.66.0.0/16"), Action: ipx.Deny})
	fmt.Println(acl.Decide(netip.MustParseAddr("10.1.1.1")), acl.Decide(netip.MustParseAddr("10.66.1.1")))
}
Output:
allow deny

func Allowlist

func Allowlist(ps ...netip.Prefix) *ACL

Allowlist denies everything except the given networks. Invalid prefixes are skipped.

func Denylist

func Denylist(ps ...netip.Prefix) *ACL

Denylist allows everything except the given networks. Invalid prefixes are skipped.

func NewACL

func NewACL(s Strategy, def Action) *ACL

NewACL returns an ACL returning def when no rule matches.

func (*ACL) Add

func (a *ACL) Add(r Rule) error

Add appends a rule; invalid prefixes are rejected.

func (*ACL) Allowed

func (a *ACL) Allowed(addr netip.Addr) bool

Allowed is Decide(addr) == Allow.

func (*ACL) Decide

func (a *ACL) Decide(addr netip.Addr) Action

Decide returns the verdict for addr. Invalid addresses are always denied.

func (*ACL) Match

func (a *ACL) Match(addr netip.Addr) (Rule, bool)

Match returns the deciding rule for addr; ok is false if the default applied.

func (*ACL) RemoveGroup

func (a *ACL) RemoveGroup(group string) int

RemoveGroup deletes all rules in group and returns how many.

func (*ACL) Rules

func (a *ACL) Rules() []Rule

Rules returns a copy of the rules in insertion order.

type Action

type Action uint8

Action is an ACL verdict.

const (
	Deny Action = iota
	Allow
)

func (Action) String

func (a Action) String() string

type AddrInfo

type AddrInfo struct {
	Addr       netip.Addr
	Version    int
	Public     bool
	Private    bool
	Loopback   bool
	Multicast  bool
	LinkLocal  bool
	Internal   bool
	Special    string // IANA block name, empty if none
	Reverse    string
	Hex        string
	Expanded   string
	EmbeddedV4 netip.Addr
}

AddrInfo is an immutable report about an address.

func InspectAddr

func InspectAddr(a netip.Addr) (AddrInfo, error)

InspectAddr builds an AddrInfo.

func (AddrInfo) String

func (i AddrInfo) String() string

type Allocator

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

Allocator hands out addresses from a pool. Safe for concurrent use.

The pool is a prefix (usable hosts only) or a Range, minus exclusions. State is in memory; persist Allocated() yourself if needed.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	al, _ := ipx.NewAllocator(netip.MustParsePrefix("10.0.0.0/29"))
	al.Reserve(netip.MustParseAddr("10.0.0.1")) // gateway
	a, _ := al.Allocate()
	fmt.Println(a, al.Available())
}
Output:
10.0.0.2 4

func NewAllocator

func NewAllocator(p netip.Prefix, exclude ...netip.Prefix) (*Allocator, error)

NewAllocator creates a pool of p's usable hosts (IPv4 network and broadcast excluded for prefixes shorter than /31).

func NewRangeAllocator

func NewRangeAllocator(r Range, exclude ...netip.Prefix) (*Allocator, error)

NewRangeAllocator creates a pool over r.

func (*Allocator) Allocate

func (al *Allocator) Allocate() (netip.Addr, error)

Allocate returns the next free address after the last one handed out, wrapping around. Returns ErrExhausted when the pool is full.

func (*Allocator) AllocateN

func (al *Allocator) AllocateN(n int) ([]netip.Addr, error)

AllocateN allocates n addresses sequentially; on failure nothing is kept.

func (*Allocator) AllocatePrev

func (al *Allocator) AllocatePrev() (netip.Addr, error)

AllocatePrev returns the highest free address, scanning downward.

func (*Allocator) AllocateRandom

func (al *Allocator) AllocateRandom() (netip.Addr, error)

AllocateRandom picks a uniformly random start point and returns the first free address from there. Useful to avoid predictable addresses in IPv6.

func (*Allocator) Allocated

func (al *Allocator) Allocated() []netip.Addr

Allocated returns the currently allocated addresses, sorted.

func (*Allocator) Available

func (al *Allocator) Available() *big.Int

Available returns how many addresses can still be allocated.

func (*Allocator) Claim

func (al *Allocator) Claim(a netip.Addr) error

Claim allocates a specific address.

func (*Allocator) IsFree

func (al *Allocator) IsFree(a netip.Addr) bool

IsFree reports whether a is in the pool and unallocated.

func (*Allocator) Release

func (al *Allocator) Release(a netip.Addr) bool

Release returns an allocated or reserved address to the pool.

func (*Allocator) Reserve

func (al *Allocator) Reserve(a netip.Addr) error

Reserve marks a so it is never handed out (gateway, DNS, …).

type Builder

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

Builder accumulates items for an IPSet. Not safe for concurrent use.

func (*Builder) AddAddr

func (b *Builder) AddAddr(a netip.Addr)

func (*Builder) AddPrefix

func (b *Builder) AddPrefix(p netip.Prefix)

func (*Builder) AddRange

func (b *Builder) AddRange(r Range)

func (*Builder) AddSet

func (b *Builder) AddSet(s *IPSet)

func (*Builder) IPSet

func (b *Builder) IPSet() *IPSet

IPSet returns the built set. The builder can keep being used.

func (*Builder) RemoveAddr

func (b *Builder) RemoveAddr(a netip.Addr)

Remove* calls apply after all adds when IPSet is called.

func (*Builder) RemovePrefix

func (b *Builder) RemovePrefix(p netip.Prefix)

func (*Builder) RemoveRange

func (b *Builder) RemoveRange(r Range)

func (*Builder) RemoveSet

func (b *Builder) RemoveSet(s *IPSet)

type Category

type Category uint16

Category labels a special-purpose block.

const (
	CatThisNetwork Category = 1 << iota
	CatPrivate              // RFC 1918
	CatShared               // RFC 6598 carrier-grade NAT
	CatLoopback
	CatLinkLocal
	CatDocumentation
	CatBenchmarking
	CatMulticast
	CatReserved
	CatBroadcast
	CatUniqueLocal // RFC 4193 fc00::/7
	CatIPv4Mapped
	CatTranslation // NAT64, 6to4
	CatProtocol    // IETF protocol assignments
	CatDiscard
	CatUnspecified
)

func Categories

func Categories(a netip.Addr) Category

Categories returns the category bits of every special block containing a.

type IPSet

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

IPSet is an immutable set of addresses stored as sorted, merged ranges. The zero value is the empty set. All operations return new sets, so an IPSet is safe for concurrent use.

Inputs are normalized: IPv4-mapped IPv6 is treated as IPv4, zones dropped.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	a := ipx.NewIPSet(netip.MustParsePrefix("10.0.0.0/24"))
	b := ipx.NewIPSet(netip.MustParsePrefix("10.0.0.128/25"))
	fmt.Println(a.Difference(b))
	fmt.Println(a.Contains(netip.MustParseAddr("::ffff:10.0.0.1")))
}
Output:
10.0.0.0/25
true

func NewIPSet

func NewIPSet(items ...any) *IPSet

NewIPSet builds a set from any mix of addresses, prefixes and ranges.

func ParseIPSet

func ParseIPSet(s string) (*IPSet, error)

ParseIPSet parses a comma/space separated list of addresses, CIDRs and "a-b" ranges.

func (*IPSet) All

func (s *IPSet) All() iter.Seq[netip.Addr]

All lazily yields every address in the set.

func (*IPSet) Complement

func (s *IPSet) Complement() *IPSet

Complement returns every address (IPv4 and IPv6) not in s.

func (*IPSet) Contains

func (s *IPSet) Contains(a netip.Addr) bool

Contains reports whether a is in the set. O(log n).

func (*IPSet) ContainsPrefix

func (s *IPSet) ContainsPrefix(p netip.Prefix) bool

ContainsPrefix reports whether all of p is in the set.

func (*IPSet) ContainsRange

func (s *IPSet) ContainsRange(r Range) bool

ContainsRange reports whether all of r is in the set.

func (*IPSet) Difference

func (s *IPSet) Difference(o *IPSet) *IPSet

Difference returns s \ o.

func (*IPSet) Equal

func (s *IPSet) Equal(o *IPSet) bool

Equal reports whether both sets hold the same addresses.

func (*IPSet) Intersect

func (s *IPSet) Intersect(o *IPSet) *IPSet

Intersect returns s ∩ o.

func (*IPSet) IsEmpty

func (s *IPSet) IsEmpty() bool

IsEmpty reports whether the set has no addresses.

func (*IPSet) MarshalText

func (s *IPSet) MarshalText() ([]byte, error)

MarshalText encodes the set as its comma-separated prefix list.

func (*IPSet) Overlaps

func (s *IPSet) Overlaps(o *IPSet) bool

Overlaps reports whether the sets share any address.

func (*IPSet) Prefixes

func (s *IPSet) Prefixes() []netip.Prefix

Prefixes returns the minimal sorted CIDR list covering the set.

func (*IPSet) Ranges

func (s *IPSet) Ranges() []Range

Ranges returns a copy of the canonical ranges.

func (*IPSet) Size

func (s *IPSet) Size() *big.Int

Size returns the number of addresses.

func (*IPSet) String

func (s *IPSet) String() string

String lists the set's prefixes, comma-separated.

func (*IPSet) SymmetricDifference

func (s *IPSet) SymmetricDifference(o *IPSet) *IPSet

SymmetricDifference returns (s \ o) ∪ (o \ s).

func (*IPSet) Union

func (s *IPSet) Union(o *IPSet) *IPSet

Union returns s ∪ o.

func (*IPSet) UnmarshalText

func (s *IPSet) UnmarshalText(b []byte) error

UnmarshalText accepts anything ParseIPSet accepts.

type PrefixInfo

type PrefixInfo struct {
	Prefix      netip.Prefix
	Version     int
	Bits        int
	Netmask     netip.Addr
	Hostmask    netip.Addr
	Network     netip.Addr
	Broadcast   netip.Addr // invalid for IPv6
	FirstUsable netip.Addr
	LastUsable  netip.Addr
	Size        *big.Int
	UsableHosts *big.Int
}

PrefixInfo is an immutable report about a network.

func InspectPrefix

func InspectPrefix(p netip.Prefix) (PrefixInfo, error)

InspectPrefix builds a PrefixInfo; host bits are masked.

func (PrefixInfo) String

func (i PrefixInfo) String() string

type Range

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

Range is an inclusive span of addresses of one family. The zero Range is invalid. Build one with NewRange, RangeOf or ParseRange.

func MergeRanges

func MergeRanges(rs []Range) []Range

MergeRanges sorts, merges overlapping/adjacent ranges and drops invalid ones, returning a new canonical slice.

func NewRange

func NewRange(from, to netip.Addr) (Range, error)

NewRange validates and returns [from, to]. Inputs are normalized (IPv4-mapped unmapped, zones dropped).

func ParseRange

func ParseRange(s string) (Range, error)

ParseRange accepts "a-b", a CIDR, or a single address.

func RangeOf

func RangeOf(p netip.Prefix) Range

RangeOf returns the address range covered by prefix p.

func (Range) Adjacent

func (r Range) Adjacent(o Range) bool

Adjacent reports whether r and o are disjoint and touch.

func (Range) All

func (r Range) All() iter.Seq[netip.Addr]

All lazily yields every address in r.

func (Range) Contains

func (r Range) Contains(a netip.Addr) bool

Contains reports whether a is inside r (IPv4-mapped input is unmapped).

func (Range) ContainsRange

func (r Range) ContainsRange(o Range) bool

ContainsRange reports whether o lies fully inside r.

func (Range) From

func (r Range) From() netip.Addr

func (Range) Intersect

func (r Range) Intersect(o Range) (Range, bool)

Intersect returns the overlap of r and o; false if disjoint.

func (Range) IsValid

func (r Range) IsValid() bool

func (Range) MarshalBinary

func (r Range) MarshalBinary() ([]byte, error)

MarshalBinary encodes as from||to raw bytes (8 or 32 bytes).

func (Range) MarshalText

func (r Range) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (Range) Merge

func (r Range) Merge(o Range) (Range, bool)

Merge joins overlapping or adjacent ranges; false if there is a gap.

func (Range) Overlaps

func (r Range) Overlaps(o Range) bool

Overlaps reports whether r and o share any address.

func (Range) Prefix

func (r Range) Prefix() (netip.Prefix, bool)

Prefix returns the single CIDR equal to r, if one exists.

func (Range) Prefixes

func (r Range) Prefixes() []netip.Prefix

Prefixes returns the minimal list of CIDRs exactly covering r.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/ipx"
)

func main() {
	r, _ := ipx.ParseRange("10.0.0.5-10.0.0.12")
	fmt.Println(r.Prefixes())
}
Output:
[10.0.0.5/32 10.0.0.6/31 10.0.0.8/30 10.0.0.12/32]

func (Range) Size

func (r Range) Size() *big.Int

Size returns the number of addresses.

func (Range) SplitRange

func (r Range) SplitRange(n uint64) []Range

SplitRange cuts r into chunks of at most n addresses.

func (Range) String

func (r Range) String() string

String formats as "from-to".

func (Range) Subtract

func (r Range) Subtract(o Range) []Range

Subtract returns r minus o: zero, one or two ranges.

func (Range) To

func (r Range) To() netip.Addr

func (*Range) UnmarshalBinary

func (r *Range) UnmarshalBinary(b []byte) error

UnmarshalBinary decodes MarshalBinary output.

func (*Range) UnmarshalText

func (r *Range) UnmarshalText(b []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

func (Range) Walk

func (r Range) Walk(ctx context.Context, fn func(netip.Addr) bool) error

Walk calls fn for each address until fn returns false or ctx is done. It checks ctx every 1024 addresses and returns ctx.Err() if cancelled.

type Rule

type Rule struct {
	Prefix   netip.Prefix
	Action   Action
	Priority int    // lower is evaluated first
	Group    string // free-form label, e.g. "office", "cloud-metadata"
}

Rule is one ACL entry.

type SafeDialer

type SafeDialer struct {
	// Dialer performs the connection; zero value uses net.Dialer defaults.
	Dialer net.Dialer
	// Lookup resolves host names; nil uses net.DefaultResolver.
	Lookup func(ctx context.Context, host string) ([]netip.Addr, error)
	// Allow, when set, overrides the default policy: an address is permitted
	// iff Allow allows it. When nil, IsInternal addresses are blocked.
	Allow *ACL
}

SafeDialer dials only addresses that pass an SSRF check. It resolves the host itself, rejects the whole connection if ANY resolved address is blocked (so a mixed public/internal answer cannot win a race), and then connects to the checked IP, never re-resolving the name. That closes the DNS-rebinding window between check and connect.

Plug it into http.Transport.DialContext for outbound fetches of user-supplied URLs. Redirects are covered because every hop dials anew.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/bakhod1r/ipx"
)

func main() {
	d := &ipx.SafeDialer{}
	client := &http.Client{Transport: &http.Transport{DialContext: d.DialContext}}
	_, err := client.Get("http://169.254.169.254/latest/meta-data/")
	fmt.Println(err != nil)
}
Output:
true

func (*SafeDialer) DialContext

func (d *SafeDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error)

DialContext has the signature of net.Dialer.DialContext.

type SpecialBlock

type SpecialBlock struct {
	Prefix   netip.Prefix
	Name     string
	RFC      string
	Category Category
	// Global mirrors the registry's "Globally Reachable" column.
	Global bool
}

SpecialBlock is one entry of the IANA special-purpose registries.

func Special

func Special(a netip.Addr) (SpecialBlock, bool)

Special returns the most specific special-purpose block containing a. IPv4-mapped input is unmapped first.

type Strategy

type Strategy uint8

Strategy selects how an ACL picks among matching rules.

const (
	// LongestPrefix: the most specific matching prefix wins; among equal
	// prefixes the lowest Priority value wins.
	LongestPrefix Strategy = iota
	// FirstMatch: rules are evaluated by ascending Priority (then insertion
	// order); the first match wins.
	FirstMatch
)

type SyncTable

type SyncTable[V any] struct {
	// contains filtered or unexported fields
}

SyncTable is a Table guarded by a RWMutex: many concurrent readers, one writer. The zero value is ready to use.

func (*SyncTable[V]) Delete

func (s *SyncTable[V]) Delete(p netip.Prefix) bool

func (*SyncTable[V]) Get

func (s *SyncTable[V]) Get(p netip.Prefix) (V, bool)

func (*SyncTable[V]) Insert

func (s *SyncTable[V]) Insert(p netip.Prefix, v V) bool

func (*SyncTable[V]) Len

func (s *SyncTable[V]) Len() int

func (*SyncTable[V]) Lookup

func (s *SyncTable[V]) Lookup(a netip.Addr) (netip.Prefix, V, bool)

func (*SyncTable[V]) Snapshot

func (s *SyncTable[V]) Snapshot() map[netip.Prefix]V

Snapshot returns a copy of all entries.

type Table

type Table[V any] struct {
	// contains filtered or unexported fields
}

Table is a binary prefix trie mapping prefixes to values, with longest-prefix-match lookup (a routing table). The zero value is ready to use. Table is not safe for concurrent mutation; use SyncTable for that.

Prefixes are normalized (masked, IPv4-mapped unmapped) on insert and addresses on lookup.

func (*Table[V]) All

func (t *Table[V]) All() iter.Seq2[netip.Prefix, V]

All yields every stored prefix and value: IPv4 first, then IPv6, each in address order with shorter prefixes before their children.

func (*Table[V]) Contains

func (t *Table[V]) Contains(a netip.Addr) bool

Contains reports whether any stored prefix contains a.

func (*Table[V]) Delete

func (t *Table[V]) Delete(p netip.Prefix) bool

Delete removes p; false if it was absent.

func (*Table[V]) Get

func (t *Table[V]) Get(p netip.Prefix) (V, bool)

Get returns the value stored at exactly p.

func (*Table[V]) Insert

func (t *Table[V]) Insert(p netip.Prefix, v V) bool

Insert stores v at p, replacing any existing value. Invalid p is ignored and reported as false.

func (*Table[V]) Len

func (t *Table[V]) Len() int

Len returns the number of stored prefixes.

func (*Table[V]) Lookup

func (t *Table[V]) Lookup(a netip.Addr) (netip.Prefix, V, bool)

Lookup returns the longest stored prefix containing a and its value.

Example
package main

import (
	"fmt"
	"net/netip"

	"github.com/bakhod1r/ipx"
)

func main() {
	var rt ipx.Table[string]
	rt.Insert(netip.MustParsePrefix("10.0.0.0/8"), "corp")
	rt.Insert(netip.MustParsePrefix("10.10.10.0/24"), "lab")
	p, v, _ := rt.Lookup(netip.MustParseAddr("10.10.10.50"))
	fmt.Println(p, v)
}
Output:
10.10.10.0/24 lab

func (*Table[V]) LookupShortest

func (t *Table[V]) LookupShortest(a netip.Addr) (netip.Prefix, V, bool)

LookupShortest returns the shortest stored prefix containing a.

func (*Table[V]) Matches

func (t *Table[V]) Matches(a netip.Addr) []V

Matches returns the values of every stored prefix containing a, shortest prefix first.

Directories

Path Synopsis
cmd
ipx command
Command ipx is a CLI for the ipx library.
Command ipx is a CLI for the ipx library.
Package httpip resolves the real client IP of an HTTP request behind explicitly trusted proxies, and provides net/http middleware.
Package httpip resolves the real client IP of an HTTP request behind explicitly trusted proxies, and provides net/http middleware.
integrations
echoipx module
fiberipx module
geoip module
ginipx module

Jump to

Keyboard shortcuts

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