From 64086afd6637fcb92e6a645cf4fb04fc513bd322 Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Fri, 8 Oct 2021 12:41:22 +0300 Subject: [PATCH 1/7] Added CoveredByNetworks() feature --- brute.go | 18 ++++++++++++++++++ cidranger.go | 1 + cidranger_test.go | 30 ++++++++++++++++++++++++++++++ go.mod | 5 +---- go.sum | 4 ---- net/ip.go | 2 +- trie.go | 39 +++++++++++++++++++++++++++++++++++++++ version.go | 8 ++++++++ 8 files changed, 98 insertions(+), 9 deletions(-) diff --git a/brute.go b/brute.go index 37a68be..bdbfb7e 100644 --- a/brute.go +++ b/brute.go @@ -108,6 +108,24 @@ func (b *bruteRanger) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) return results, nil } +// CoveredByNetworks returns the list of RangerEntry(s) the given ipnet +// is covered. It's like ContainingNetworks() for ipnet. +func (b *bruteRanger) CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) { + entries, err := b.getEntriesByVersion(network.IP) + if err != nil { + return nil, err + } + 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) diff --git a/cidranger.go b/cidranger.go index 2e8f118..3c1421a 100644 --- a/cidranger.go +++ b/cidranger.go @@ -89,6 +89,7 @@ type Ranger interface { Contains(ip net.IP) (bool, error) ContainingNetworks(ip net.IP) ([]RangerEntry, error) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) + CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) Len() int } diff --git a/cidranger_test.go b/cidranger_test.go index c1c741e..764975f 100644 --- a/cidranger_test.go +++ b/cidranger_test.go @@ -30,6 +30,10 @@ func TestCoveredNetworksAgainstBaseIPv4(t *testing.T) { testCoversNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV4AWSRangesIPNets)) } +func TestCoveredByNetworksAgainstBaseIPv4(t *testing.T) { + testCoveredByNetworksAgainstBase(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. @@ -120,6 +124,32 @@ func testCoversNetworksAgainstBase(t *testing.T, iterations int, netGen networkG } } +func testCoveredByNetworksAgainstBase(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.CoveredByNetworks(network.IPNet) + assert.NoError(t, err) + for _, ranger := range rangers { + actual, err := ranger.CoveredByNetworks(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 a35ea91..1d5a9aa 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module github.com/yl2chen/cidranger go 1.13 -require ( - github.com/stretchr/testify v1.6.1 - gopkg.in/yaml.v2 v2.2.2 // indirect -) +require github.com/stretchr/testify v1.6.1 diff --git a/go.sum b/go.sum index d063842..afe7890 100644 --- a/go.sum +++ b/go.sum @@ -3,13 +3,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/net/ip.go b/net/ip.go index 75cb356..6f38e2e 100644 --- a/net/ip.go +++ b/net/ip.go @@ -209,7 +209,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/trie.go b/trie.go index 31976bd..ec55bb6 100644 --- a/trie.go +++ b/trie.go @@ -124,6 +124,13 @@ func (p *prefixTrie) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) { return p.coveredNetworks(net) } +// CoveredByNetworks returns the list of RangerEntry(s) the given ipnet +// is covered. It's like ContainingNetworks() for ipnet. +func (p *prefixTrie) CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) { + net := rnet.NewNetwork(network) + return p.coveredByNetworks(net) +} + // Len returns number of networks in ranger. func (p *prefixTrie) Len() int { return p.size @@ -217,6 +224,38 @@ func (p *prefixTrie) coveredNetworks(network rnet.Network) ([]RangerEntry, error return results, nil } +func (p *prefixTrie) coveredByNetworks(network rnet.Network) ([]RangerEntry, error) { + 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.coveredByNetworks(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/version.go b/version.go index 2c3fe2b..257d9b1 100644 --- a/version.go +++ b/version.go @@ -61,6 +61,14 @@ func (v *versionedRanger) CoveredNetworks(network net.IPNet) ([]RangerEntry, err return ranger.CoveredNetworks(network) } +func (v *versionedRanger) CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) { + ranger, err := v.getRangerForIP(network.IP) + if err != nil { + return nil, err + } + return ranger.CoveredByNetworks(network) +} + // Len returns number of networks in ranger. func (v *versionedRanger) Len() int { return v.ipV4Ranger.Len() + v.ipV6Ranger.Len() From 377e0c3c40cb49312e5f550610938f79c624152d Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Fri, 8 Oct 2021 12:53:31 +0300 Subject: [PATCH 2/7] modified go.mod file --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1d5a9aa..ff47d66 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/yl2chen/cidranger +module github.com/Ramzeth/cidranger go 1.13 From 3f2bd2eac99970fcb2fc7b66f01c0e0c3070f57d Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Tue, 26 Oct 2021 16:21:50 +0300 Subject: [PATCH 3/7] Rename methods to "Covering" instead of "CoveredBy". Added more tests. Fix minor bug. --- brute.go | 6 ++-- brute_test.go | 30 ++++++++++++++++ cidranger.go | 2 +- cidranger_test.go | 14 +++++--- go.mod | 7 ++-- go.sum | 4 +++ trie.go | 14 ++++---- trie_test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++-- version.go | 4 +-- 9 files changed, 150 insertions(+), 22 deletions(-) diff --git a/brute.go b/brute.go index bdbfb7e..0094fd2 100644 --- a/brute.go +++ b/brute.go @@ -108,14 +108,14 @@ func (b *bruteRanger) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) return results, nil } -// CoveredByNetworks returns the list of RangerEntry(s) the given ipnet +// Covering returns the list of RangerEntry(s) the given ipnet // is covered. It's like ContainingNetworks() for ipnet. -func (b *bruteRanger) CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) { +func (b *bruteRanger) CoveringNetworks(network net.IPNet) ([]RangerEntry, error) { entries, err := b.getEntriesByVersion(network.IP) if err != nil { return nil, err } - results := []RangerEntry{} + var results []RangerEntry testNetwork := rnet.NewNetwork(network) for _, entry := range entries { entryNetwork := rnet.NewNetwork(entry.Network()) diff --git a/brute_test.go b/brute_test.go index 71ee637..846ac3d 100644 --- a/brute_test.go +++ b/brute_test.go @@ -175,3 +175,33 @@ 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, _ := net.ParseCIDR(insert) + err := ranger.Insert(NewBasicRangerEntry(*network)) + assert.NoError(t, err) + } + var expectedEntries []string + for _, network := range tc.networks { + expectedEntries = append(expectedEntries, network) + } + sort.Strings(expectedEntries) + _, snet, _ := net.ParseCIDR(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 3c1421a..04220c5 100644 --- a/cidranger.go +++ b/cidranger.go @@ -89,7 +89,7 @@ type Ranger interface { Contains(ip net.IP) (bool, error) ContainingNetworks(ip net.IP) ([]RangerEntry, error) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) - CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) + CoveringNetworks(network net.IPNet) ([]RangerEntry, error) Len() int } diff --git a/cidranger_test.go b/cidranger_test.go index 764975f..d2c4d8b 100644 --- a/cidranger_test.go +++ b/cidranger_test.go @@ -30,8 +30,8 @@ func TestCoveredNetworksAgainstBaseIPv4(t *testing.T) { testCoversNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV4AWSRangesIPNets)) } -func TestCoveredByNetworksAgainstBaseIPv4(t *testing.T) { - testCoveredByNetworksAgainstBase(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 @@ -49,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") @@ -124,7 +128,7 @@ func testCoversNetworksAgainstBase(t *testing.T, iterations int, netGen networkG } } -func testCoveredByNetworksAgainstBase(t *testing.T, iterations int, netGen networkGenerator) { +func testCoveringNetworksAgainstBase(t *testing.T, iterations int, netGen networkGenerator) { if testing.Short() { t.Skip("Skipping memory test in `-short` mode") } @@ -137,10 +141,10 @@ func testCoveredByNetworksAgainstBase(t *testing.T, iterations int, netGen netwo for i := 0; i < iterations; i++ { network := netGen() - expected, err := baseRanger.CoveredByNetworks(network.IPNet) + expected, err := baseRanger.CoveringNetworks(network.IPNet) assert.NoError(t, err) for _, ranger := range rangers { - actual, err := ranger.CoveredByNetworks(network.IPNet) + actual, err := ranger.CoveringNetworks(network.IPNet) assert.NoError(t, err) assert.Equal(t, len(expected), len(actual)) for _, network := range actual { diff --git a/go.mod b/go.mod index ff47d66..9a1e835 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ -module github.com/Ramzeth/cidranger +module github.com/yl2chen/cidranger go 1.13 -require github.com/stretchr/testify v1.6.1 +require ( + github.com/stretchr/testify v1.6.1 + gopkg.in/yaml.v2 v2.2.2 // indirect +) \ No newline at end of file diff --git a/go.sum b/go.sum index afe7890..84e2efa 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,13 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= +github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/trie.go b/trie.go index ec55bb6..c2aff89 100644 --- a/trie.go +++ b/trie.go @@ -124,11 +124,11 @@ func (p *prefixTrie) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) { return p.coveredNetworks(net) } -// CoveredByNetworks returns the list of RangerEntry(s) the given ipnet -// is covered. It's like ContainingNetworks() for ipnet. -func (p *prefixTrie) CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) { +// Covering returns the list of RangerEntry(s) the given ipnet +// is covered by. It's like ContainingNetworks() for ipnet. +func (p *prefixTrie) CoveringNetworks(network net.IPNet) ([]RangerEntry, error) { net := rnet.NewNetwork(network) - return p.coveredByNetworks(net) + return p.coveringNetworks(net) } // Len returns number of networks in ranger. @@ -224,8 +224,8 @@ func (p *prefixTrie) coveredNetworks(network rnet.Network) ([]RangerEntry, error return results, nil } -func (p *prefixTrie) coveredByNetworks(network rnet.Network) ([]RangerEntry, error) { - results := []RangerEntry{} +func (p *prefixTrie) coveringNetworks(network rnet.Network) ([]RangerEntry, error) { + var results []RangerEntry if !p.network.Covers(network) { return results, nil } @@ -241,7 +241,7 @@ func (p *prefixTrie) coveredByNetworks(network rnet.Network) ([]RangerEntry, err } child := p.children[bit] if child != nil { - ranges, err := child.coveredByNetworks(network) + ranges, err := child.coveringNetworks(network) if err != nil { return nil, err } diff --git a/trie_test.go b/trie_test.go index 04f2900..2d6d336 100644 --- a/trie_test.go +++ b/trie_test.go @@ -397,7 +397,7 @@ func TestPrefixTrieContainingNetworks(t *testing.T) { } } -type coveredNetworkTest struct { +type networkTest struct { version rnet.IPVersion inserts []string search string @@ -405,7 +405,7 @@ type coveredNetworkTest struct { name string } -var coveredNetworkTests = []coveredNetworkTest{ +var coveredNetworkTests = []networkTest{ { rnet.IPv4, []string{"192.168.0.0/24"}, @@ -469,6 +469,70 @@ 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{"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 +556,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, _ := net.ParseCIDR(insert) + err := trie.Insert(NewBasicRangerEntry(*network)) + assert.NoError(t, err) + } + var expectedEntries []RangerEntry + for _, network := range tc.networks { + _, net, _ := net.ParseCIDR(network) + expectedEntries = append(expectedEntries, + NewBasicRangerEntry(*net)) + } + _, snet, _ := net.ParseCIDR(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 257d9b1..206290c 100644 --- a/version.go +++ b/version.go @@ -61,12 +61,12 @@ func (v *versionedRanger) CoveredNetworks(network net.IPNet) ([]RangerEntry, err return ranger.CoveredNetworks(network) } -func (v *versionedRanger) CoveredByNetworks(network net.IPNet) ([]RangerEntry, error) { +func (v *versionedRanger) CoveringNetworks(network net.IPNet) ([]RangerEntry, error) { ranger, err := v.getRangerForIP(network.IP) if err != nil { return nil, err } - return ranger.CoveredByNetworks(network) + return ranger.CoveringNetworks(network) } // Len returns number of networks in ranger. From 3ee8b3708a83d90da80b2b6b200e3aafeaf42401 Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Tue, 26 Oct 2021 16:49:10 +0300 Subject: [PATCH 4/7] go.mod changed --- go.mod | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 9a1e835..8418dda 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ -module github.com/yl2chen/cidranger +module github.com/Ramzeth/cidranger -go 1.13 +go 1.17 require ( - github.com/stretchr/testify v1.6.1 - gopkg.in/yaml.v2 v2.2.2 // indirect -) \ No newline at end of file + github.com/davecgh/go-spew v1.1.0 + github.com/pmezard/go-difflib v1.0.0 + github.com/stretchr/testify v1.2.1 +) From 5f323575655c02a5e58cf8c4d6d8e3a9629df0b6 Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Wed, 19 Jan 2022 12:20:43 +0300 Subject: [PATCH 5/7] Added "Adjacient" networks entry search --- brute.go | 5 +++++ cidranger.go | 1 + cidranger_test.go | 2 +- go.mod | 13 ++++++++++--- go.sum | 14 +++++++++----- net/ip.go | 16 ++++++++++++++++ net/ip_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++++ trie.go | 33 +++++++++++++++++++++++++++++++- trie_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++- version.go | 11 ++++++++++- 10 files changed, 178 insertions(+), 12 deletions(-) diff --git a/brute.go b/brute.go index 0094fd2..30e9794 100644 --- a/brute.go +++ b/brute.go @@ -140,3 +140,8 @@ func (b *bruteRanger) getEntriesByVersion(ip net.IP) (map[string]RangerEntry, er } return nil, ErrInvalidNetworkInput } + +// Just to complete interface +func (p *bruteRanger) Adjacient(network net.IPNet) (RangerEntry, error) { + return nil, nil +} diff --git a/cidranger.go b/cidranger.go index 04220c5..0590a83 100644 --- a/cidranger.go +++ b/cidranger.go @@ -90,6 +90,7 @@ type Ranger interface { ContainingNetworks(ip net.IP) ([]RangerEntry, error) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) CoveringNetworks(network net.IPNet) ([]RangerEntry, error) + Adjacient(network net.IPNet) (RangerEntry, error) Len() int } diff --git a/cidranger_test.go b/cidranger_test.go index d2c4d8b..9523e3a 100644 --- a/cidranger_test.go +++ b/cidranger_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + rnet "github.com/Ramzeth/cidranger/net" "github.com/stretchr/testify/assert" - rnet "github.com/yl2chen/cidranger/net" ) /* diff --git a/go.mod b/go.mod index 8418dda..ed1524f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,14 @@ module github.com/Ramzeth/cidranger go 1.17 require ( - github.com/davecgh/go-spew v1.1.0 - github.com/pmezard/go-difflib v1.0.0 - github.com/stretchr/testify v1.2.1 + github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.4.0 + github.com/yl2chen/cidranger v1.0.2 +) + +require ( + 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-20191026070338-33540a1f6037 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index 84e2efa..552e5fe 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,19 @@ -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.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -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/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/net/ip.go b/net/ip.go index 6f38e2e..fad9b28 100644 --- a/net/ip.go +++ b/net/ip.go @@ -88,6 +88,8 @@ func (n NetworkNumber) ToIP() net.IP { } if len(ip) == net.IPv4len { ip = net.IPv4(ip[0], ip[1], ip[2], ip[3]) + // Convert to 4-byte value, because parseCIDR returns 4-byte IPv4 address - https://github.com/golang/go/issues/41214 + ip = ip.To4() } return ip } @@ -144,6 +146,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. diff --git a/net/ip_test.go b/net/ip_test.go index 1e915df..dd6cf9c 100644 --- a/net/ip_test.go +++ b/net/ip_test.go @@ -1,6 +1,7 @@ package net import ( + "errors" "math" "net" "testing" @@ -75,6 +76,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(net.ParseIP("192.168.0.0")), + 8, + // 192.168.1.0 + NetworkNumber{0b11000000101010000000000100000000}, + "Flip bit 8", + nil, + }, + { + NewNetworkNumber(net.ParseIP("128.0.0.1")), + 31, + // 0.0.0.1 + NetworkNumber{0b00000000000000000000000000000001}, + "Flip bit 31", + nil, + }, + { + NewNetworkNumber(net.ParseIP("128.0.0.1")), + 32, + // 0.0.0.1 + NetworkNumber{0b00000000000000000000000000000001}, + "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 c2aff89..c73401b 100644 --- a/trie.go +++ b/trie.go @@ -5,7 +5,7 @@ import ( "net" "strings" - rnet "github.com/yl2chen/cidranger/net" + rnet "github.com/Ramzeth/cidranger/net" ) // prefixTrie is a path-compressed (PC) trie implementation of the @@ -152,6 +152,37 @@ func (p *prefixTrie) String() string { p.targetBitPosition(), p.hasEntry(), strings.Join(children, "")) } +// Returns adjacient entries to entry, identifiend by given network. if adjacient exists. +func (p *prefixTrie) Adjacient(network net.IPNet) (RangerEntry, error) { + adjacientNumber := rnet.NewNetworkNumber(network.IP) + ones, size := network.Mask.Size() + position := size - ones + err := adjacientNumber.FlipNthBit(uint(position)) + if err != nil { + return nil, err + } + adjacientNet := rnet.NewNetwork(net.IPNet{adjacientNumber.ToIP(), network.Mask}) + return p.adjacient(adjacientNet) +} + +func (p *prefixTrie) adjacient(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.adjacient(network) + } + return nil, nil +} + func (p *prefixTrie) contains(number rnet.NetworkNumber) (bool, error) { if !p.network.Contains(number) { return false, nil diff --git a/trie_test.go b/trie_test.go index 2d6d336..1b16d5b 100644 --- a/trie_test.go +++ b/trie_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" + rnet "github.com/Ramzeth/cidranger/net" + log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" - rnet "github.com/yl2chen/cidranger/net" ) func getAllByVersion(version rnet.IPVersion) *net.IPNet { @@ -397,6 +398,51 @@ func TestPrefixTrieContainingNetworks(t *testing.T) { } } +func TestPrefixTrie_Adjacient(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 adjacient", + }, + { + rnet.IPv4, + []string{"128.0.0.5/1"}, + "0.0.5.0/1", + "128.0.0.0/1", + "check /1 adjacient, with some bits in ip address range", + }, + } + 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, _ := net.ParseCIDR(insert) + err := trie.Insert(NewBasicRangerEntry(*network)) + assert.NoError(t, err) + } + _, testNet, _ := net.ParseCIDR(tc.network) + entry, err := trie.Adjacient(*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 diff --git a/version.go b/version.go index 206290c..a091202 100644 --- a/version.go +++ b/version.go @@ -3,7 +3,7 @@ package cidranger import ( "net" - rnet "github.com/yl2chen/cidranger/net" + rnet "github.com/Ramzeth/cidranger/net" ) type rangerFactory func(rnet.IPVersion) Ranger @@ -74,6 +74,15 @@ func (v *versionedRanger) Len() int { return v.ipV4Ranger.Len() + v.ipV6Ranger.Len() } +// Len returns number of networks in ranger. +func (v *versionedRanger) Adjacient(network net.IPNet) (RangerEntry, error) { + ranger, err := v.getRangerForIP(network.IP) + if err != nil { + return nil, err + } + return ranger.Adjacient(network) +} + func (v *versionedRanger) getRangerForIP(ip net.IP) (Ranger, error) { if ip.To4() != nil { return v.ipV4Ranger, nil From b9bee7176e2fc5075e4b774c386390307f20b48a Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Wed, 19 Jan 2022 12:51:23 +0300 Subject: [PATCH 6/7] Fix typo in word adjacent --- brute.go | 2 +- cidranger.go | 2 +- trie.go | 18 ++++++++++-------- trie_test.go | 8 ++++---- version.go | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/brute.go b/brute.go index 30e9794..2b060b9 100644 --- a/brute.go +++ b/brute.go @@ -142,6 +142,6 @@ func (b *bruteRanger) getEntriesByVersion(ip net.IP) (map[string]RangerEntry, er } // Just to complete interface -func (p *bruteRanger) Adjacient(network net.IPNet) (RangerEntry, error) { +func (p *bruteRanger) Adjacent(network net.IPNet) (RangerEntry, error) { return nil, nil } diff --git a/cidranger.go b/cidranger.go index 0590a83..72bd4bc 100644 --- a/cidranger.go +++ b/cidranger.go @@ -90,7 +90,7 @@ type Ranger interface { ContainingNetworks(ip net.IP) ([]RangerEntry, error) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) CoveringNetworks(network net.IPNet) ([]RangerEntry, error) - Adjacient(network net.IPNet) (RangerEntry, error) + Adjacent(network net.IPNet) (RangerEntry, error) Len() int } diff --git a/trie.go b/trie.go index c73401b..42d4be2 100644 --- a/trie.go +++ b/trie.go @@ -152,20 +152,22 @@ func (p *prefixTrie) String() string { p.targetBitPosition(), p.hasEntry(), strings.Join(children, "")) } -// Returns adjacient entries to entry, identifiend by given network. if adjacient exists. -func (p *prefixTrie) Adjacient(network net.IPNet) (RangerEntry, error) { - adjacientNumber := rnet.NewNetworkNumber(network.IP) +// 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 net.IPNet) (RangerEntry, error) { + adjacentNumber := rnet.NewNetworkNumber(network.IP) ones, size := network.Mask.Size() position := size - ones - err := adjacientNumber.FlipNthBit(uint(position)) + err := adjacentNumber.FlipNthBit(uint(position)) if err != nil { return nil, err } - adjacientNet := rnet.NewNetwork(net.IPNet{adjacientNumber.ToIP(), network.Mask}) - return p.adjacient(adjacientNet) + adjacentNet := rnet.NewNetwork(net.IPNet{adjacentNumber.ToIP(), network.Mask}) + return p.adjacent(adjacentNet) } -func (p *prefixTrie) adjacient(network rnet.Network) (RangerEntry, error) { +func (p *prefixTrie) adjacent(network rnet.Network) (RangerEntry, error) { if p.hasEntry() && p.network.Equal(network) { return p.entry, nil } @@ -178,7 +180,7 @@ func (p *prefixTrie) adjacient(network rnet.Network) (RangerEntry, error) { } child := p.children[bit] if child != nil { - return child.adjacient(network) + return child.adjacent(network) } return nil, nil } diff --git a/trie_test.go b/trie_test.go index 1b16d5b..56b9e78 100644 --- a/trie_test.go +++ b/trie_test.go @@ -398,7 +398,7 @@ func TestPrefixTrieContainingNetworks(t *testing.T) { } } -func TestPrefixTrie_Adjacient(t *testing.T) { +func TestPrefixTrie_Adjacent(t *testing.T) { cases := []struct { version rnet.IPVersion inserts []string @@ -411,14 +411,14 @@ func TestPrefixTrie_Adjacient(t *testing.T) { []string{"192.168.0.0/24"}, "192.168.1.0/24", "192.168.0.0/24", - "check /24 adjacient", + "check /24 adjacent", }, { rnet.IPv4, []string{"128.0.0.5/1"}, "0.0.5.0/1", "128.0.0.0/1", - "check /1 adjacient, with some bits in ip address range", + "check /1 adjacent, with some bits in ip address range", }, } log.SetLevel(log.TraceLevel) @@ -431,7 +431,7 @@ func TestPrefixTrie_Adjacient(t *testing.T) { assert.NoError(t, err) } _, testNet, _ := net.ParseCIDR(tc.network) - entry, err := trie.Adjacient(*testNet) + entry, err := trie.Adjacent(*testNet) assert.NoError(t, err) netString := "" if entry != nil { diff --git a/version.go b/version.go index a091202..0a828c6 100644 --- a/version.go +++ b/version.go @@ -75,12 +75,12 @@ func (v *versionedRanger) Len() int { } // Len returns number of networks in ranger. -func (v *versionedRanger) Adjacient(network net.IPNet) (RangerEntry, error) { +func (v *versionedRanger) Adjacent(network net.IPNet) (RangerEntry, error) { ranger, err := v.getRangerForIP(network.IP) if err != nil { return nil, err } - return ranger.Adjacient(network) + return ranger.Adjacent(network) } func (v *versionedRanger) getRangerForIP(ip net.IP) (Ranger, error) { From 8c031e836d42dfa645b927cf9374dad0f6a26329 Mon Sep 17 00:00:00 2001 From: Ramzeth Date: Fri, 21 Jan 2022 16:04:53 +0300 Subject: [PATCH 7/7] Fix in adjacent position check --- trie.go | 4 ++++ trie_test.go | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/trie.go b/trie.go index 42d4be2..6e9bffd 100644 --- a/trie.go +++ b/trie.go @@ -158,6 +158,10 @@ func (p *prefixTrie) String() string { func (p *prefixTrie) Adjacent(network net.IPNet) (RangerEntry, error) { adjacentNumber := rnet.NewNetworkNumber(network.IP) ones, size := network.Mask.Size() + 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 err := adjacentNumber.FlipNthBit(uint(position)) if err != nil { diff --git a/trie_test.go b/trie_test.go index 56b9e78..b2d36fb 100644 --- a/trie_test.go +++ b/trie_test.go @@ -420,6 +420,13 @@ func TestPrefixTrie_Adjacent(t *testing.T) { "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 { @@ -459,6 +466,13 @@ var coveredNetworkTests = []networkTest{ []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"}, @@ -523,6 +537,13 @@ var coveringNetworkTests = []networkTest{ []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"},