ipset

package
v0.0.34-beta Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2025 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Discrete sets and ranges of IP addresses.

Set Types

Interface hierarchy: Block set is Interval set is Discrete set.

NewBlock creates a RFC-4632 CIDR Block set. NewSingle creates a Block set from a single address.

NewInterval creates contiguous Interval set of addresses from lower and upper bounds.

NewDiscrete creates Discrete set as union of other address sets. Use NewDiscrete to create the empty set.

Functions return a struct that conforms to the most specialized interface possible.

Iteration

Use Discrete.Addresses to iterate over constituent addresses.

Use Discrete.Intervals to iterate over constituent Interval sets.

Use the Blocks function to iterate over constituent Block sets within Interval sets.

References

https://www.rfc-editor.org/rfc/rfc4632

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Adjacent

func Adjacent[A ip.Int[A]](i0, i1 Interval[A]) bool

Tests if IP address ranges are one element from overlap

func Blocks

func Blocks[A ip.Int[A]](set Interval[A]) iter.Seq[Block[A]]

Subdivides Interval into CIDR Block sets

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	first := ip.V4().MustFromBytes(192, 0, 2, 101)
	last := ip.V4().MustFromBytes(192, 0, 2, 240)
	freeAddresses := ipset.NewInterval(first, last)

	printCidrBlocksIn(freeAddresses)
}

func printCidrBlocksIn[A ip.Int[A]](addressRange ipset.Interval[A]) {
	for block := range ipset.Blocks(addressRange) {
		println(block.String())
	}
}

func Contiguous

func Contiguous[A ip.Int[A]](i0, i1 Interval[A]) bool

Tests if IP address ranges either Intersect or are Adjacent

func Eq

func Eq[A ip.Int[A]](set0, set1 Discrete[A]) (equal bool)

Tests if two discrete sets are equal. Iterates each Discrete set's Interval sets comparing first and last elements.

func Intersect

func Intersect[A ip.Int[A]](i0, i1 Interval[A]) bool

Tests if IP address ranges have common elements

func ParseCIDRNotation

func ParseCIDRNotation[A ip.Int[A]](f ip.Family[A], notation string) (netAddress A, maskBits int, err error)

Parses RFC 4632 CIDR notation. See also the NewBlock function.

For the IPv4 family and expression "192.168.0.0/128", returns 192.168.0.0 as the network address part and 128 as the mask bits.

Returns error if second argument is invalid CIDR notation.

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
	"github.com/ipfreely-uk/go/txt"
)

func main() {
	address, mask, _ := ipset.ParseCIDRNotation(ip.V6(), "2001:db8::/32")
	reservedForDocumentation := ipset.NewBlock(address, mask)
	printRangeDetails(reservedForDocumentation)
}

func printRangeDetails[A ip.Int[A]](addresses ipset.Interval[A]) {
	println("Start:", addresses.First().String())
	println("End:", addresses.Last().String())
	println("Addresses:", txt.CommaDelim(addresses.Size()))
	println()
}

func ParseUnknownCIDRNotation

func ParseUnknownCIDRNotation(notation string) (netAddress ip.Address, maskBits int, err error)

Parses CIDR notation where IP address family is unknown. Returns error if argument is invalid CIDR notation.

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
	"github.com/ipfreely-uk/go/txt"
)

func main() {
	reservedForDocumentation := []string{
		"192.0.2.0/24",
		"198.51.100.0/24",
		"203.0.113.0/24",
		"2001:db8::/32",
	}
	for _, notation := range reservedForDocumentation {
		address, mask, err := ipset.ParseUnknownCIDRNotation(notation)
		if err != nil {
			panic(err)
		}
		switch a := address.(type) {
		case ip.Addr4:
			printRangeDetails(ipset.NewBlock(a, mask))
		case ip.Addr6:
			printRangeDetails(ipset.NewBlock(a, mask))
		}
	}
}

func printRangeDetails[A ip.Int[A]](addresses ipset.Interval[A]) {
	println("Start:", addresses.First().String())
	println("End:", addresses.Last().String())
	println("Addresses:", txt.CommaDelim(addresses.Size()))
	println()
}

func Subnets

func Subnets[A ip.Int[A]](b Block[A], maskBits int) iter.Seq[Block[A]]

Splits Block into subnets of a given size.

Panics on illegal mask bits. maskBits must be greater or equal to Block.MaskSize and less than or equal to the bit width of the address family.

Example
package main

import (
	"fmt"
	"math/big"

	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipmask"
	"github.com/ipfreely-uk/go/ipset"
	"github.com/ipfreely-uk/go/txt"
)

func maskRequiredFor[A ip.Int[A]](f ip.Family[A], allocateableAddresses *big.Int) (bits int) {
	var min *big.Int
	if f.Version() == ip.V4().Version() {

		two := big.NewInt(2)
		min = two.Add(two, allocateableAddresses)
	} else {
		min = allocateableAddresses
	}
	width := f.Width()
	for m := width; m >= 0; m-- {
		sizeForMask := ipmask.Size(f, m)
		if sizeForMask.Cmp(min) >= 0 {
			return m
		}
	}
	formatted := txt.CommaDelim(allocateableAddresses)
	msg := fmt.Sprintf("%s is larger than family %s", formatted, f.String())
	panic(msg)
}

func main() {
	oneHundredAddresses := big.NewInt(100)
	mask := maskRequiredFor(ip.V4(), oneHundredAddresses)

	netAddr, bits, _ := ipset.ParseCIDRNotation(ip.V4(), "203.0.113.0/24")
	block := ipset.NewBlock(netAddr, bits)

	println(fmt.Sprintf("Dividing %s into blocks of at least %s addresses", block.CidrNotation(), oneHundredAddresses.String()))
	for sub := range ipset.Subnets(block, mask) {
		println(sub.String())
	}
}

Types

type Block

type Block[A ip.Int[A]] interface {
	Interval[A]
	// Mask size in bits
	MaskSize() (bits int)
	// Mask as IP address
	Mask() (address A)
	// The block in CIDR notation.
	CidrNotation() string
}

Immutable RFC-4632 CIDR block. Roughly equivalent to the [netip.Prefix] type.

Example
package main

import (
	"crypto/rand"

	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	netAddress := ip.MustParse(ip.V6(), "2001:db8:cafe::")
	block := ipset.NewBlock(netAddress, 56)

	for i := 0; i < 3; i++ {
		randomAddr := randomAddressFrom(block)
		println("Random address from", block.String(), "=", randomAddr.String())
	}
}

// Pick random address from block
func randomAddressFrom[A ip.Int[A]](netBlock ipset.Block[A]) (address A) {
	netAddr := netBlock.First()
	family := netAddr.Family()
	inverseMask := netBlock.Mask().Not()

	return random(family).And(inverseMask).Or(netAddr)
}

// Generate a random address for given family
func random[A ip.Int[A]](f ip.Family[A]) (address A) {
	slice := make([]byte, f.Width()/8)
	_, _ = rand.Read(slice)
	return f.MustFromBytes(slice...)
}

func NewBlock

func NewBlock[A ip.Int[A]](network A, mask int) Block[A]

Creates Block set.

Panics if mask does not cover network address or is out of range for address family.

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
	"github.com/ipfreely-uk/go/txt"
)

func main() {
	network := ip.MustParse(ip.V6(), "2001:db8::")
	block := ipset.NewBlock(network, 32)

	println("Block", block.String())
	println("First", block.First().String())
	println("Last", block.Last().String())
	println("Size", txt.CommaDelim(block.Size()))
}
Example (Second)
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipmask"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	network := ip.MustParse(ip.V4(), "192.168.0.0")
	mask := ip.MustParse(ip.V4(), "255.255.255.0")
	subnet := block(network, mask)
	println(subnet.String())
}

func block[A ip.Int[A]](network, mask A) ipset.Block[A] {
	bits := ipmask.Bits(mask)
	return ipset.NewBlock(network, bits)
}

func NewSingle

func NewSingle[A ip.Int[A]](address A) Block[A]

Creates Block set from a single address

type Builder

type Builder[A ip.Int[A]] struct {
	// contains filtered or unexported fields
}

Discrete set builder.

The only advantage over using a slice and NewDiscrete is that this type will attempt to reduce the buffer over a certain threshold.

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipmask"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	addr, bits, err := ipset.ParseCIDRNotation(ip.V4(), "10.0.0.0/15")
	if err != nil {
		panic(err.Error())
	}
	network := ipset.NewBlock(addr, bits)

	lucky := removeUnlucky(network)

	println(lucky.String())
}

var thirteen = ip.V4().FromInt(13)
var maskComplement = ipmask.For(ip.V4(), 24).Not()

func removeUnlucky(addresses ipset.Discrete[ip.Addr4]) (lucky ipset.Discrete[ip.Addr4]) {
	bldr := ipset.Builder[ip.Addr4]{}
	for a := range addresses.Addresses() {
		if !unlucky(a) {
			bldr.Union(ipset.NewSingle(a))
		}
	}
	return bldr.Build()
}

func unlucky(a ip.Addr4) bool {
	lastDigits := a.And(maskComplement)
	return ip.Eq(lastDigits, thirteen)
}

func (*Builder[A]) Build

func (b *Builder[A]) Build() Discrete[A]

Creates Discrete set and empties the buffer for reuse

func (*Builder[A]) Union

func (b *Builder[A]) Union(d Discrete[A]) *Builder[A]

Creates union with existing members

type Discrete

type Discrete[A ip.Int[A]] interface {
	// Tests if address in set
	Contains(address A) bool
	// Number of unique addresses.
	Size() (cardinality *big.Int)
	// Tests for empty set
	Empty() bool
	// Unique addresses from least to greatest
	Addresses() iter.Seq[A]
	// Contents as distinct [Interval] sets.
	// Intervals do not [Intersect] and are not [Adjacent].
	// Intervals are returned from least address to greatest.
	Intervals() iter.Seq[Interval[A]]
	// Informational only
	String() string
}

Immutable discrete ordered set of IP addresses. Seq types provided by implementations are reusable.

func NewDiscrete

func NewDiscrete[A ip.Int[A]](sets ...Discrete[A]) (set Discrete[A])

Creates Discrete set as a union of addresses from the operand elements.

If set is contiguous range returns result of NewInterval function. If set is CIDR range returns result of NewBlock function. Zero-length slice returns the empty set.

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	set := func(first, last string) ipset.Interval[ip.Addr4] {
		v4 := ip.V4()
		p := ip.MustParse[ip.Addr4]
		return ipset.NewInterval(p(v4, first), p(v4, last))
	}
	r0 := set("192.0.2.0", "192.0.2.100")
	r1 := set("192.0.2.101", "192.0.2.111")
	r2 := set("192.0.2.200", "192.0.2.200")

	union := ipset.NewDiscrete(r0, r1, r2)
	println(r0.String(), "\u222A", r1.String(), "\u222A", r2.String(), "=", union.String())
}
Example (Second)
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	printEmptySetFor(ip.V4())
	printEmptySetFor(ip.V6())
}

func printEmptySetFor[A ip.Int[A]](f ip.Family[A]) {
	empty := ipset.NewDiscrete[A]()
	println(f.String(), empty.String())
}
Example (Third)
package main

import (
	"fmt"

	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	s0 := parseV4("192.0.2.0/32")
	s1 := parseV4("192.0.2.11/32")
	s2 := parseV4("192.0.2.12/32")
	printSetType(s0)
	printSetType(s1, s2)
	printSetType(s0, s1, s2)
}

func parseV4(notation string) ipset.Block[ip.Addr4] {
	a, m, err := ipset.ParseCIDRNotation(ip.V4(), notation)
	if err != nil {
		panic(err)
	}
	return ipset.NewBlock(a, m)
}

func printSetType[A ip.Int[A]](sets ...ipset.Discrete[A]) {
	union := ipset.NewDiscrete(sets...)
	switch s := union.(type) {
	case ipset.Block[A]:
		println(fmt.Sprintf("%s is a block set", s.String()))
	case ipset.Interval[A]:
		println(fmt.Sprintf("%s is an interval set", s.String()))
	case ipset.Discrete[A]:
		println(fmt.Sprintf("%s is a discrete set", s.String()))
	}
}

type Interval

type Interval[A ip.Int[A]] interface {
	Discrete[A]
	// Least address
	First() (address A)
	// Greatest address
	Last() (address A)
}

Immutable set of IP addresses between first and last inclusive.

A range of one or more IP addresses. The name interval was chosen because range is a keyword in Go. Interval is a term from mathematical set theory.

func Extremes

func Extremes[A ip.Int[A]](i0, i1 Interval[A]) Interval[A]

Creates Interval using least and greatest values from each

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	r0 := parseV6Interval("2001:db8::", "2001:db8::100")
	r1 := parseV6Interval("2001:db8::10", "2001:db8::ffff:ffff:ffff")

	if ipset.Contiguous(r0, r1) {
		r2 := ipset.Extremes(r0, r1)
		println(r2.String())
	}
}

func parseV6Interval(first, last string) ipset.Interval[ip.Addr6] {
	v6 := ip.V6()
	p := ip.MustParse[ip.Addr6]
	return ipset.NewInterval(p(v6, first), p(v6, last))
}

func NewInterval

func NewInterval[A ip.Int[A]](first, last A) Interval[A]

Creates Interval set.

If range is valid CIDR block returns value from NewBlock instead.

Example
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

func main() {
	// 1st address in IPv4 subnet is network address.
	// Last address in IPv4 subnet is broadcast address.
	// The rest can be used for unicast.
	sub := ipset.NewBlock(ip.V4().MustFromBytes(203, 0, 113, 8), 29)
	first := ip.Next(sub.First())
	last := ip.Prev(sub.Last())

	allocations := ipset.NewInterval(first, last)

	println("Assignable addresses in ", sub.String())
	for a := range allocations.Addresses() {
		println(a.String())
	}
}

Jump to

Keyboard shortcuts

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