From 17d6a3f1b7f3b4d96d04acd1b581d53a94859f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikk=20Margus=20M=C3=B6ll?= Date: Sun, 28 Aug 2022 15:41:39 +0300 Subject: [PATCH] https://github.com/yl2chen/cidranger/pull/43 --- brute.go | 23 +++++++ brute_test.go | 28 ++++++++ cidranger.go | 33 +++++----- cidranger_test.go | 36 ++++++++++- go.mod | 8 ++- go.sum | 11 +++- net/ip.go | 16 ++++- net/ip_test.go | 47 ++++++++++++++ trie.go | 89 ++++++++++++++++++++++++++ trie_test.go | 160 +++++++++++++++++++++++++++++++++++++++++++++- version.go | 17 +++++ 11 files changed, 442 insertions(+), 26 deletions(-) diff --git a/brute.go b/brute.go index 9159792..c3087b1 100644 --- a/brute.go +++ b/brute.go @@ -108,6 +108,24 @@ func (b *bruteRanger) CoveredNetworks(network netip.Prefix) ([]RangerEntry, erro return results, nil } +// Covering returns the list of RangerEntry(s) the given ipnet +// is covered. It's like ContainingNetworks() for ipnet. +func (b *bruteRanger) CoveringNetworks(network netip.Prefix) ([]RangerEntry, error) { + entries, err := b.getEntriesByVersion(network.Addr()) + if err != nil { + return nil, err + } + var results []RangerEntry + testNetwork := rnet.NewNetwork(network) + for _, entry := range entries { + entryNetwork := rnet.NewNetwork(entry.Network()) + if entryNetwork.Covers(testNetwork) { + results = append(results, entry) + } + } + return results, nil +} + // Len returns number of networks in ranger. func (b *bruteRanger) Len() int { return len(b.ipV4Entries) + len(b.ipV6Entries) @@ -122,3 +140,8 @@ func (b *bruteRanger) getEntriesByVersion(ip netip.Addr) (map[netip.Prefix]Range } return nil, ErrInvalidNetworkInput } + +// Just to complete interface +func (p *bruteRanger) Adjacent(network netip.Prefix) (RangerEntry, error) { + return nil, nil +} diff --git a/brute_test.go b/brute_test.go index 6da5a92..a93a24f 100644 --- a/brute_test.go +++ b/brute_test.go @@ -155,3 +155,31 @@ func TestCoveredNetworks(t *testing.T) { }) } } + +func TestCoveringNetworks(t *testing.T) { + for _, tc := range coveringNetworkTests { + t.Run(tc.name, func(t *testing.T) { + ranger := newBruteRanger() + for _, insert := range tc.inserts { + network := netip.MustParsePrefix(insert) + err := ranger.Insert(NewBasicRangerEntry(network)) + assert.NoError(t, err) + } + var expectedEntries []string + expectedEntries = append(expectedEntries, tc.networks...) + sort.Strings(expectedEntries) + snet := netip.MustParsePrefix(tc.search) + networks, err := ranger.CoveringNetworks(snet) + assert.NoError(t, err) + + var results []string + for _, result := range networks { + net := result.Network() + results = append(results, net.String()) + } + sort.Strings(results) + + assert.Equal(t, expectedEntries, results) + }) + } +} diff --git a/cidranger.go b/cidranger.go index f5aa859..99c1250 100644 --- a/cidranger.go +++ b/cidranger.go @@ -4,38 +4,37 @@ inclusion tests against it. To create a new instance of the path-compressed trie: - ranger := NewPCTrieRanger() + ranger := NewPCTrieRanger() To insert or remove an entry (any object that satisfies the RangerEntry interface): - _, network, _ := net.ParseCIDR("192.168.0.0/24") - ranger.Insert(NewBasicRangerEntry(*network)) - ranger.Remove(network) + _, network, _ := net.ParseCIDR("192.168.0.0/24") + ranger.Insert(NewBasicRangerEntry(*network)) + ranger.Remove(network) If you desire for any value to be attached to the entry, simply create custom struct that satisfies the RangerEntry interface: - type RangerEntry interface { - Network() net.IPNet - } + type RangerEntry interface { + Network() net.IPNet + } To test whether an IP is contained in the constructed networks ranger: - // returns bool, error - containsBool, err := ranger.Contains(net.ParseIP("192.168.0.1")) + // returns bool, error + containsBool, err := ranger.Contains(net.ParseIP("192.168.0.1")) To get a list of CIDR blocks in constructed ranger that contains IP: - // returns []RangerEntry, error - entries, err := ranger.ContainingNetworks(net.ParseIP("192.168.0.1")) + // returns []RangerEntry, error + entries, err := ranger.ContainingNetworks(net.ParseIP("192.168.0.1")) To get a list of all IPv4/IPv6 rangers respectively: - // returns []RangerEntry, error - entries, err := ranger.CoveredNetworks(*AllIPv4) - entries, err := ranger.CoveredNetworks(*AllIPv6) - + // returns []RangerEntry, error + entries, err := ranger.CoveredNetworks(*AllIPv4) + entries, err := ranger.CoveredNetworks(*AllIPv6) */ package cidranger @@ -78,7 +77,7 @@ func (b *basicRangerEntry) Network() netip.Prefix { // itself. func NewBasicRangerEntry(ipNet netip.Prefix) RangerEntry { return &basicRangerEntry{ - ipNet: ipNet, //.Masked(), + ipNet: ipNet.Masked(), } } @@ -89,6 +88,8 @@ type Ranger interface { Contains(ip netip.Addr) (bool, error) ContainingNetworks(ip netip.Addr) ([]RangerEntry, error) CoveredNetworks(network netip.Prefix) ([]RangerEntry, error) + CoveringNetworks(network netip.Prefix) ([]RangerEntry, error) + Adjacent(network netip.Prefix) (RangerEntry, error) Len() int } diff --git a/cidranger_test.go b/cidranger_test.go index fd002b7..5a0a6e9 100644 --- a/cidranger_test.go +++ b/cidranger_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" rnet "github.com/monoidic/cidranger/net" + "github.com/stretchr/testify/assert" ) /* @@ -30,6 +30,10 @@ func TestCoveredNetworksAgainstBaseIPv4(t *testing.T) { testCoversNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV4AWSRangesIPNets)) } +func TestCoveringNetworksAgainstBaseIPv4(t *testing.T) { + testCoveringNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV4AWSRangesIPNets)) +} + // IPv6 spans an extremely large address space (2^128), randomly generated IPs // will often fall outside of the test ranges (AWS public CIDR blocks), so it // it more meaningful for testing to run from a curated list of IPv6 IPs. @@ -45,6 +49,10 @@ func TestCoveredNetworksAgainstBaseIPv6(t *testing.T) { testCoversNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV6AWSRangesIPNets)) } +func TestCoveringNetworksAgainstBaseIPv6(t *testing.T) { + testCoveringNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV6AWSRangesIPNets)) +} + func testContainsAgainstBase(t *testing.T, iterations int, ipGen ipGenerator) { if testing.Short() { t.Skip("Skipping memory test in `-short` mode") @@ -121,6 +129,32 @@ func testCoversNetworksAgainstBase(t *testing.T, iterations int, netGen networkG } } +func testCoveringNetworksAgainstBase(t *testing.T, iterations int, netGen networkGenerator) { + if testing.Short() { + t.Skip("Skipping memory test in `-short` mode") + } + rangers := []Ranger{NewPCTrieRanger()} + baseRanger := newBruteRanger() + for _, ranger := range rangers { + configureRangerWithAWSRanges(t, ranger) + } + configureRangerWithAWSRanges(t, baseRanger) + + for i := 0; i < iterations; i++ { + network := netGen() + expected, err := baseRanger.CoveringNetworks(network.IPNet) + assert.NoError(t, err) + for _, ranger := range rangers { + actual, err := ranger.CoveringNetworks(network.IPNet) + assert.NoError(t, err) + assert.Equal(t, len(expected), len(actual)) + for _, network := range actual { + assert.Contains(t, expected, network) + } + } + } +} + /* ****************************************************************** Benchmarks. diff --git a/go.mod b/go.mod index d1ec911..9f05b8d 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,14 @@ module github.com/monoidic/cidranger go 1.18 -require github.com/stretchr/testify v1.6.1 +require ( + github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.7.0 +) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index afe7890..683a8b9 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/net/ip.go b/net/ip.go index 033c1fe..5bd0e7c 100644 --- a/net/ip.go +++ b/net/ip.go @@ -137,6 +137,20 @@ func (n NetworkNumber) Bit(position uint) (uint32, error) { return (n[idx] >> rShift) & 1, nil } +// FlipNthBit reverses the bit value at position. Position numbering is LSB 0. +func (n *NetworkNumber) FlipNthBit(position uint) error { + if int(position) > len(*n)*BitsPerUint32-1 { + return ErrInvalidBitPosition + } + idx := len(*n) - 1 - int(position/BitsPerUint32) + bitUintPosition := position % 32 + XORMask := 1 << bitUintPosition + //byteNum := net.IPv6len - (position / 8) - 1 + // getByteIndexOfBit(bitNum) + (*n)[idx] ^= uint32(XORMask) + return nil +} + // LeastCommonBitPosition returns the smallest position of the preceding common // bits of the 2 network numbers, and returns an error ErrNoGreatestCommonBit // if the two network number diverges from the first bit. @@ -219,7 +233,7 @@ func (n Network) Contains(nn NetworkNumber) bool { return true } -// Contains returns true if Network covers o, false otherwise +// Covers returns true if Network covers o, false otherwise func (n Network) Covers(o Network) bool { if len(n.Number) != len(o.Number) { return false diff --git a/net/ip_test.go b/net/ip_test.go index a1eb636..04ae6de 100644 --- a/net/ip_test.go +++ b/net/ip_test.go @@ -1,6 +1,7 @@ package net import ( + "errors" "math" "net/netip" "testing" @@ -73,6 +74,52 @@ func TestNetworkNumberBit(t *testing.T) { } } +func TestNetworkNumber_FlipNthBit(t *testing.T) { + cases := []struct { + initial NetworkNumber + position uint + expected NetworkNumber + name string + expectedErr error + }{ + { + NewNetworkNumber(netip.MustParseAddr("192.168.0.0")), + 8, + // 192.168.1.0 + NetworkNumber{0xc0_a8_01_00}, + "Flip bit 8", + nil, + }, + { + NewNetworkNumber(netip.MustParseAddr("128.0.0.1")), + 31, + // 0.0.0.1 + NetworkNumber{0x00_00_00_01}, + "Flip bit 31", + nil, + }, + { + NewNetworkNumber(netip.MustParseAddr("128.0.0.1")), + 32, + // 0.0.0.1 + NetworkNumber{0x00_00_00_01}, + "error in position", + errors.New("bit position not valid"), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + actual := tc.initial + err := actual.FlipNthBit(tc.position) + assert.Equal(t, tc.expectedErr, err) + if err == nil { + assert.Equal(t, tc.expected, actual) + + } + }) + } +} + func TestNetworkNumberBitError(t *testing.T) { cases := []struct { ip NetworkNumber diff --git a/trie.go b/trie.go index 6c4c6aa..e587a77 100644 --- a/trie.go +++ b/trie.go @@ -1,6 +1,7 @@ package cidranger import ( + "errors" "fmt" "net/netip" "strings" @@ -125,6 +126,13 @@ func (p *prefixTrie) CoveredNetworks(network netip.Prefix) ([]RangerEntry, error return p.coveredNetworks(net) } +// Covering returns the list of RangerEntry(s) the given ipnet +// is covered by. It's like ContainingNetworks() for ipnet. +func (p *prefixTrie) CoveringNetworks(network netip.Prefix) ([]RangerEntry, error) { + net := rnet.NewNetwork(network) + return p.coveringNetworks(net) +} + // Len returns number of networks in ranger. func (p *prefixTrie) Len() int { return p.size @@ -146,6 +154,55 @@ func (p *prefixTrie) String() string { p.targetBitPosition(), p.hasEntry(), strings.Join(children, "")) } +// Returns adjacent entries to givent entry, identified by given network. Returns nil if adjacent entry not exists. +// Adjacent networks are networks with only different lower bit in network address, e.g. 192.168.0.0/24 and 192.168.1.0/24 +// That networks can be mergeg, e.g 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 +func (p *prefixTrie) Adjacent(network netip.Prefix) (entry RangerEntry, err error) { + addr := network.Masked().Addr() + adjacentNumber := rnet.NewNetworkNumber(addr) + ones := network.Bits() + var size int + if addr.Is4() { + size = 32 + } else if addr.Is6() { + size = 128 + } else { + return nil, errors.New("invalid subnet") + } + + if ones == 0 { + // It's a full network, e.g. 0.0.0.0/0, there is no adjacents + return nil, nil + } + position := size - ones + if err := adjacentNumber.FlipNthBit(uint(position)); err != nil { + return nil, err + } + adjacentNet := rnet.NewNetwork(netip.PrefixFrom(adjacentNumber.ToIP(), ones)) + if entry, err = p.adjacent(adjacentNet); err != nil { + return nil, err + } + return entry, err +} + +func (p *prefixTrie) adjacent(network rnet.Network) (RangerEntry, error) { + if p.hasEntry() && p.network.Equal(network) { + return p.entry, nil + } + if p.targetBitPosition() < 0 { + return nil, nil + } + bit, err := p.targetBitFromIP(network.Number) + if err != nil { + return nil, err + } + child := p.children[bit] + if child != nil { + return child.adjacent(network) + } + return nil, nil +} + func (p *prefixTrie) contains(number rnet.NetworkNumber) (bool, error) { if !p.network.Contains(number) { return false, nil @@ -218,6 +275,38 @@ func (p *prefixTrie) coveredNetworks(network rnet.Network) ([]RangerEntry, error return results, nil } +func (p *prefixTrie) coveringNetworks(network rnet.Network) ([]RangerEntry, error) { + var results []RangerEntry + if !p.network.Covers(network) { + return results, nil + } + if p.hasEntry() { + results = []RangerEntry{p.entry} + } + if p.targetBitPosition() < 0 { + return results, nil + } + bit, err := p.targetBitFromIP(network.Number) + if err != nil { + return nil, err + } + child := p.children[bit] + if child != nil { + ranges, err := child.coveringNetworks(network) + if err != nil { + return nil, err + } + if len(ranges) > 0 { + if len(results) > 0 { + results = append(results, ranges...) + } else { + results = ranges + } + } + } + return results, nil +} + func (p *prefixTrie) insert(network rnet.Network, entry RangerEntry) (bool, error) { if p.network.Equal(network) { sizeIncreased := p.entry == nil diff --git a/trie_test.go b/trie_test.go index 30b97c3..81239b6 100644 --- a/trie_test.go +++ b/trie_test.go @@ -9,8 +9,9 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" rnet "github.com/monoidic/cidranger/net" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" ) func getAllByVersion(version rnet.IPVersion) netip.Prefix { @@ -397,7 +398,59 @@ func TestPrefixTrieContainingNetworks(t *testing.T) { } } -type coveredNetworkTest struct { +func TestPrefixTrie_Adjacent(t *testing.T) { + cases := []struct { + version rnet.IPVersion + inserts []string + network string + expected string + name string + }{ + { + rnet.IPv4, + []string{"192.168.0.0/24"}, + "192.168.1.0/24", + "192.168.0.0/24", + "check /24 adjacent", + }, + { + rnet.IPv4, + []string{"128.0.0.5/1"}, + "0.0.5.0/1", + "128.0.0.0/1", + "check /1 adjacent, with some bits in ip address range", + }, + { + rnet.IPv4, + []string{"0.0.0.0/0"}, + "0.0.0.0/0", + "", + "0.0.0.0/0 can't have adjacent", + }, + } + log.SetLevel(log.TraceLevel) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + trie := newPrefixTree(tc.version) + for _, insert := range tc.inserts { + network := netip.MustParsePrefix(insert) + err := trie.Insert(NewBasicRangerEntry(network)) + assert.NoError(t, err) + } + testNet := netip.MustParsePrefix(tc.network) + entry, err := trie.Adjacent(testNet) + assert.NoError(t, err) + netString := "" + if entry != nil { + entryNet := entry.Network() + netString = entryNet.String() + } + assert.Equal(t, tc.expected, netString) + }) + } +} + +type networkTest struct { version rnet.IPVersion inserts []string search string @@ -405,7 +458,7 @@ type coveredNetworkTest struct { name string } -var coveredNetworkTests = []coveredNetworkTest{ +var coveredNetworkTests = []networkTest{ { rnet.IPv4, []string{"192.168.0.0/24"}, @@ -413,6 +466,13 @@ var coveredNetworkTests = []coveredNetworkTest{ []string{"192.168.0.0/24"}, "basic covered networks", }, + { + rnet.IPv4, + []string{"192.168.0.0/24"}, + "192.168.0.0/24", + []string{"192.168.0.0/24"}, + "covered of equal networks", + }, { rnet.IPv4, []string{"192.168.0.0/24"}, @@ -469,6 +529,77 @@ var coveredNetworkTests = []coveredNetworkTest{ }, } +var coveringNetworkTests = []networkTest{ + { + rnet.IPv4, + []string{"192.168.0.0/16"}, + "192.168.0.0/24", + []string{"192.168.0.0/16"}, + "basic covering networks", + }, + { + rnet.IPv4, + []string{"192.168.0.0/24"}, + "192.168.0.0/24", + []string{"192.168.0.0/24"}, + "covering of equal networks", + }, + { + rnet.IPv4, + []string{"10.1.0.0/16"}, + "192.168.0.0/24", + nil, + "nothing", + }, + { + rnet.IPv4, + []string{"192.168.0.0/16", "192.168.0.0/24"}, + "192.168.0.0/25", + []string{"192.168.0.0/16", "192.168.0.0/24"}, + "multiple networks", + }, + { + rnet.IPv4, + []string{"192.168.0.0/16", "192.168.0.0/24", "192.168.0.0/25"}, + "192.168.0.1/32", + []string{"192.168.0.0/16", "192.168.0.0/24", "192.168.0.0/25"}, + "multiple networks 2", + }, + { + rnet.IPv4, + []string{"192.168.0.0/16"}, + "192.168.1.1/32", + []string{"192.168.0.0/16"}, + "leaf", + }, + { + rnet.IPv4, + []string{"0.0.0.0/0", "192.168.1.1/32"}, + "192.168.0.0/16", + []string{"0.0.0.0/0"}, + "leaf with root", + }, + { + rnet.IPv4, + []string{ + "0.0.0.0/0", "192.168.0.0/24", "192.168.1.1/32", + "10.1.0.0/16", "10.1.1.0/24", + }, + "192.168.0.0/16", + []string{"0.0.0.0/0"}, + "path not taken", + }, + { + rnet.IPv4, + []string{ + "192.168.0.0/16", + }, + "192.168.0.0/15", + nil, + "only masks different", + }, +} + func TestPrefixTrieCoveredNetworks(t *testing.T) { for _, tc := range coveredNetworkTests { t.Run(tc.name, func(t *testing.T) { @@ -492,6 +623,29 @@ func TestPrefixTrieCoveredNetworks(t *testing.T) { } } +func TestPrefixTrieCoveringNetworks(t *testing.T) { + for _, tc := range coveringNetworkTests { + t.Run(tc.name, func(t *testing.T) { + trie := newPrefixTree(tc.version) + for _, insert := range tc.inserts { + network := netip.MustParsePrefix(insert) + err := trie.Insert(NewBasicRangerEntry(network)) + assert.NoError(t, err) + } + var expectedEntries []RangerEntry + for _, network := range tc.networks { + net := netip.MustParsePrefix(network) + expectedEntries = append(expectedEntries, + NewBasicRangerEntry(net)) + } + snet := netip.MustParsePrefix(tc.search) + networks, err := trie.CoveringNetworks(snet) + assert.NoError(t, err) + assert.Equal(t, expectedEntries, networks) + }) + } +} + func TestTrieMemUsage(t *testing.T) { if testing.Short() { t.Skip("Skipping memory test in `-short` mode") diff --git a/version.go b/version.go index c0a272f..b270de2 100644 --- a/version.go +++ b/version.go @@ -61,11 +61,28 @@ func (v *versionedRanger) CoveredNetworks(network netip.Prefix) ([]RangerEntry, return ranger.CoveredNetworks(network) } +func (v *versionedRanger) CoveringNetworks(network netip.Prefix) ([]RangerEntry, error) { + ranger, err := v.getRangerForIP(network.Addr()) + if err != nil { + return nil, err + } + return ranger.CoveringNetworks(network) +} + // Len returns number of networks in ranger. func (v *versionedRanger) Len() int { return v.ipV4Ranger.Len() + v.ipV6Ranger.Len() } +// Len returns number of networks in ranger. +func (v *versionedRanger) Adjacent(network netip.Prefix) (RangerEntry, error) { + ranger, err := v.getRangerForIP(network.Addr()) + if err != nil { + return nil, err + } + return ranger.Adjacent(network) +} + func (v *versionedRanger) getRangerForIP(ip netip.Addr) (Ranger, error) { if ip.Is4() { return v.ipV4Ranger, nil