Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.

Commit 6bdf9a5

Browse files
smikulcikHenryGessau
authored andcommitted
Add IPRanges (#14)
* Add IPRanges * Updates from Carl's review * Address Carl's Review
1 parent 502ac7e commit 6bdf9a5

File tree

4 files changed

+249
-4
lines changed

4 files changed

+249
-4
lines changed

iprange.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package netaddr
2+
3+
import (
4+
"fmt"
5+
"net"
6+
)
7+
8+
// IPRange range of ips not necessarily aligned to a power of 2
9+
type IPRange struct {
10+
First, Last net.IP
11+
}
12+
13+
func (r *IPRange) String() string {
14+
return fmt.Sprintf("[%s,%s]", r.First, r.Last)
15+
}
16+
17+
// IPRangeFromIPNet get an IPRange from an *ip.Net
18+
func IPRangeFromIPNet(cidr *net.IPNet) *IPRange {
19+
return &IPRange{
20+
First: NetworkAddr(cidr),
21+
Last: BroadcastAddr(cidr),
22+
}
23+
}
24+
25+
// Minus returns the ranges in r that are not in b
26+
func (r *IPRange) Minus(b *IPRange) []*IPRange {
27+
diff := []*IPRange{}
28+
if IPLessThan(r.First, b.First) {
29+
diff = append(diff, &IPRange{First: r.First, Last: IPMin(r.Last, decrementIP(b.First))})
30+
}
31+
32+
if IPLessThan(b.Last, r.Last) {
33+
diff = append(diff, &IPRange{First: IPMax(r.First, incrementIP(b.Last)), Last: r.Last})
34+
}
35+
return diff
36+
}
37+
38+
// Contains returns true if b is contained in r
39+
func (r *IPRange) Contains(b *IPRange) bool {
40+
if (IPLessThan(r.First, b.First) || r.First.Equal(b.First)) &&
41+
(IPLessThan(b.Last, r.Last) || r.Last.Equal(b.Last)) {
42+
return true
43+
}
44+
return false
45+
}

iprange_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package netaddr
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestIPRangeFromIPNet(t *testing.T) {
11+
net, err := ParseNet("10.0.0.0/8")
12+
assert.Nil(t, err)
13+
14+
ipRange := IPRangeFromIPNet(net)
15+
16+
assert.Equal(t, "10.0.0.0", ipRange.First.String())
17+
assert.Equal(t, "10.255.255.255", ipRange.Last.String())
18+
assert.Equal(t, "[10.0.0.0,10.255.255.255]", ipRange.String())
19+
}
20+
21+
func TestIPRangeFromIPNetIPv6(t *testing.T) {
22+
net, err := ParseNet("2001::/16")
23+
assert.Nil(t, err)
24+
25+
ipRange := IPRangeFromIPNet(net)
26+
27+
assert.Equal(t, "2001::", ipRange.First.String())
28+
assert.Equal(t, "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ipRange.Last.String())
29+
assert.Equal(t, "[2001::,2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff]", ipRange.String())
30+
}
31+
32+
func TestIPRangeDifference(t *testing.T) {
33+
for _, tc := range []struct {
34+
a, b *IPRange
35+
result string
36+
}{
37+
// Give ranges [10.0.0.0-10.0.0.25], try every overlap case {9.0.0.0, 10.0.0.0, 10.0.0.24, 10.0.0.255, 11.0.0.0}
38+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("9.0.0.0")}, "[[10.0.0.0,10.0.0.255]]"},
39+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("10.0.0.0")}, "[[10.0.0.1,10.0.0.255]]"},
40+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("10.0.0.24")}, "[[10.0.0.25,10.0.0.255]]"},
41+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("10.0.0.255")}, "[]"},
42+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("11.0.0.0")}, "[]"},
43+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.0")}, "[[10.0.0.1,10.0.0.255]]"},
44+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.24")}, "[[10.0.0.25,10.0.0.255]]"},
45+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, "[]"},
46+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("11.0.0.0")}, "[]"},
47+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.24"), ParseIP("10.0.0.24")}, "[[10.0.0.0,10.0.0.23] [10.0.0.25,10.0.0.255]]"},
48+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.24"), ParseIP("10.0.0.255")}, "[[10.0.0.0,10.0.0.23]]"},
49+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.24"), ParseIP("11.0.0.0")}, "[[10.0.0.0,10.0.0.23]]"},
50+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.255"), ParseIP("10.0.0.255")}, "[[10.0.0.0,10.0.0.254]]"},
51+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.255"), ParseIP("11.0.0.0")}, "[[10.0.0.0,10.0.0.254]]"},
52+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("11.0.0.0"), ParseIP("11.0.0.0")}, "[[10.0.0.0,10.0.0.255]]"},
53+
54+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.0")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.0")}, "[]"},
55+
} {
56+
diff := tc.a.Minus(tc.b)
57+
assert.Equal(t, tc.result, fmt.Sprintf("%s", diff))
58+
}
59+
}
60+
61+
func TestIPRangeContains(t *testing.T) {
62+
for i, tc := range []struct {
63+
a, b *IPRange
64+
result bool
65+
}{
66+
// Give ranges [10.0.0.0-10.0.0.25], try every overlap case {9.0.0.0, 10.0.0.0, 10.0.0.24, 10.0.0.255, 11.0.0.0}
67+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("9.0.0.0")}, false},
68+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("10.0.0.0")}, false},
69+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("10.0.0.24")}, false},
70+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("10.0.0.255")}, false},
71+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("9.0.0.0"), ParseIP("11.0.0.0")}, false},
72+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.0")}, true},
73+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.24")}, true},
74+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, true},
75+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("11.0.0.0")}, false},
76+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.24"), ParseIP("10.0.0.24")}, true},
77+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.24"), ParseIP("10.0.0.255")}, true},
78+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.24"), ParseIP("11.0.0.0")}, false},
79+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.255"), ParseIP("10.0.0.255")}, true},
80+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("10.0.0.255"), ParseIP("11.0.0.0")}, false},
81+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.255")}, &IPRange{ParseIP("11.0.0.0"), ParseIP("11.0.0.0")}, false},
82+
83+
{&IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.0")}, &IPRange{ParseIP("10.0.0.0"), ParseIP("10.0.0.0")}, true},
84+
} {
85+
ret := tc.a.Contains(tc.b)
86+
if !assert.Equal(t, tc.result, ret) {
87+
t.Logf("Test %d failed: a: %s b: %s was %v and was supposed to be %v", i, tc.a, tc.b, ret, tc.result)
88+
}
89+
}
90+
}

net_utils.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,7 @@ func ipToNet(ip net.IP) *net.IPNet {
196196

197197
// incrementIP returns the given IP + 1
198198
func incrementIP(ip net.IP) (result net.IP) {
199-
result = net.ParseIP("::")
200-
if len(ip) == 4 {
201-
result = net.ParseIP("0.0.0.0").To4()
202-
}
199+
result = make([]byte, len(ip)) // start off with a nice empty ip of proper length
203200

204201
carry := true
205202
for i := len(ip) - 1; i >= 0; i-- {
@@ -214,6 +211,23 @@ func incrementIP(ip net.IP) (result net.IP) {
214211
return
215212
}
216213

214+
// decrementIP returns the given IP - 1
215+
func decrementIP(ip net.IP) (result net.IP) {
216+
result = make([]byte, len(ip)) // start off with a nice empty ip of proper length
217+
218+
borrow := true
219+
for i := len(ip) - 1; i >= 0; i-- {
220+
result[i] = ip[i]
221+
if borrow {
222+
result[i]--
223+
if result[i] != 255 { // if we overflowed, we'd end up here
224+
borrow = false
225+
}
226+
}
227+
}
228+
return
229+
}
230+
217231
// expandNet returns a slice containing all of the IPs in the given net up to
218232
// the given limit
219233
func expandNet(n *net.IPNet, limit int) []net.IP {
@@ -235,3 +249,39 @@ func expandNet(n *net.IPNet, limit int) []net.IP {
235249
}
236250
return result
237251
}
252+
253+
// IPLessThan compare two ip addresses true
254+
// ordered by ipv4 first, then ipv6 later
255+
// then by section left-most is most significant
256+
// e.g.
257+
// 10.0.0.0
258+
// 10.0.0.1
259+
// 192.169.0.1
260+
// 2001:db8::
261+
func IPLessThan(a, b net.IP) bool {
262+
if len(a) != len(b) { // ipv6 comes after ipv4
263+
return len(a) < len(b)
264+
}
265+
for i := range a { // go left to right and compare each one
266+
if a[i] != b[i] {
267+
return a[i] < b[i]
268+
}
269+
}
270+
return false // they are equal
271+
}
272+
273+
// IPMin returns the minimum of a and b
274+
func IPMin(a, b net.IP) net.IP {
275+
if IPLessThan(a, b) {
276+
return a
277+
}
278+
return b
279+
}
280+
281+
// IPMax returns the maximum of a and b
282+
func IPMax(a, b net.IP) net.IP {
283+
if IPLessThan(a, b) {
284+
return b
285+
}
286+
return a
287+
}

net_utils_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
package netaddr
22

33
import (
4+
"fmt"
45
"math/big"
56
"net"
7+
"sort"
68
"testing"
79

810
"github.com/stretchr/testify/assert"
911
)
1012

13+
func TestDecrement(t *testing.T) {
14+
for _, tc := range []*struct {
15+
in, out net.IP
16+
}{
17+
{ParseIP("192.168.2.5"), ParseIP("192.168.2.4")},
18+
{ParseIP("192.168.0.0"), ParseIP("192.167.255.255")},
19+
{ParseIP("10.0.0.0"), ParseIP("9.255.255.255")},
20+
{ParseIP("0.0.0.0"), ParseIP("255.255.255.255")}, // 0 will cycle
21+
{ParseIP("::"), ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")}, // 0 will cycle
22+
{ParseIP("1::"), ParseIP("0:ffff:ffff:ffff:ffff:ffff:ffff:ffff")}, // 0 will cycle
23+
} {
24+
actual := decrementIP(tc.in)
25+
assert.Equal(t, tc.out, actual)
26+
}
27+
}
28+
1129
func TestExpandNet(t *testing.T) {
1230
n, _ := ParseNet("203.0.113.0/29")
1331
ips := expandNet(n, 10)
@@ -200,3 +218,45 @@ func TestBroadcastAddr(t *testing.T) {
200218
assert.Equal(t, ParseIP("2001:db8::ffff:ffff:ffff:ffff"), BroadcastAddr(parse("2001:db8::/64")))
201219
assert.Equal(t, ParseIP("2001:dff:ffff:ffff:ffff:ffff:ffff:ffff"), BroadcastAddr(parse("2001:db8::/24")))
202220
}
221+
222+
func TestIPLessThan(t *testing.T) {
223+
ips := []net.IP{
224+
ParseIP("10.0.0.0"),
225+
ParseIP("2001::"),
226+
ParseIP("192.168.1.1"),
227+
ParseIP("192.168.1.2"),
228+
ParseIP("10.0.0.1"),
229+
ParseIP("0:0:0:0:0:ffff:c0a8:1"), // ipv4 version of 192.168.0.1
230+
ParseIP("192.168.0.2").To16(), // this should come after the ipv4s
231+
ParseIP("10.0.1.3"),
232+
ParseIP("::"),
233+
ParseIP("1:1::"),
234+
ParseIP("10.2.2.3"),
235+
ParseIP("10.2.1.2"),
236+
ParseIP("10.0.0.0"),
237+
ParseIP("10.0.1.2"),
238+
ParseIP("2001:43::"),
239+
ParseIP("10.2.1.1"),
240+
}
241+
sort.SliceStable(ips, func(i, j int) bool {
242+
return IPLessThan(ips[i], ips[j])
243+
})
244+
assert.Equal(t, "["+
245+
"10.0.0.0 "+
246+
"10.0.0.0 "+
247+
"10.0.0.1 "+
248+
"10.0.1.2 "+
249+
"10.0.1.3 "+
250+
"10.2.1.1 "+
251+
"10.2.1.2 "+
252+
"10.2.2.3 "+
253+
"192.168.1.1 "+
254+
"192.168.1.2 "+
255+
":: "+
256+
"192.168.0.1 "+
257+
"192.168.0.2 "+
258+
"1:1:: "+
259+
"2001:: "+
260+
"2001:43::"+
261+
"]", fmt.Sprintf("%s", ips))
262+
}

0 commit comments

Comments
 (0)