summaryrefslogtreecommitdiff
path: root/internal/cidr/cidr.go
blob: de67b16cc990b61c57cc63c72adbc213d42d75b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package cidr

import (
	"fmt"
	"math/big"
	"net/netip"

	"go4.org/netipx"
)

// taken from github.com/apparentelymart/go-cidr/ and modified to use the net/netip
// package instead of the stdlib net package - this will hopefully be merged back
// upstream at some point

// SubnetBig takes a parent CIDR range and creates a subnet within it with the
// given number of additional prefix bits and the given network number. It
// differs from Subnet in that it takes a *big.Int for the num, instead of an int.
//
// For example, 10.3.0.0/16, extended by 8 bits, with a network number of 5,
// becomes 10.3.5.0/24 .
func SubnetBig(base netip.Prefix, newBits int, num *big.Int) (netip.Prefix, error) {
	parentLen := base.Bits()
	addrLen := base.Addr().BitLen()

	newPrefixLen := parentLen + newBits

	if newPrefixLen > addrLen {
		return netip.Prefix{}, fmt.Errorf("insufficient address space to extend prefix of %d by %d", parentLen, newBits)
	}

	//nolint:gosec // G115 doesn't apply here
	maxNetNum := uint64(1<<uint64(newBits)) - 1
	if num.Uint64() > maxNetNum {
		return netip.Prefix{}, fmt.Errorf("prefix extension of %d does not accommodate a subnet numbered %d", newBits, num)
	}

	prefix := netip.PrefixFrom(insertNumIntoIP(base.Masked().Addr(), num, newPrefixLen), newPrefixLen)

	return prefix, nil
}

// HostBig takes a parent CIDR range and turns it into a host IP address with
// the given host number. It differs from Host in that it takes a *big.Int for
// the num, instead of an int.
//
// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2.
func HostBig(base netip.Prefix, num *big.Int) (netip.Addr, error) {
	parentLen := base.Bits()
	addrLen := base.Addr().BitLen()

	hostLen := addrLen - parentLen

	maxHostNum := big.NewInt(int64(1))

	//nolint:gosec // G115 doesn't apply here
	maxHostNum.Lsh(maxHostNum, uint(hostLen))
	maxHostNum.Sub(maxHostNum, big.NewInt(1))

	num2 := big.NewInt(num.Int64())
	if num.Cmp(big.NewInt(0)) == -1 {
		num2.Neg(num)
		num2.Sub(num2, big.NewInt(int64(1)))
		num.Sub(maxHostNum, num2)
	}

	if num2.Cmp(maxHostNum) == 1 {
		return netip.Addr{}, fmt.Errorf("prefix of %d does not accommodate a host numbered %d", parentLen, num)
	}

	return insertNumIntoIP(base.Masked().Addr(), num, addrLen), nil
}

func ipToInt(ip netip.Addr) (*big.Int, int) {
	val := &big.Int{}
	val.SetBytes(ip.AsSlice())

	return val, ip.BitLen()
}

func intToIP(ipInt *big.Int, bits int) netip.Addr {
	ipBytes := ipInt.Bytes()
	ret := make([]byte, bits/8)
	// Pack our IP bytes into the end of the return array,
	// since big.Int.Bytes() removes front zero padding.
	for i := 1; i <= len(ipBytes); i++ {
		ret[len(ret)-i] = ipBytes[len(ipBytes)-i]
	}

	addr, ok := netip.AddrFromSlice(ret)
	if !ok {
		panic("invalid IP address")
	}

	return addr
}

func insertNumIntoIP(ip netip.Addr, bigNum *big.Int, prefixLen int) netip.Addr {
	ipInt, totalBits := ipToInt(ip)

	//nolint:gosec // G115 isn't relevant here
	bigNum.Lsh(bigNum, uint(totalBits-prefixLen))
	ipInt.Or(ipInt, bigNum)
	return intToIP(ipInt, totalBits)
}

// PreviousSubnet returns the subnet of the desired mask in the IP space
// just lower than the start of Prefix provided. If the IP space rolls over
// then the second return value is true
func PreviousSubnet(network netip.Prefix, prefixLen int) (netip.Prefix, bool) {
	previousIP := network.Masked().Addr().Prev()

	previous, err := previousIP.Prefix(prefixLen)
	if err != nil {
		return netip.Prefix{}, false
	}
	if !previous.IsValid() {
		return previous, true
	}

	return previous.Masked(), false
}

// NextSubnet returns the next available subnet of the desired mask size
// starting for the maximum IP of the offset subnet
// If the IP exceeds the maximum IP then the second return value is true
func NextSubnet(network netip.Prefix, prefixLen int) (netip.Prefix, bool) {
	currentLast := netipx.PrefixLastIP(network)

	currentSubnet, err := currentLast.Prefix(prefixLen)
	if err != nil {
		return netip.Prefix{}, false
	}

	last := netipx.PrefixLastIP(currentSubnet).Next()
	next, err := last.Prefix(prefixLen)
	if err != nil {
		return netip.Prefix{}, false
	}
	if !last.IsValid() {
		return next, true
	}
	return next, false
}