ipset

package
v0.0.29-alpha Latest Latest
Warning

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

Go to latest
Published: Feb 7, 2025 License: Apache-2.0 Imports: 9 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())
	}
}
Output:

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
address, mask, _ := ipset.ParseCIDRNotation(ip.V6(), "2001:db8::/32")
reservedForDocumentation := ipset.NewBlock(address, mask)
printRangeDetails(reservedForDocumentation)
Output:

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
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))
	}
}
Output:

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"

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

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 := ip.SubnetAddressCount(f, m)
		if sizeForMask.Cmp(min) >= 0 {
			return m
		}
	}
	formatted := humanize.BigComma(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())
	}
}
Output:

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...)
}
Output:

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 (
	humanize "github.com/dustin/go-humanize"
	"github.com/ipfreely-uk/go/ip"
	"github.com/ipfreely-uk/go/ipset"
)

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", humanize.BigComma(block.Size()))
}
Output:

Example (Second)
package main

import (
	"github.com/ipfreely-uk/go/ip"
	"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 := ip.SubnetMaskBits(mask)
	return ipset.NewBlock(network, bits)
}
Output:

func NewSingle

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

Creates Block set from a single address

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)
	// 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())
}
Output:

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())
}
Output:

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()))
	}
}
Output:

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))
}
Output:

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())
	}
}
Output:

Jump to

Keyboard shortcuts

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