summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2023-02-04 16:32:49 -0500
committerDave Henderson <dhenderson@gmail.com>2023-02-04 21:01:14 -0500
commitedf224ccf7c66498b2d9743fcfb29bf4c0960931 (patch)
tree33461cb75f2d63e91be1d8a7ec5a9e72ce962f12 /internal
parentaf3e81ac52f93f07aea644b6397dbca97e5d30aa (diff)
Deprecate netaddr-based funcs
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/cidr/cidr.go138
-rw-r--r--internal/cidr/cidr_test.go203
-rw-r--r--internal/tests/integration/net_test.go2
3 files changed, 342 insertions, 1 deletions
diff --git a/internal/cidr/cidr.go b/internal/cidr/cidr.go
new file mode 100644
index 00000000..7ac4b053
--- /dev/null
+++ b/internal/cidr/cidr.go
@@ -0,0 +1,138 @@
+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)
+ }
+
+ 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))
+ maxHostNum.Lsh(maxHostNum, uint(hostLen))
+ maxHostNum.Sub(maxHostNum, big.NewInt(1))
+
+ numUint64 := big.NewInt(int64(num.Uint64()))
+ if num.Cmp(big.NewInt(0)) == -1 {
+ numUint64.Neg(num)
+ numUint64.Sub(numUint64, big.NewInt(int64(1)))
+ num.Sub(maxHostNum, numUint64)
+ }
+
+ if numUint64.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)
+ 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 maxium 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
+}
diff --git a/internal/cidr/cidr_test.go b/internal/cidr/cidr_test.go
new file mode 100644
index 00000000..1b8cc90a
--- /dev/null
+++ b/internal/cidr/cidr_test.go
@@ -0,0 +1,203 @@
+package cidr
+
+import (
+ "fmt"
+ "math/big"
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSubnetBig(t *testing.T) {
+ cases := []struct {
+ base string
+ num *big.Int
+ out string
+ bits int
+ err bool
+ }{
+ {
+ base: "192.168.2.0/20",
+ bits: 4,
+ num: big.NewInt(int64(6)),
+ out: "192.168.6.0/24",
+ },
+ {
+ base: "192.168.2.0/20",
+ bits: 4,
+ num: big.NewInt(int64(0)),
+ out: "192.168.0.0/24",
+ },
+ {
+ base: "192.168.0.0/31",
+ bits: 1,
+ num: big.NewInt(int64(1)),
+ out: "192.168.0.1/32",
+ },
+ {
+ base: "192.168.0.0/21",
+ bits: 4,
+ num: big.NewInt(int64(7)),
+ out: "192.168.3.128/25",
+ },
+ {
+ base: "fe80::/48",
+ bits: 16,
+ num: big.NewInt(int64(6)),
+ out: "fe80:0:0:6::/64",
+ },
+ {
+ base: "fe80::/48",
+ bits: 33,
+ num: big.NewInt(int64(6)),
+ out: "fe80::3:0:0:0/81",
+ },
+ {
+ base: "fe80::/49",
+ bits: 16,
+ num: big.NewInt(int64(7)),
+ out: "fe80:0:0:3:8000::/65",
+ },
+ {
+ base: "192.168.2.0/31",
+ bits: 2,
+ num: big.NewInt(int64(0)),
+ err: true, // not enough bits to expand into
+ },
+ {
+ base: "fe80::/126",
+ bits: 4,
+ num: big.NewInt(int64(0)),
+ err: true, // not enough bits to expand into
+ },
+ {
+ base: "192.168.2.0/24",
+ bits: 4,
+ num: big.NewInt(int64(16)),
+ err: true, // can't fit 16 into 4 bits
+ },
+ }
+
+ for _, testCase := range cases {
+ t.Run(fmt.Sprintf("SubnetBig(%#v,%#v,%#v)", testCase.base, testCase.bits, testCase.num), func(t *testing.T) {
+ base := netip.MustParsePrefix(testCase.base)
+
+ subnet, err := SubnetBig(base, testCase.bits, testCase.num)
+ if testCase.err {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, testCase.out, subnet.String())
+ }
+ })
+ }
+}
+
+func TestHostBig(t *testing.T) {
+ cases := []struct {
+ prefix string
+ num *big.Int
+ out string
+ err bool
+ }{
+ {
+ prefix: "192.168.2.0/20",
+ num: big.NewInt(int64(6)),
+ out: "192.168.0.6",
+ },
+ {
+ prefix: "192.168.0.0/20",
+ num: big.NewInt(int64(257)),
+ out: "192.168.1.1",
+ },
+ {
+ prefix: "2001:db8::/32",
+ num: big.NewInt(int64(1)),
+ out: "2001:db8::1",
+ },
+ {
+ prefix: "192.168.1.0/24",
+ num: big.NewInt(int64(256)),
+ err: true, // only 0-255 will fit in 8 bits
+ },
+ {
+ prefix: "192.168.0.0/30",
+ num: big.NewInt(int64(-3)),
+ out: "192.168.0.1", // 4 address (0-3) in 2 bits; 3rd from end = 1
+ },
+ {
+ prefix: "192.168.0.0/30",
+ num: big.NewInt(int64(-4)),
+ out: "192.168.0.0", // 4 address (0-3) in 2 bits; 4th from end = 0
+ },
+ {
+ prefix: "192.168.0.0/30",
+ num: big.NewInt(int64(-5)),
+ err: true, // 4 address (0-3) in 2 bits; cannot accommodate 5
+ },
+ {
+ prefix: "fd9d:bc11:4020::/64",
+ num: big.NewInt(int64(2)),
+ out: "fd9d:bc11:4020::2",
+ },
+ {
+ prefix: "fd9d:bc11:4020::/64",
+ num: big.NewInt(int64(-2)),
+ out: "fd9d:bc11:4020:0:ffff:ffff:ffff:fffe",
+ },
+ }
+
+ for _, testCase := range cases {
+ t.Run(fmt.Sprintf("HostBig(%v,%v)", testCase.prefix, testCase.num), func(t *testing.T) {
+ network := netip.MustParsePrefix(testCase.prefix)
+
+ gotIP, err := HostBig(network, testCase.num)
+ if testCase.err {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, testCase.out, gotIP.String())
+ }
+ })
+ }
+}
+
+func TestPreviousNextSubnet(t *testing.T) {
+ testCases := []struct {
+ next, prev string
+ overflow bool
+ }{
+ {"10.0.0.0/24", "9.255.255.0/24", false},
+ {"100.0.0.0/26", "99.255.255.192/26", false},
+ {"0.0.0.0/26", "255.255.255.192/26", true},
+ {"2001:db8:e000::/36", "2001:db8:d000::/36", false},
+ {"::/64", "ffff:ffff:ffff:ffff::/64", true},
+ }
+ for _, tc := range testCases {
+ c1 := netip.MustParsePrefix(tc.next)
+ c2 := netip.MustParsePrefix(tc.prev)
+ mask := c1.Bits()
+
+ p1, rollback := PreviousSubnet(c1, mask)
+ if tc.overflow {
+ assert.True(t, rollback)
+ continue
+ }
+
+ assert.Equal(t, c2.String(), p1.String())
+ }
+
+ for _, tc := range testCases {
+ c1 := netip.MustParsePrefix(tc.next)
+ c2 := netip.MustParsePrefix(tc.prev)
+ mask := c1.Bits()
+
+ n1, rollover := NextSubnet(c2, mask)
+ if tc.overflow {
+ assert.True(t, rollover)
+ continue
+ }
+ assert.Equal(t, c1, n1)
+ }
+}
diff --git a/internal/tests/integration/net_test.go b/internal/tests/integration/net_test.go
index 35218eaf..a0ff4e6f 100644
--- a/internal/tests/integration/net_test.go
+++ b/internal/tests/integration/net_test.go
@@ -9,7 +9,7 @@ func TestNet_LookupIP(t *testing.T) {
}
func TestNet_CIDRHost(t *testing.T) {
- inOutTestExperimental(t, `{{ net.ParseIPPrefix "10.12.127.0/20" | net.CIDRHost 16 }}`, "10.12.112.16")
+ inOutTestExperimental(t, `{{ net.ParsePrefix "10.12.127.0/20" | net.CIDRHost 16 }}`, "10.12.112.16")
inOutTestExperimental(t, `{{ "10.12.127.0/20" | net.CIDRHost 16 }}`, "10.12.112.16")
inOutTestExperimental(t, `{{ net.CIDRHost 268 "10.12.127.0/20" }}`, "10.12.113.12")
inOutTestExperimental(t, `{{ net.CIDRHost 34 "fd00:fd12:3456:7890:00a2::/72" }}`, "fd00:fd12:3456:7890::22")