Skip to content

Commit

Permalink
Merge pull request #9 from yl2chen/yc-covered-networks-1
Browse files Browse the repository at this point in the history
Add CoveredNetworks option
  • Loading branch information
yl2chen authored Feb 5, 2018
2 parents 9a60958 + 67a2b5f commit 0517d71
Show file tree
Hide file tree
Showing 82 changed files with 17,841 additions and 1 deletion.
21 changes: 21 additions & 0 deletions brute.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cidranger

import (
"net"

rnet "github.com/yl2chen/cidranger/net"
)

// bruteRanger is a brute force implementation of Ranger. Insertion and
Expand Down Expand Up @@ -87,6 +89,25 @@ func (b *bruteRanger) ContainingNetworks(ip net.IP) ([]RangerEntry, error) {
return results, nil
}

// CoveredNetworks returns the list of RangerEntry(s) the given ipnet
// covers. That is, the networks that are completely subsumed by the
// specified network.
func (b *bruteRanger) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) {
entries, err := b.getEntriesByVersion(network.IP)
if err != nil {
return nil, err
}
var results []RangerEntry
testNetwork := rnet.NewNetwork(network)
for _, entry := range entries {
entryNetwork := rnet.NewNetwork(entry.Network())
if testNetwork.Covers(entryNetwork) {
results = append(results, entry)
}
}
return results, nil
}

func (b *bruteRanger) getEntriesByVersion(ip net.IP) (map[string]RangerEntry, error) {
if ip.To4() != nil {
return b.ipV4Entries, nil
Expand Down
31 changes: 31 additions & 0 deletions brute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cidranger

import (
"net"
"sort"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -144,3 +145,33 @@ func TestContainingNetworks(t *testing.T) {
})
}
}

func TestCoveredNetworks(t *testing.T) {
for _, tc := range coveredNetworkTests {
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.CoveredNetworks(*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)
})
}
}
1 change: 1 addition & 0 deletions cidranger.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type Ranger interface {
Remove(network net.IPNet) (RangerEntry, error)
Contains(ip net.IP) (bool, error)
ContainingNetworks(ip net.IP) ([]RangerEntry, error)
CoveredNetworks(network net.IPNet) ([]RangerEntry, error)
}

// NewPCTrieRanger returns a versionedRanger that supports both IPv4 and IPv6
Expand Down
44 changes: 44 additions & 0 deletions cidranger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func TestContainingNetworksAgaistBaseIPv4(t *testing.T) {
testContainingNetworksAgainstBase(t, 100000, randIPv4Gen)
}

func TestCoveredNetworksAgainstBaseIPv4(t *testing.T) {
testCoversNetworksAgainstBase(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.
Expand All @@ -37,6 +41,10 @@ func TestContainingNetworksAgaistBaseIPv6(t *testing.T) {
testContainingNetworksAgainstBase(t, 100000, curatedAWSIPv6Gen)
}

func TestCoveredNetworksAgainstBaseIPv6(t *testing.T) {
testCoversNetworksAgainstBase(t, 100000, randomIPNetGenFactory(ipV6AWSRangesIPNets))
}

func testContainsAgainstBase(t *testing.T, iterations int, ipGen ipGenerator) {
rangers := []Ranger{NewPCTrieRanger()}
baseRanger := newBruteRanger()
Expand Down Expand Up @@ -80,6 +88,29 @@ func testContainingNetworksAgainstBase(t *testing.T, iterations int, ipGen ipGen
}
}

func testCoversNetworksAgainstBase(t *testing.T, iterations int, netGen networkGenerator) {
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.CoveredNetworks(network.IPNet)
assert.NoError(t, err)
for _, ranger := range rangers {
actual, err := ranger.CoveredNetworks(network.IPNet)
assert.NoError(t, err)
assert.Equal(t, len(expected), len(actual))
for _, network := range actual {
assert.Contains(t, expected, network)
}
}
}
}

/*
******************************************************************
Benchmarks.
Expand Down Expand Up @@ -183,6 +214,14 @@ func curatedAWSIPv6Gen() rnet.NetworkNumber {
return nn
}

type networkGenerator func() rnet.Network

func randomIPNetGenFactory(pool []*net.IPNet) networkGenerator {
return func() rnet.Network {
return rnet.NewNetwork(*pool[rand.Intn(len(pool))])
}
}

type AWSRanges struct {
Prefixes []Prefix `json:"prefixes"`
IPv6Prefixes []IPv6Prefix `json:"ipv6_prefixes"`
Expand All @@ -201,6 +240,7 @@ type IPv6Prefix struct {
}

var awsRanges *AWSRanges
var ipV4AWSRangesIPNets []*net.IPNet
var ipV6AWSRangesIPNets []*net.IPNet

func loadAWSRanges() *AWSRanges {
Expand Down Expand Up @@ -235,5 +275,9 @@ func init() {
_, network, _ := net.ParseCIDR(prefix.IPPrefix)
ipV6AWSRangesIPNets = append(ipV6AWSRangesIPNets, network)
}
for _, prefix := range awsRanges.Prefixes {
_, network, _ := net.ParseCIDR(prefix.IPPrefix)
ipV4AWSRangesIPNets = append(ipV4AWSRangesIPNets, network)
}
rand.Seed(time.Now().Unix())
}
10 changes: 10 additions & 0 deletions net/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ func (n Network) Contains(nn NetworkNumber) bool {
return true
}

// Contains returns true if Network covers o, false otherwise
func (n Network) Covers(o Network) bool {
if len(n.Number) != len(o.Number) {
return false
}
nMaskSize, _ := n.IPNet.Mask.Size()
oMaskSize, _ := o.IPNet.Mask.Size()
return n.Contains(o.Number) && nMaskSize <= oMaskSize
}

// LeastCommonBitPosition returns the smallest position of the preceding common
// bits of the 2 networks, and returns an error ErrNoGreatestCommonBit
// if the two network number diverges from the first bit.
Expand Down
27 changes: 27 additions & 0 deletions net/ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,33 @@ func TestNetworkContainsVersionMismatch(t *testing.T) {
}
}

func TestNetworkCovers(t *testing.T) {
cases := []struct {
network string
covers string
result bool
name string
}{
{"10.0.0.0/24", "10.0.0.1/25", true, "contains"},
{"10.0.0.0/24", "11.0.0.1/25", false, "not contains"},
{"10.0.0.0/16", "10.0.0.0/15", false, "prefix false"},
{"10.0.0.0/15", "10.0.0.0/16", true, "prefix true"},
{"10.0.0.0/15", "10.0.0.0/15", true, "same"},
{"10::0/15", "10.0.0.0/15", false, "ip version mismatch"},
{"10::0/15", "10::0/16", true, "ipv6"},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, n, _ := net.ParseCIDR(tc.network)
network := NewNetwork(*n)
_, n, _ = net.ParseCIDR(tc.covers)
covers := NewNetwork(*n)
assert.Equal(t, tc.result, network.Covers(covers))
})
}
}

func TestNetworkLeastCommonBitPosition(t *testing.T) {
cases := []struct {
cidr1 string
Expand Down
27 changes: 27 additions & 0 deletions trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ func (p *prefixTrie) ContainingNetworks(ip net.IP) ([]RangerEntry, error) {
return p.containingNetworks(nn)
}

// CoveredNetworks returns the list of RangerEntry(s) the given ipnet
// covers. That is, the networks that are completely subsumed by the
// specified network.
func (p *prefixTrie) CoveredNetworks(network net.IPNet) ([]RangerEntry, error) {
net := rnet.NewNetwork(network)
return p.coveredNetworks(net)
}

// String returns string representation of trie, mainly for visualization and
// debugging.
func (p *prefixTrie) String() string {
Expand Down Expand Up @@ -176,6 +184,25 @@ func (p *prefixTrie) containingNetworks(number rnet.NetworkNumber) ([]RangerEntr
return results, nil
}

func (p *prefixTrie) coveredNetworks(network rnet.Network) ([]RangerEntry, error) {
var results []RangerEntry
if network.Covers(p.network) {
for entry := range p.walkDepth() {
results = append(results, entry)
}
} else if p.targetBitPosition() >= 0 {
bit, err := p.targetBitFromIP(network.Number)
if err != nil {
return results, err
}
child := p.children[bit]
if child != nil {
return child.coveredNetworks(network)
}
}
return results, nil
}

func (p *prefixTrie) insert(network rnet.Network, entry RangerEntry) error {
if p.network.Equal(network) {
p.entry = entry
Expand Down
95 changes: 95 additions & 0 deletions trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,98 @@ func TestPrefixTrieContainingNetworks(t *testing.T) {
})
}
}

type coveredNetworkTest struct {
version rnet.IPVersion
inserts []string
search string
networks []string
name string
}

var coveredNetworkTests = []coveredNetworkTest{
{
rnet.IPv4,
[]string{"192.168.0.0/24"},
"192.168.0.0/16",
[]string{"192.168.0.0/24"},
"basic covered networks",
},
{
rnet.IPv4,
[]string{"192.168.0.0/24"},
"10.1.0.0/16",
nil,
"nothing",
},
{
rnet.IPv4,
[]string{"192.168.0.0/24", "192.168.0.0/25"},
"192.168.0.0/16",
[]string{"192.168.0.0/24", "192.168.0.0/25"},
"multiple networks",
},
{
rnet.IPv4,
[]string{"192.168.0.0/24", "192.168.0.0/25", "192.168.0.1/32"},
"192.168.0.0/16",
[]string{"192.168.0.0/24", "192.168.0.0/25", "192.168.0.1/32"},
"multiple networks 2",
},
{
rnet.IPv4,
[]string{"192.168.1.1/32"},
"192.168.0.0/16",
[]string{"192.168.1.1/32"},
"leaf",
},
{
rnet.IPv4,
[]string{"0.0.0.0/0", "192.168.1.1/32"},
"192.168.0.0/16",
[]string{"192.168.1.1/32"},
"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{"192.168.0.0/24", "192.168.1.1/32"},
"path not taken",
},
{
rnet.IPv4,
[]string{
"192.168.0.0/15",
},
"192.168.0.0/16",
nil,
"only masks different",
},
}

func TestPrefixTrieCoveredNetworks(t *testing.T) {
for _, tc := range coveredNetworkTests {
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.CoveredNetworks(*snet)
assert.NoError(t, err)
assert.Equal(t, expectedEntries, networks)
})
}
}
1 change: 0 additions & 1 deletion vendor/github.com/stretchr/testify
Submodule testify deleted from 890a5c
24 changes: 24 additions & 0 deletions vendor/github.com/stretchr/testify/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions vendor/github.com/stretchr/testify/.travis.gofmt.sh

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0517d71

Please sign in to comment.