Skip to content

Commit 00a2086

Browse files
committed
Nets betweening and tests
- 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
1 parent 47b95ce commit 00a2086

File tree

2 files changed

+112
-48
lines changed

2 files changed

+112
-48
lines changed

net.go

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Net interface {
1717
Mask() net.IPMask
1818
String() string
1919
Version() int
20+
finalAddress() (net.IP, int)
2021
}
2122

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

34+
// AllNetsBetween takes two net.IPs as input and will return a slice of
35+
// netblocks spanning the range between them, inclusively, even if it must
36+
// return one or more single-address netblocks to do so
37+
func AllNetsBetween(a, b net.IP) ([]Net, error) {
38+
var lastNet Net
39+
if EffectiveVersion(a) == IP4Version {
40+
lastNet = Net4{}
41+
} else {
42+
lastNet = Net6{}
43+
}
44+
45+
var nets []Net
46+
47+
for {
48+
ipnet, tf, err := NewNetBetween(a, b)
49+
if err != nil {
50+
return nets, err
51+
}
52+
53+
nets = append(nets, ipnet)
54+
if tf {
55+
return nets, nil
56+
}
57+
58+
finalIP, _ := ipnet.finalAddress()
59+
if CompareIPs(finalIP, b) > 0 {
60+
return nets, nil
61+
}
62+
63+
if lastNet.IP() == nil {
64+
lastNet = ipnet
65+
} else if CompareIPs(ipnet.IP(), lastNet.IP()) > 0 {
66+
lastNet = ipnet
67+
} else {
68+
return nets, nil
69+
}
70+
71+
a = NextIP(finalIP)
72+
if CompareIPs(a, b) > 0 {
73+
return nets, nil
74+
}
75+
}
76+
}
77+
3378
// NewNetBetween takes two net.IP's as input and will return the largest
34-
// netblock that can fit between them (exclusive of the IP's themselves).
79+
// netblock that can fit between them inclusive of at least the first address.
3580
// If there is an exact fit it will set a boolean to true, otherwise the bool
3681
// will be false. If no fit can be found (probably because a >= b) an
37-
// ErrNoValidRange will be returned.
82+
// ErrNoValidRange will be returned
3883
func NewNetBetween(a, b net.IP) (Net, bool, error) {
39-
if CompareIPs(a, b) != -1 {
84+
if CompareIPs(a, b) == 1 { // != -1 {
4085
return nil, false, ErrNoValidRange
4186
}
4287

4388
if EffectiveVersion(a) != EffectiveVersion(b) {
4489
return nil, false, ErrNoValidRange
4590
}
4691

47-
return fitNetworkBetween(NextIP(a), PreviousIP(b), 1)
92+
return fitNetworkBetween(a, b, 1)
4893
}
4994

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

107-
va := CompareIPs(xnet.FirstAddress(), a)
108-
vb := CompareIPs(xnet.LastAddress(), b)
109-
if va >= 0 && vb < 0 {
110-
return xnet, false, nil
111-
}
152+
finalIP, _ := xnet.finalAddress()
153+
va := CompareIPs(xnet.IP(), a)
154+
vb := CompareIPs(finalIP, b)
112155
if va == 0 && vb == 0 {
113156
return xnet, true, nil
114157
}
158+
if va >= 0 && vb <= 0 {
159+
return xnet, false, nil
160+
}
115161
return fitNetworkBetween(a, b, mask+1)
116162
}
117163

net_test.go

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -42,75 +42,76 @@ func TestNewNet(t *testing.T) {
4242
}
4343

4444
var NewNetBetweenTests = []struct {
45-
start net.IP
46-
end net.IP
47-
xnet string
48-
exact bool
49-
err error
45+
start net.IP
46+
end net.IP
47+
xnet string
48+
exact bool
49+
err error
50+
netslen int
5051
}{
51-
{
52-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"),
53-
"192.168.1.0/24", false, nil,
54-
},
55-
{
52+
{ // 0
5653
net.ParseIP("192.168.0.255"), net.ParseIP("10.0.0.0"),
57-
"", false, ErrNoValidRange,
54+
"", false, ErrNoValidRange, 0,
5855
},
5956
{
6057
net.ParseIP("192.168.0.255"), net.ParseIP("2001:db8:0:1::"),
61-
"", false, ErrNoValidRange,
58+
"", false, ErrNoValidRange, 0,
6259
},
6360
{
6461
net.ParseIP("2001:db8:0:1::"), net.ParseIP("192.168.0.255"),
65-
"", false, ErrNoValidRange,
62+
"", false, ErrNoValidRange, 0,
6663
},
6764
{
68-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"),
69-
"", false, ErrNoValidRange,
65+
net.ParseIP("2001:db8:0:1::"), net.ParseIP("2001:db8::"),
66+
"", false, ErrNoValidRange, 0,
7067
},
7168
{
72-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.1"),
73-
"192.168.1.0/32", true, nil,
69+
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"),
70+
"192.168.0.255/32", true, nil, 1,
7471
},
75-
{
76-
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.2"),
77-
"192.168.1.1/32", true, nil,
72+
{ // 5
73+
net.ParseIP("2001:db8:0:1::"), net.ParseIP("2001:db8:0:1::"),
74+
"2001:db8:0:1::/128", true, nil, 1,
7875
},
7976
{
80-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"),
81-
"192.168.1.0/31", true, nil,
77+
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.2.0"),
78+
"192.168.1.0/24", false, nil, 2,
8279
},
8380
{
84-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.3"),
85-
"192.168.1.0/31", false, nil,
81+
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:2::"),
82+
"2001:db8:1::/48", false, nil, 2,
8683
},
8784
{
88-
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.3"),
89-
"192.168.1.0/30", true, nil,
85+
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.255"),
86+
"192.168.1.0/24", true, nil, 1,
9087
},
9188
{
92-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.4"),
93-
"192.168.1.0/30", false, nil,
89+
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:1:ffff:ffff:ffff:ffff:ffff"),
90+
"2001:db8:1::/48", true, nil, 1,
9491
},
95-
{
96-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.5"),
97-
"192.168.1.0/30", false, nil,
92+
{ // 10
93+
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"),
94+
"192.168.1.0/31", true, nil, 1,
9895
},
9996
{
100-
net.ParseIP("192.168.0.254"), net.ParseIP("192.168.2.0"),
101-
"192.168.0.255/32", false, nil,
97+
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:1::1"),
98+
"2001:db8:1::/127", true, nil, 1,
10299
},
103100
{
104-
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"),
105-
"192.168.1.0/24", false, nil,
101+
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"),
102+
"192.168.0.255/32", false, nil, 3,
106103
},
107104
{
108-
net.ParseIP("12.168.0.254"), net.ParseIP("12.168.0.255"),
109-
"12.168.0.254/32", true, nil,
105+
net.ParseIP("2001:db8:0:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:1::1"),
106+
"2001:db8:0:ffff:ffff:ffff:ffff:ffff/128", false, nil, 2,
110107
},
111108
{
112-
net.ParseIP("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:0:1::"),
113-
"2001:db8::/64", true, nil,
109+
net.ParseIP("10.0.0.0"), net.ParseIP("255.0.0.0"),
110+
"10.0.0.0/7", false, nil, 13,
111+
},
112+
{ // 15
113+
net.ParseIP("2001:db8::"), net.ParseIP("2001:db8:ffff:ffff:ffff:ffff:ffff::"),
114+
"2001:db8::/33", false, nil, 81,
114115
},
115116
}
116117

@@ -133,6 +134,23 @@ func TestNewNetBetween(t *testing.T) {
133134
}
134135
}
135136

137+
func TestAllNetsBetween(t *testing.T) {
138+
for i, tt := range NewNetBetweenTests {
139+
//t.Logf("[%d] nets between %s and %s", i, tt.start, tt.end)
140+
xnets, err := AllNetsBetween(tt.start, tt.end)
141+
//t.Logf("[%d] got %+v, '%v'", i, xnets, err)
142+
if e := compareErrors(err, tt.err); len(e) > 0 {
143+
t.Errorf("[%d] expected error '%v', got '%v'", i, tt.err, err)
144+
}
145+
if tt.err == nil {
146+
if len(xnets) != tt.netslen {
147+
t.Logf("[%d] AllNetsBetween(%s, %s) [%+v]", i, tt.start, tt.end, xnets)
148+
t.Errorf("[%d] expected %d networks, got %d", i, tt.netslen, len(xnets))
149+
}
150+
}
151+
}
152+
}
153+
136154
var ParseCIDRTests = []struct {
137155
s string
138156
xnet string

0 commit comments

Comments
 (0)