Skip to content

Commit

Permalink
Nets betweening and tests
Browse files Browse the repository at this point in the history
 - Fix NewNetBetween to be more predictable in cases where the resu;t is a
   single-address network

 - add AllNetsBetween which returns all the netblocks between two supplied
   addresses

 - improve the test cases to eliminate duplicatation and provide equivalent
   coverage for v4 and v6
  • Loading branch information
c-robinson committed Jan 6, 2024
1 parent 47b95ce commit 00a2086
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 48 deletions.
64 changes: 55 additions & 9 deletions net.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Net interface {
Mask() net.IPMask
String() string
Version() int
finalAddress() (net.IP, int)
}

// NewNet returns a new Net object containing ip at the specified masklen. In
Expand All @@ -30,21 +31,65 @@ func NewNet(ip net.IP, masklen int) Net {
return NewNet4(ip, masklen)
}

// AllNetsBetween takes two net.IPs as input and will return a slice of
// netblocks spanning the range between them, inclusively, even if it must
// return one or more single-address netblocks to do so
func AllNetsBetween(a, b net.IP) ([]Net, error) {
var lastNet Net
if EffectiveVersion(a) == IP4Version {
lastNet = Net4{}
} else {
lastNet = Net6{}
}

var nets []Net

for {
ipnet, tf, err := NewNetBetween(a, b)
if err != nil {
return nets, err
}

nets = append(nets, ipnet)
if tf {
return nets, nil
}

finalIP, _ := ipnet.finalAddress()
if CompareIPs(finalIP, b) > 0 {
return nets, nil
}

if lastNet.IP() == nil {
lastNet = ipnet
} else if CompareIPs(ipnet.IP(), lastNet.IP()) > 0 {
lastNet = ipnet
} else {
return nets, nil
}

a = NextIP(finalIP)
if CompareIPs(a, b) > 0 {
return nets, nil
}
}
}

// NewNetBetween takes two net.IP's as input and will return the largest
// netblock that can fit between them (exclusive of the IP's themselves).
// netblock that can fit between them inclusive of at least the first address.
// If there is an exact fit it will set a boolean to true, otherwise the bool
// will be false. If no fit can be found (probably because a >= b) an
// ErrNoValidRange will be returned.
// ErrNoValidRange will be returned
func NewNetBetween(a, b net.IP) (Net, bool, error) {
if CompareIPs(a, b) != -1 {
if CompareIPs(a, b) == 1 { // != -1 {
return nil, false, ErrNoValidRange
}

if EffectiveVersion(a) != EffectiveVersion(b) {
return nil, false, ErrNoValidRange
}

return fitNetworkBetween(NextIP(a), PreviousIP(b), 1)
return fitNetworkBetween(a, b, 1)
}

// ByNet implements sort.Interface for iplib.Net based on the
Expand Down Expand Up @@ -104,14 +149,15 @@ func fitNetworkBetween(a, b net.IP, mask int) (Net, bool, error) {
return NewNet(b, maskMax(b)), true, nil
}

va := CompareIPs(xnet.FirstAddress(), a)
vb := CompareIPs(xnet.LastAddress(), b)
if va >= 0 && vb < 0 {
return xnet, false, nil
}
finalIP, _ := xnet.finalAddress()
va := CompareIPs(xnet.IP(), a)
vb := CompareIPs(finalIP, b)
if va == 0 && vb == 0 {
return xnet, true, nil
}
if va >= 0 && vb <= 0 {
return xnet, false, nil
}
return fitNetworkBetween(a, b, mask+1)
}

Expand Down
96 changes: 57 additions & 39 deletions net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,75 +42,76 @@ func TestNewNet(t *testing.T) {
}

var NewNetBetweenTests = []struct {
start net.IP
end net.IP
xnet string
exact bool
err error
start net.IP
end net.IP
xnet string
exact bool
err error
netslen int
}{
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"),
"192.168.1.0/24", false, nil,
},
{
{ // 0
net.ParseIP("192.168.0.255"), net.ParseIP("10.0.0.0"),
"", false, ErrNoValidRange,
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("2001:db8:0:1::"),
"", false, ErrNoValidRange,
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("2001:db8:0:1::"), net.ParseIP("192.168.0.255"),
"", false, ErrNoValidRange,
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"),
"", false, ErrNoValidRange,
net.ParseIP("2001:db8:0:1::"), net.ParseIP("2001:db8::"),
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.1"),
"192.168.1.0/32", true, nil,
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"),
"192.168.0.255/32", true, nil, 1,
},
{
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.2"),
"192.168.1.1/32", true, nil,
{ // 5
net.ParseIP("2001:db8:0:1::"), net.ParseIP("2001:db8:0:1::"),
"2001:db8:0:1::/128", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"),
"192.168.1.0/31", true, nil,
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.2.0"),
"192.168.1.0/24", false, nil, 2,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.3"),
"192.168.1.0/31", false, nil,
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:2::"),
"2001:db8:1::/48", false, nil, 2,
},
{
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.3"),
"192.168.1.0/30", true, nil,
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.255"),
"192.168.1.0/24", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.4"),
"192.168.1.0/30", false, nil,
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:1:ffff:ffff:ffff:ffff:ffff"),
"2001:db8:1::/48", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.5"),
"192.168.1.0/30", false, nil,
{ // 10
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"),
"192.168.1.0/31", true, nil, 1,
},
{
net.ParseIP("192.168.0.254"), net.ParseIP("192.168.2.0"),
"192.168.0.255/32", false, nil,
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:1::1"),
"2001:db8:1::/127", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"),
"192.168.1.0/24", false, nil,
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"),
"192.168.0.255/32", false, nil, 3,
},
{
net.ParseIP("12.168.0.254"), net.ParseIP("12.168.0.255"),
"12.168.0.254/32", true, nil,
net.ParseIP("2001:db8:0:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:1::1"),
"2001:db8:0:ffff:ffff:ffff:ffff:ffff/128", false, nil, 2,
},
{
net.ParseIP("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:0:1::"),
"2001:db8::/64", true, nil,
net.ParseIP("10.0.0.0"), net.ParseIP("255.0.0.0"),
"10.0.0.0/7", false, nil, 13,
},
{ // 15
net.ParseIP("2001:db8::"), net.ParseIP("2001:db8:ffff:ffff:ffff:ffff:ffff::"),
"2001:db8::/33", false, nil, 81,
},
}

Expand All @@ -133,6 +134,23 @@ func TestNewNetBetween(t *testing.T) {
}
}

func TestAllNetsBetween(t *testing.T) {
for i, tt := range NewNetBetweenTests {
//t.Logf("[%d] nets between %s and %s", i, tt.start, tt.end)
xnets, err := AllNetsBetween(tt.start, tt.end)
//t.Logf("[%d] got %+v, '%v'", i, xnets, err)
if e := compareErrors(err, tt.err); len(e) > 0 {
t.Errorf("[%d] expected error '%v', got '%v'", i, tt.err, err)
}
if tt.err == nil {
if len(xnets) != tt.netslen {
t.Logf("[%d] AllNetsBetween(%s, %s) [%+v]", i, tt.start, tt.end, xnets)
t.Errorf("[%d] expected %d networks, got %d", i, tt.netslen, len(xnets))
}
}
}
}

var ParseCIDRTests = []struct {
s string
xnet string
Expand Down

0 comments on commit 00a2086

Please sign in to comment.