Help us shape the future of Go! Take the 2026 Go Developer Survey

inet

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2019 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package inet represents IP-addresses and IP-Blocks as comparable types.

The IP addresses and blocks from this package can be used as keys in maps, freely copied and fast sorted without prior conversion from/to IPv4/IPv6. A Tree implementation for lookups with longest-prefix-match is included.

Some missing utility functions in the standard library for IP-addresses and IP-blocks are provided.

This is a package for system programming, all fields are public for easy and fast serialization without special treatment. Anyway, you should not direct modify the fields and bytes, unless you know what you are doing.

IP addresses are represented as fixed arrays of 21 bytes, this ensures natural sorting (IPv4 < IPv6).

type IP [21]byte

 IP[0]    = version information (4 or 6)
 IP[1:5]  = IPv4 address, if version == 4, else zero
 IP[5:21] = IPv6 address, if version == 6, else zero

Blocks are IP-networks or IP-ranges, e.g.

192.168.0.1/24              // CIDR, network
::1/128                     // CIDR, network
10.0.0.3-10.0.17.134        // range
2001:db8::1-2001:db8::f6    // range

A Block is represented as a struct of three IP addresses:

type Block struct {
 Base IP
 Last IP
 Mask IP  // may be zero for begin-end ranges
}

Tree is an implementation of a CIDR/Block prefix tree for fast IP lookup with longest-prefix-match. It is NOT a standard patricia-trie, this isn't possible for general blocks not represented by bitmasks.

type Tree struct {
 // Contains the root node the tree.
 Root *Node
}

Node, recursive tree data structure. Items abstracted via Itemer interface

type Node struct {
 Item   *Itemer
 Parent *Node
 Childs []*Node
}

Itemer interface for Tree items, maybe with payload and not just ip Blocks. See relation between Compare and Contains at inet.Block.Compare()

type Itemer interface {

 // Contains, defines the depth in the tree, parent child relationship.
 Contains(Itemer) bool

 // Compare, defines equality and sort order on same tree level, siblings relationship.
 Compare(Itemer) int
}

The tree can be visualized as:

▼
├─ 10.0.0.0/9
│  ├─ 10.0.0.0/11
│  │  ├─ 10.0.0.0/20
│  │  ├─ 10.0.16.0/20
│  │  └─ 10.0.32.0/20
│  └─ 10.32.0.0/11
│     ├─ 10.32.8.0/22
│     ├─ 10.32.12.0/22
│     └─ 10.32.16.0/22
├─ 2001:db8:900::/48
│  ├─ 2001:db8:900::/49
│  │  ├─ 2001:db8:900::/52

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// IPZero is the zero-value for type IP.
	//
	// IP is represented as an array, so we have no nil as zero value.
	// IPZero can be used for that.
	IPZero = IP{}

	// BlockZero is the zero-value for type Block.
	//
	// Block is represented as a struct, so we have no nil as zero value.
	// BlockZero can be used for that.
	BlockZero = Block{Base: IP{}, Last: IP{}, Mask: IP{}}

	// MaxCIDRSplit limits the input parameter for SplitCIDR() to 20 (max 2^20 CIDRs) for security.
	MaxCIDRSplit int = 20
)
View Source
var (
	ErrInvalidIP       = errors.New("invalid IP")
	ErrInvalidBlock    = errors.New("invalid Block")
	ErrVersionMismatch = errors.New("IPv4/IPv6 version mismatch")
	ErrOverflow        = errors.New("overflow")
	ErrUnderflow       = errors.New("underflow")
)

Functions

func SortBlock

func SortBlock(bs []Block)

SortBlock sorts the given slice of Blocks in place, see Compare() for sort order. IPv4 Blocks are sorted before IPv6 Blocks. Outer sets are sorted before their subsets!

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	var buf []inet.Block
	for _, s := range []string{
		"2001:db8:dead:beef::/44",
		"10.0.0.0/9",
		"::/0",
		"10.96.0.2-10.96.1.17",
		"0.0.0.0/0",
		"::-::ffff",
		"2001:db8::/32",
	} {
		buf = append(buf, inet.MustBlock(s))
	}

	inet.SortBlock(buf)
	fmt.Printf("%v\n", buf)

}
Output:
[0.0.0.0/0 10.0.0.0/9 10.96.0.2-10.96.1.17 ::/0 ::/112 2001:db8::/32 2001:db8:dea0::/44]

func SortIP

func SortIP(ips []IP)

SortIP sorts the given slice in place. IPv4 addresses are sorted 'naturally' before IPv6 addresses, no prior conversion or version split necessary.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	var buf []inet.IP
	for _, s := range []string{
		"2001:db8::",
		"127.0.0.1",
		"::1",
	} {
		buf = append(buf, inet.MustIP(s))
	}

	inet.SortIP(buf)
	fmt.Printf("%v\n", buf)

}
Output:
[127.0.0.1 ::1 2001:db8::]

Types

type Block

type Block struct {
	Base IP
	Last IP
	Mask IP // IPZero for ranges without CIDR mask
}

Block is an IP-network or IP-range, e.g.

192.168.0.1/24              // network, with CIDR mask
::1/128                     // network, with CIDR mask
10.0.0.3-10.0.17.134        // range
2001:db8::1-2001:db8::f6    // range

This Block representation is comparable and can be used as key in maps and fast sorted without conversions to/from the different IP versions.

func Aggregate

func Aggregate(bs []Block) []Block

Aggregate, returns the minimal number of CIDRs spanning the range of input blocks.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	var bs []inet.Block
	for _, s := range []string{
		"10.0.0.0/32",
		"10.0.0.1/32",
		"10.0.0.4/30",
		"10.0.0.7/31",
		"fe80::/12",
		"fe80:0000:0000:0000:fe2d:5eff:fef0:fc64/128",
		"fe80::/10",
	} {
		bs = append(bs, inet.MustBlock(s))
	}

	packed := inet.Aggregate(bs)
	fmt.Printf("%v\n", packed)

}
Output:
[10.0.0.0/31 10.0.0.4/30 fe80::/10]

func MustBlock

func MustBlock(i interface{}) Block

MustBlock is a helper that calls ParseBlock and returns just inet.Block or panics on errr. It is intended for use in variable initializations.

func ParseBlock added in v0.10.0

func ParseBlock(i interface{}) (Block, error)

ParseBlock parses and returns the input as type Block. The input type may be:

 string

 net.IPNet
*net.IPNet

 net.IP
*net.IP

 inet.IP

Example for valid input strings:

"2001:db8:dead:/38"
"10.0.0.0/8"

"2001:db8::1-2001:db8::ff00:35"
"192.168.2.3-192.168.7.255"

If a begin-end range can be represented as a CIDR, ParseBlock() generates the netmask and returns the range as CIDR.

IP addresses in the form of net.IP or inet.IP as input are converted to /32 or /128 blocks Returns error and BlockZero on invalid input.

Example
package main

import (
	"fmt"
	"net"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, anyOf := range []interface{}{
		"fe80::1-fe80::2",         // block from string
		"10.0.0.0-11.255.255.255", // block from string, as range but true CIDR, see output

		net.IP{192, 168, 0, 0},         // IP from net.IP
		inet.MustIP("2001:db8:900::1"), // IP from inet.IP
	} {
		a, _ := inet.ParseBlock(anyOf)
		fmt.Printf("block: %v\n", a)
	}

}
Output:
block: fe80::1-fe80::2
block: 10.0.0.0/7
block: 192.168.0.0/32
block: 2001:db8:900::1/128

func (Block) BitLen added in v0.8.0

func (a Block) BitLen() int

BitLen returns the minimum number of bits to represent the block.

func (Block) BlockToCIDRList

func (a Block) BlockToCIDRList() []Block

BlockToCIDRList returns a list of CIDRs spanning the range of a.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, s := range []string{
		"10.128.0.0-10.128.2.7",
		"2001:b8::3-2001:b8::f",
	} {
		a := inet.MustBlock(s)
		fmt.Printf("%v -> %v\n", a, a.BlockToCIDRList())
	}

}
Output:
10.128.0.0-10.128.2.7 -> [10.128.0.0/23 10.128.2.0/29]
2001:b8::3-2001:b8::f -> [2001:b8::3/128 2001:b8::4/126 2001:b8::8/125]

func (Block) Compare

func (a Block) Compare(b Itemer) int

Compare returns an integer comparing two IP Blocks, implements the Itemer interface for Tree items.

 0 if a == b,

-1 if a is v4 and b is v6
+1 if a is v6 and b is v4

-1 if a.Base < b.Base
+1 if a.Base > b.Base

-1 if a.Base == b.Base and a is SuperSet of b
+1 if a.Base == b.Base and a is Subset of b
Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	a := inet.MustBlock("127.0.0.0/8")
	b := inet.MustBlock("127.0.0.0/8")
	fmt.Printf("Block{%v}.Compare(Block{%v}) = %d\n", a, b, a.Compare(b))

	a = inet.MustBlock("0.0.0.0/0")
	b = inet.MustBlock("::/0")
	fmt.Printf("Block{%v}.Compare(Block{%v}) = %d\n", a, b, a.Compare(b))

	a = inet.MustBlock("127.128.0.0/9")
	b = inet.MustBlock("127.0.0.0/8")
	fmt.Printf("Block{%v}.Compare(Block{%v}) = %d\n", a, b, a.Compare(b))

	a = inet.MustBlock("fe80::/10")
	b = inet.MustBlock("fe80::/12")
	fmt.Printf("Block{%v}.Compare(Block{%v}) = %d\n", a, b, a.Compare(b))

}
Output:
Block{127.0.0.0/8}.Compare(Block{127.0.0.0/8}) = 0
Block{0.0.0.0/0}.Compare(Block{::/0}) = -1
Block{127.128.0.0/9}.Compare(Block{127.0.0.0/8}) = 1
Block{fe80::/10}.Compare(Block{fe80::/12}) = -1

func (Block) Contains

func (a Block) Contains(b Itemer) bool

Contains reports whether Block a contains Block b. a and b may NOT coincide. Implements the Itemer interface for Tree items.

a   |------------|    |------------|           |------------|
b |-----------------| |-----------------| |-----------------|

func (Block) FindFreeCIDR

func (a Block) FindFreeCIDR(bs []Block) []Block

FindFreeCIDR returns all free CIDR blocks (of max possible bitlen) within given CIDR, minus the inner CIDR blocks. Panics if inner blocks are no subset of (or not equal to) outer block.

Example (V4)
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	outer := inet.MustBlock("192.168.2.0/24")

	inner := make([]inet.Block, 0)
	inner = append(inner, inet.MustBlock("192.168.2.0/26"))
	inner = append(inner, inet.MustBlock("192.168.2.240-192.168.2.249"))

	free := outer.FindFreeCIDR(inner)
	fmt.Printf("%v - %v\nfree: %v\n", outer, inner, free)

}
Output:
192.168.2.0/24 - [192.168.2.0/26 192.168.2.240-192.168.2.249]
free: [192.168.2.64/26 192.168.2.128/26 192.168.2.192/27 192.168.2.224/28 192.168.2.250/31 192.168.2.252/30]
Example (V6)
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	outer := inet.MustBlock("2001:db8:de00::/40")

	inner := make([]inet.Block, 0)
	inner = append(inner, inet.MustBlock("2001:db8:dea0::/44"))

	free := outer.FindFreeCIDR(inner)
	fmt.Printf("%v - %v\nfree: %v\n", outer, inner, free)

}
Output:
2001:db8:de00::/40 - [2001:db8:dea0::/44]
free: [2001:db8:de00::/41 2001:db8:de80::/43 2001:db8:deb0::/44 2001:db8:dec0::/42]

func (Block) IsCIDR

func (a Block) IsCIDR() bool

IsCIDR returns true if the block is a true CIDR, not just a begin-end range.

func (Block) IsDisjunctWith

func (a Block) IsDisjunctWith(b Block) bool

IsDisjunctWith reports whether the Blocks a and b are disjunct

a       |----------|
b |---|

a |------|
b          |---|

func (Block) IsValid

func (a Block) IsValid() bool

IsValid returns true on valid Blocks, false otherwise.

func (Block) MarshalText

func (a Block) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface. The encoding is the same as returned by String.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, s := range []string{
		"127.0.0.0/8",
		"fe80::/10",
		"10.0.0.0-10.1.0.0",
		"",
	} {
		a, _ := inet.ParseBlock(s)
		bs, _ := a.MarshalText()
		fmt.Printf("%-20v %-20q %v\n", string(bs), string(bs), bs)
	}

}
Output:
127.0.0.0/8          "127.0.0.0/8"        [49 50 55 46 48 46 48 46 48 47 56]
fe80::/10            "fe80::/10"          [102 101 56 48 58 58 47 49 48]
10.0.0.0-10.1.0.0    "10.0.0.0-10.1.0.0"  [49 48 46 48 46 48 46 48 45 49 48 46 49 46 48 46 48]
                     ""                   []

func (Block) OverlapsWith

func (a Block) OverlapsWith(b Block) bool

OverlapsWith reports whether the Blocks overlaps.

a    |-------|
b |------|

a |------|
b    |-------|

a |----|
b      |---------|

a      |---------|
b |----|

func (Block) Size

func (a Block) Size() string

Size returns the number of ip addresses as string. Returns a string, since the amount of ip addresses can be greater than uint64.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, s := range []string{
		"0.0.0.0/12",
		"::/0",
		"10.0.0.0-10.0.0.43",
		"2001:db8::1-2001:db8::ffec",
	} {
		a := inet.MustBlock(s)
		fmt.Printf("%-30v size min. %d bits\n", a, a.BitLen())

		if a.BitLen() < 21 {
			fmt.Printf("%-30v len %s IPs\n", a, a.Size())
		}
	}

}
Output:
0.0.0.0/12                     size min. 20 bits
0.0.0.0/12                     len 1048576 IPs
::/0                           size min. 128 bits
10.0.0.0-10.0.0.43             size min. 6 bits
10.0.0.0-10.0.0.43             len 44 IPs
2001:db8::1-2001:db8::ffec     size min. 16 bits
2001:db8::1-2001:db8::ffec     len 65516 IPs

func (Block) SplitCIDR

func (a Block) SplitCIDR(n int) []Block

SplitCIDR returns the next 2^n CIDRs, splitted from outer block. The number of CIDRs is limited to MaxCIDRSplit, panics if more CIDRs are requested. Returns nil at max mask length or if block is no CIDR.

Example (V4)
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	a := inet.MustBlock("127.0.0.1/8")
	splits := a.SplitCIDR(2)
	fmt.Println(a, splits)

}
Output:
127.0.0.0/8 [127.0.0.0/10 127.64.0.0/10 127.128.0.0/10 127.192.0.0/10]
Example (V6)
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	a := inet.MustBlock("2001:db8:dea0::/44")
	splits := a.SplitCIDR(1)
	fmt.Println(a, splits)

}
Output:
2001:db8:dea0::/44 [2001:db8:dea0::/45 2001:db8:dea8::/45]

func (Block) String

func (a Block) String() string

String implements the fmt.Stringer interface. Returns "" on BockZero.

func (*Block) UnmarshalText

func (a *Block) UnmarshalText(text []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface. The block is expected in a form accepted by ParseBlock.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	var a = new(inet.Block)
	for _, s := range []string{
		"127.0.000.255/8",         // base gets truncated by CIDR mask, see output
		"10.000.000.000-10.1.0.0", // leading zeros are normalized, see output
		"",                        // empty input string aka []byte(nil) returns zero-value (BlockZero) on UnmarshalText()
		"fe80::",                  // invalid
	} {
		err := a.UnmarshalText([]byte(s))
		if err != nil {
			fmt.Println("ERROR:", err)
			continue
		}
		fmt.Printf("%q\n", a)
	}

}
Output:
"127.0.0.0/8"
"10.0.0.0-10.1.0.0"
""
ERROR: invalid Block

func (Block) Version

func (a Block) Version() int

Version returns the IP version, 4 or 6, panics on invalid block.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, s := range []string{
		"10.0.0.1/8",
		"fe80::/10",
		"::-::1",
	} {
		a, _ := inet.ParseBlock(s)
		fmt.Println(a.Version())
	}

}
Output:
4
6
6

type IP

type IP [21]byte

IP represents a single IPv4 or IPv6 address in a fixed array of 21 bytes.

IP[0]    = version information (4 or 6)
IP[1:5]  = IPv4 address, if version == 4, else zero
IP[5:21] = IPv6 address, if version == 6, else zero

This IP representation is comparable and can be used as key in maps and fast sorted by bytes.Compare() without conversions to/from the different IP versions.

func MustIP

func MustIP(i interface{}) IP

MustIP is a helper that calls ParseIP and returns just inet.IP or panics on errr. It is intended for use in variable initializations.

func ParseIP added in v0.10.0

func ParseIP(i interface{}) (IP, error)

ParseIP parses and returns the input as type IP. The input type may be:

 string
 net.IP
*net.IP
 []byte

The hard part is done by net.ParseIP(). Returns IPZero and error on invalid input.

Example
package main

import (
	"fmt"
	"net"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, anyOf := range []interface{}{
		"2001:db8::",         // string
		net.IP{10, 0, 0, 1},  // net.IP
		[]byte{127, 0, 0, 1}, // []byte, IPv4
		&net.IP{0: 0x20, 1: 0x01, 2: 0x0d, 3: 0xb9, 15: 0x01}, // *net.IP, IPv6
		[]byte{0: 0xff, 1: 0xfe, 15: 0x01},                    // []byte, IPv6
	} {
		ip, _ := inet.ParseIP(anyOf)
		fmt.Printf("ip: %v\n", ip)
	}

}
Output:
ip: 2001:db8::
ip: 10.0.0.1
ip: 127.0.0.1
ip: 2001:db9::1
ip: fffe::1

func (IP) AddBytes

func (ip IP) AddBytes(bs []byte) IP

AddBytes adds byte slice to ip, panics on overflow.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, tt := range []struct {
		ip  string
		add []byte
	}{
		{"0.0.0.0", []byte{255, 255, 255, 255}},
		{"::", []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
	} {
		ip := inet.MustIP(tt.ip)
		z := ip.AddBytes(tt.add)
		fmt.Printf("%v + %#x = %v\n", tt.ip, tt.add, z)
	}

}
Output:
0.0.0.0 + 0xffffffff = 255.255.255.255
:: + 0xffffffffffffffffffffffffffffffff = ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff

func (IP) AddUint64

func (ip IP) AddUint64(i uint64) IP

AddUint64 adds i to ip, panics on overflow.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("127.0.0.1"),
		inet.MustIP("2001:db8::"),
	} {
		z := ip.AddUint64(1)
		fmt.Printf("%-10v + %d = %v\n", ip, 1, z)
	}

}
Output:
127.0.0.1  + 1 = 127.0.0.2
2001:db8:: + 1 = 2001:db8::1

func (IP) Bytes

func (ip IP) Bytes() []byte

Bytes returns the ip address in byte representation. Returns 4 bytes for IPv4 and 16 bytes for IPv6. Panics on invalid input.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("192.168.2.1"),
		inet.MustIP("fffe:0db8::"),
	} {
		buf := ip.Bytes()
		fmt.Printf("%#v\n", buf)
	}

}
Output:
[]byte{0xc0, 0xa8, 0x2, 0x1}
[]byte{0xff, 0xfe, 0xd, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

func (IP) Compare

func (a IP) Compare(b IP) int

Compare returns an integer comparing two IP addresses lexicographically. The result will be:

 0 if a == b
-1 if a < b
+1 if a > b
Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	a := inet.MustIP("127.0.0.1")
	b := inet.MustIP("10.10.0.1")
	fmt.Printf("IP{%v}.Compare(IP{%v}) = %d\n", a, b, a.Compare(b))

	a = inet.MustIP("0.0.0.0")
	b = inet.MustIP("::")
	fmt.Printf("IP{%v}.Compare(IP{%v}) = %d\n", a, b, a.Compare(b))

	a = inet.MustIP("fe80::1")
	b = inet.MustIP("fe80::1")
	fmt.Printf("IP{%v}.Compare(IP{%v}) = %d\n", a, b, a.Compare(b))

}
Output:
IP{127.0.0.1}.Compare(IP{10.10.0.1}) = 1
IP{0.0.0.0}.Compare(IP{::}) = -1
IP{fe80::1}.Compare(IP{fe80::1}) = 0

func (IP) Expand

func (ip IP) Expand() string

Expand IP address into canonical form, useful for grep, aligned output and lexical sort.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("192.168.2.1"),
		inet.MustIP("fffe:db8::"),
	} {

		fmt.Printf("%q\n", ip.Expand())
	}

}
Output:
"192.168.002.001"
"fffe:0db8:0000:0000:0000:0000:0000:0000"

func (IP) IsValid

func (ip IP) IsValid() bool

IsValid returns true on valid IPs, false otherwise.

func (IP) MarshalText

func (ip IP) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface. The encoding is the same as returned by String.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("127.0.0.1"),
		inet.MustIP("fe80::1"),
		inet.IPZero,
	} {

		bs, err := ip.MarshalText()
		if err != nil {
			fmt.Printf("%.78s ...\n", err)
			continue
		}
		fmt.Printf("%-15v %-15q %v\n", string(bs), string(bs), bs)
	}

}
Output:
127.0.0.1       "127.0.0.1"     [49 50 55 46 48 46 48 46 49]
fe80::1         "fe80::1"       [102 101 56 48 58 58 49]
                ""              []

func (IP) Reverse

func (ip IP) Reverse() string

Reverse IP address, needed for PTR entries in DNS zone files.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("192.168.2.1"),
		inet.MustIP("fffe:db8::"),
	} {

		fmt.Printf("%q\n", ip.Reverse())
	}

}
Output:
"1.2.168.192"
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.e.f.f.f"

func (IP) String

func (ip IP) String() string

String implements the fmt.Stringer interface. Returns "" on IPZero, panics otherwise on invalid input.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("127.0.0.1"),
		inet.MustIP("fe80::1"),
		inet.IPZero,
		// {5, 10, 0, 0, 0}, inet.String() panics on invalid input.
	} {
		fmt.Printf("%#v\n", ip.String())
	}

}
Output:
"127.0.0.1"
"fe80::1"
""

func (IP) SubBytes

func (ip IP) SubBytes(bs []byte) IP

SubBytes subtract byte slice from ip, panics on underflow.

func (IP) SubUint64

func (ip IP) SubUint64(i uint64) IP

SubUint64 subtracts i from ip, panics on underflow.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("127.0.0.0"),
		inet.MustIP("2001:db8::"),
	} {
		z := ip.SubUint64(1)
		fmt.Printf("%-10v - %#x = %v\n", ip, 1, z)
	}

}
Output:
127.0.0.0  - 0x1 = 126.255.255.255
2001:db8:: - 0x1 = 2001:db7:ffff:ffff:ffff:ffff:ffff:ffff

func (IP) ToNetIP

func (ip IP) ToNetIP() net.IP

ToNetIP converts to net.IP. Panics on invalid input.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("192.168.2.1"),
		inet.MustIP("fffe:db8::"),
		// {5}, Panics on invalid input.
	} {
		fmt.Printf("%#v\n", ip.ToNetIP())
	}

}
Output:
net.IP{0xc0, 0xa8, 0x2, 0x1}
net.IP{0xff, 0xfe, 0xd, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

func (*IP) UnmarshalText

func (ip *IP) UnmarshalText(text []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface. The IP address is expected in a form accepted by ParseIP(string).

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	var ip = new(inet.IP)
	for _, s := range []string{
		"127.0.0.1",
		"fe80::1",
		"", // empty input string aka []byte(nil) returns zero-value (IPZero) on UnmarshalText()
		"ge80::1",
	} {
		err := ip.UnmarshalText([]byte(s))
		if err != nil {
			fmt.Println(err)
			continue
		}
		fmt.Printf("%-15q %03d\n", ip, ip)
	}

}
Output:
"127.0.0.1"     &[004 127 000 000 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000]
"fe80::1"       &[006 000 000 000 000 254 128 000 000 000 000 000 000 000 000 000 000 000 000 000 001]
""              &[000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000]
invalid IP

func (IP) Version

func (ip IP) Version() int

Version returns 4 or 6 for valid IPs. Panics on invalid IP.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	for _, ip := range []inet.IP{
		inet.MustIP("192.168.2.1"),
		inet.MustIP("fffe:db8::"),
	} {
		fmt.Println(ip.Version())
	}

}
Output:
4
6

type Itemer added in v0.2.0

type Itemer interface {

	// Contains, defines the depth in the tree, parent child relationship.
	Contains(Itemer) bool

	// Compare, defines equality and sort order on same tree level, siblings relationship.
	Compare(Itemer) int
}

Itemer interface for tree items, maybe with payload and not just ip Blocks. See relation between Compare and Contains at inet.Block.Compare()

Example (Interface)
package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/gaissmai/go-inet/inet"
)

// record implements inet.Itemer
type record struct {
	cidr inet.Block
	txt  string
}

// Contains, part of the Itemer interface
func (a record) Contains(b inet.Itemer) bool {
	c, ok := b.(record)
	if !ok {
		panic(fmt.Errorf("incompatible types: %T != %T", a, b))
	}
	return a.cidr.Contains(c.cidr) ||
		(a.cidr.Compare(c.cidr) == 0 && strings.Compare(a.txt, c.txt) < 0)
}

// Compare, part of the Itemer interface
func (a record) Compare(b inet.Itemer) int {
	c, ok := b.(record)
	if !ok {
		panic(fmt.Errorf("incompatible types: %T != %T", a, b))
	}

	cmp := a.cidr.Compare(c.cidr)
	if cmp != 0 {
		return cmp
	}
	return strings.Compare(a.txt, c.txt)
}

// String implements the Stringer interface
func (a record) String() string {
	cidr := a.cidr.String()
	spaceDots := 38 - len(cidr)
	spacer := strings.Repeat(".", spaceDots)
	return fmt.Sprintf("%s %s %s", cidr, spacer, a.txt)
}

func main() {
	tr := inet.NewTree()
	for _, r := range []record{
		record{inet.MustBlock("0.0.0.0/8"), "lorem"},
		record{inet.MustBlock("1.0.0.0/8"), "lorem"},
		record{inet.MustBlock("::/64"), "lorem"},
		record{inet.MustBlock("5.0.0.0/8"), "lorem"},
		record{inet.MustBlock("0.0.0.0/0"), "lorem"},
		record{inet.MustBlock("10.0.0.0-10.0.0.17"), "lorem"},
		record{inet.MustBlock("::/0"), "same cidr"},
		record{inet.MustBlock("::/0"), "same cidr, different text"},
		record{inet.MustBlock("2001:7c0:900:1c2::/64"), "lorem"},
		record{inet.MustBlock("2001:7c0:900:1c2::0/127"), "lorem"},
		record{inet.MustBlock("2001:7c0:900:1c2::1/128"), "lorem"},
		record{inet.MustBlock("0.0.0.0/10"), "lorem"},
		// ...
	} {
		tr.Insert(r)
	}

	tr.Fprint(os.Stdout)

	/*

	   ▼
	   ├─ 0.0.0.0/0 ............................. lorem
	   │  ├─ 0.0.0.0/8 ............................. lorem
	   │  │  └─ 0.0.0.0/10 ............................ lorem
	   │  ├─ 1.0.0.0/8 ............................. lorem
	   │  ├─ 5.0.0.0/8 ............................. lorem
	   │  └─ 10.0.0.0-10.0.0.17 .................... lorem
	   └─ ::/0 .................................. same cidr
	      └─ ::/0 .................................. same cidr, different text
	         ├─ ::/64 ................................. lorem
	         └─ 2001:7c0:900:1c2::/64 ................. lorem
	            └─ 2001:7c0:900:1c2::/127 ................ lorem
	               └─ 2001:7c0:900:1c2::1/128 ............... lorem

	*/

}

type Node added in v0.2.0

type Node struct {
	Item   *Itemer
	Parent *Node
	Childs []*Node
}

Node, recursive data structure. Items are abstracted via Itemer interface

type Tree added in v0.2.0

type Tree struct {
	// Contains the root node of a tree.
	Root *Node
}

Tree is an implementation of a CIDR/Block tree for fast IP lookup with longest-prefix-match. It is NOT a standard patricia-trie, this isn't possible for general blocks not represented by bitmasks.

func NewTree added in v0.2.0

func NewTree() *Tree

NewTree allocates a new tree and returns the pointer.

func (*Tree) Fprint added in v0.2.0

func (t *Tree) Fprint(w io.Writer)

Fprint prints the ordered tree in ASCII graph to io.Writer. The items should implement the Stringer interface for readable output.

▼
├─ ::/8.................   "Reserved by IETF     [RFC3513][RFC4291]"
├─ 100::/8..............   "Reserved by IETF     [RFC3513][RFC4291]"
├─ 200::/7..............   "Reserved by IETF     [RFC4048]"
├─ 400::/6..............   "Reserved by IETF     [RFC3513][RFC4291]"
├─ 800::/5..............   "Reserved by IETF     [RFC3513][RFC4291]"
├─ 1000::/4.............   "Reserved by IETF     [RFC3513][RFC4291]"
├─ 2000::/3.............   "Global Unicast       [RFC3513][RFC4291]"
│  ├─ 2000::/4.............  "Test"
│  └─ 3000::/4.............  "FREE"
├─ 4000::/3.............   "Reserved by IETF     [RFC3513][RFC4291]"
├─ 6000::/3.............   "Reserved by IETF     [RFC3513][RFC4291]"

func (*Tree) Insert added in v0.2.0

func (t *Tree) Insert(b Itemer)

Insert one item into the tree. The position within the tree is defined by the Contains() and Compare() methods, part of the Itemer interface .

If you insert many values you should sort them first.

func (*Tree) InsertBulk added in v0.6.0

func (t *Tree) InsertBulk(i interface{})

InsertBulk takes a slice of []items, sorts the values and inserts it into the tree. If the input arg is no slice of items implementing the Itemer interface, the method will panic.

It is a convenience method. The user can also sort the slice itself and insert the elements in a loop with Insert()

func (*Tree) Lookup added in v0.2.0

func (t *Tree) Lookup(b Itemer) (Itemer, bool)

Lookup item for longest prefix match in the tree. If not found, returns input argument and false.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	bs := make([]inet.Block, 0)
	for _, s := range []string{
		"0.0.0.0/8",
		"1.0.0.0/8",
		"5.0.0.0/8",
		"0.0.0.0/0",
		"::/64",
		"::/0",
		"0.0.0.0/10",
	} {
		bs = append(bs, inet.MustBlock(s))
	}

	bt := inet.NewTree()
	bt.InsertBulk(bs)

	q := inet.MustBlock(inet.MustIP("5.0.122.12"))

	if match, ok := bt.Lookup(q); ok {
		fmt.Printf("inet.Lookup(%v): LPM found at: %v\n", q, match)
	}

}
Output:
inet.Lookup(5.0.122.12/32): LPM found at: 5.0.0.0/8
Example (Exists)
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {
	bs := make([]inet.Block, 0)
	for _, s := range []string{
		"0.0.0.0/8",
		"1.0.0.0/8",
		"5.0.0.0/8",
		"0.0.0.0/0",
		"::/64",
		"::/0",
		"0.0.0.0/10",
	} {
		bs = append(bs, inet.MustBlock(s))
	}

	bt := inet.NewTree()
	bt.InsertBulk(bs)

	// look for exists, exact match, not just LPM
	for _, s := range []string{
		"5.0.0.0/8",
		"5.0.1.2/32",
	} {
		q := inet.MustBlock(s)

		match, ok := bt.Lookup(q)
		if ok && match.Compare(q) == 0 {
			fmt.Printf("%q exists in tree\n", q)
		} else {
			fmt.Printf("%q doesn't exists in tree\n", q)
		}
	}

}
Output:
"5.0.0.0/8" exists in tree
"5.0.1.2/32" doesn't exists in tree

func (*Tree) Remove added in v0.2.0

func (t *Tree) Remove(b Itemer) bool

Remove one item from tree, relink parent/child relation at the gap. Returns true on success, false if not found.

func (*Tree) Walk added in v0.2.0

func (t *Tree) Walk(walkFn WalkFunc) error

Walk the Tree starting at root, calling walkFn for each node. At every node the walkFn is called with the node and the current depth as arguments. The walk stops if the walkFn returns an error not nil. The error is propagated by Walk() to the caller.

Example
package main

import (
	"fmt"

	"github.com/gaissmai/go-inet/inet"
)

func main() {

	bs := make([]inet.Block, 0)
	for _, s := range []string{
		"0.0.0.0/8",
		"1.0.0.0/8",
		"5.0.0.0/8",
		"0.0.0.0/0",
		"10.0.0.0-10.0.0.17",
		"::/64",
		"::/0",
		"2001:db8:900:1c2::/64",
		"2001:db8:900:1c2::0/127",
		"2001:db8:900:1c2::1/128",
		"0.0.0.0/10",
	} {
		bs = append(bs, inet.MustBlock(s))
	}
	tr := inet.NewTree()
	tr.InsertBulk(bs)

	var maxDepth int
	var maxWidth int

	var walkFn inet.WalkFunc = func(n *inet.Node, depth int) error {
		if depth > maxDepth {
			maxDepth = depth
		}
		if l := len(n.Childs); l > maxWidth {
			maxWidth = l
		}
		return nil
	}

	_ = tr.Walk(walkFn)

	fmt.Printf("max depth: %v\n", maxDepth)
	fmt.Printf("max width: %v\n", maxWidth)

}
Output:
max depth: 3
max width: 4

type WalkFunc added in v0.2.0

type WalkFunc func(n *Node, depth int) error

WalkFunc is the type of the function called for each node visited by Walk(). The arguments to the WalkFunc are the current node in the tree and the depth, starting by 0.

The Walk() stops if the WalkFunc returns an error.

Jump to

Keyboard shortcuts

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