netaddr 1.5.3 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +201 -0
- data/README.md +28 -8
- data/lib/eui48.rb +65 -0
- data/lib/eui64.rb +77 -0
- data/lib/ipv4.rb +87 -0
- data/lib/ipv4net.rb +262 -0
- data/lib/ipv6.rb +146 -0
- data/lib/ipv6net.rb +267 -0
- data/lib/mask128.rb +70 -0
- data/lib/mask32.rb +105 -0
- data/lib/netaddr.rb +122 -20
- data/lib/util.rb +373 -0
- data/test/eui48_test.rb +30 -0
- data/test/eui64_test.rb +32 -0
- data/test/examples.rb +155 -0
- data/test/ipv4_test.rb +60 -0
- data/test/ipv4net_test.rb +206 -0
- data/test/ipv6_test.rb +135 -0
- data/test/ipv6net_test.rb +199 -0
- data/test/mask128_test.rb +45 -0
- data/test/mask32_test.rb +56 -0
- data/test/netaddr_test.rb +149 -0
- data/test/run_all.rb +10 -0
- metadata +25 -20
- data/Errors +0 -7
- data/changelog +0 -52
- data/lib/cidr.rb +0 -2014
- data/lib/cidr_shortcuts.rb +0 -401
- data/lib/eui.rb +0 -402
- data/lib/ip_math.rb +0 -227
- data/lib/methods.rb +0 -1013
- data/lib/tree.rb +0 -816
- data/lib/validation_shortcuts.rb +0 -201
- data/license +0 -13
- data/test/cidr_test.rb +0 -545
- data/test/eui_test.rb +0 -101
- data/test/methods_test.rb +0 -331
- data/test/tree_test.rb +0 -347
data/lib/mask128.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module NetAddr
|
2
|
+
|
3
|
+
#Mask128 represents a 128-bit netmask.
|
4
|
+
class Mask128
|
5
|
+
# mask is the Integer representation of this netmask
|
6
|
+
attr_reader :mask
|
7
|
+
|
8
|
+
# prefix_len is the Integer prefix length of this netmask
|
9
|
+
attr_reader :prefix_len
|
10
|
+
|
11
|
+
# Create a Mask128 from an Integer prefix length. Valid values are 0-128.
|
12
|
+
# Throws ValidationError on error.
|
13
|
+
def initialize(prefix_len)
|
14
|
+
if (!prefix_len.kind_of?(Integer))
|
15
|
+
raise ValidationError, "Expected an Integer for 'prefix_len' but got a #{prefix_len.class}."
|
16
|
+
elsif ( (prefix_len < 0) || (prefix_len > 128) )
|
17
|
+
raise ValidationError, "#{prefix_len} must be in the range of 0-128."
|
18
|
+
end
|
19
|
+
@prefix_len = prefix_len
|
20
|
+
@mask = NetAddr::F128 ^ (NetAddr::F128 >> @prefix_len)
|
21
|
+
end
|
22
|
+
|
23
|
+
# parse will create an Mask128 from its string representation.
|
24
|
+
# arguments:
|
25
|
+
# * mask - String representing an netmask (ie. "/64").
|
26
|
+
#
|
27
|
+
# Throws ValidationError on error.
|
28
|
+
def Mask128.parse(mask)
|
29
|
+
mask = mask.strip
|
30
|
+
if (mask.start_with?("/")) # cidr format
|
31
|
+
mask = mask[1..-1] # remove "/"
|
32
|
+
end
|
33
|
+
return Mask128.new(Integer(mask))
|
34
|
+
rescue ArgumentError
|
35
|
+
raise ValidationError, "#{mask} is not valid integer."
|
36
|
+
end
|
37
|
+
|
38
|
+
#cmp compares equality with another Mask128. Return:
|
39
|
+
#* 1 if this Mask128 is larger in capacity
|
40
|
+
#* 0 if the two are equal
|
41
|
+
#* -1 if this Mask128 is smaller in capacity
|
42
|
+
def cmp(other)
|
43
|
+
if (!other.kind_of?(Mask128))
|
44
|
+
raise ArgumentError, "Expected an Mask128 object for 'other' but got a #{other.class}."
|
45
|
+
end
|
46
|
+
if (self.prefix_len < other.prefix_len)
|
47
|
+
return 1
|
48
|
+
elsif (self.prefix_len > other.prefix_len)
|
49
|
+
return -1
|
50
|
+
end
|
51
|
+
return 0
|
52
|
+
end
|
53
|
+
|
54
|
+
#len returns the number of IP addresses in this network. This is only useful if you have a subnet
|
55
|
+
# smaller than a /64 as it will always return 0 for prefixes <= 64.
|
56
|
+
def len()
|
57
|
+
if (self.prefix_len <= 64)
|
58
|
+
return 0
|
59
|
+
end
|
60
|
+
return (self.mask ^ NetAddr::F128) + 1 # bit flip the netmask and add 1
|
61
|
+
end
|
62
|
+
|
63
|
+
# to_s returns the Mask128 as a String
|
64
|
+
def to_s()
|
65
|
+
return "/#{@prefix_len}"
|
66
|
+
end
|
67
|
+
|
68
|
+
end # end class Mask128
|
69
|
+
|
70
|
+
end # end module
|
data/lib/mask32.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module NetAddr
|
2
|
+
|
3
|
+
#Mask32 represents a 32-bit netmask.
|
4
|
+
class Mask32
|
5
|
+
# mask is the Integer representation of this netmask
|
6
|
+
attr_reader :mask
|
7
|
+
|
8
|
+
# prefix_len is the Integer prefix length of this netmask
|
9
|
+
attr_reader :prefix_len
|
10
|
+
|
11
|
+
# Create a Mask32 from an Integer prefix length. Valid values are 0-32.
|
12
|
+
# Throws ValidationError on error.
|
13
|
+
def initialize(prefix_len)
|
14
|
+
if (!prefix_len.kind_of?(Integer))
|
15
|
+
raise ValidationError, "Expected an Integer for 'prefix_len' but got a #{prefix_len.class}."
|
16
|
+
elsif ( (prefix_len < 0) || (prefix_len > 32) )
|
17
|
+
raise ValidationError, "#{prefix_len} must be in the range of 0-32."
|
18
|
+
end
|
19
|
+
@prefix_len = prefix_len
|
20
|
+
@mask = NetAddr::F32 ^ (NetAddr::F32 >> @prefix_len)
|
21
|
+
end
|
22
|
+
|
23
|
+
# parse will create an Mask32 from its string representation.
|
24
|
+
# arguments:
|
25
|
+
# * mask - String representing a netmask (ie. "/24" or "255.255.255.0").
|
26
|
+
#
|
27
|
+
# Throws ValidationError on error.
|
28
|
+
def Mask32.parse(mask)
|
29
|
+
mask = mask.strip
|
30
|
+
if (mask.start_with?("/")) # cidr format
|
31
|
+
mask = mask[1..-1] # remove "/"
|
32
|
+
end
|
33
|
+
|
34
|
+
if (!mask.include?("."))
|
35
|
+
begin
|
36
|
+
return Mask32.new(Integer(mask))
|
37
|
+
rescue ArgumentError
|
38
|
+
raise ValidationError, "#{mask} is not valid integer."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# for extended netmask
|
43
|
+
# determine length of netmask by cycling through bit by bit and looking
|
44
|
+
# for the first '1' bit, tracking the length as we go. we also want to verify
|
45
|
+
# that the mask is valid (ie. not something like 255.254.255.0). we do this
|
46
|
+
# by creating a hostmask which covers the '0' bits of the mask. once we have
|
47
|
+
# separated the net vs host mask we xor them together. the result should be that
|
48
|
+
# all bits are now '1'. if not then we know we have an invalid netmask.
|
49
|
+
maskI = Util.parse_IPv4(mask)
|
50
|
+
prefix = 32
|
51
|
+
hostmask = 1
|
52
|
+
i = maskI
|
53
|
+
32.downto(1) do
|
54
|
+
if (i&1 == 1)
|
55
|
+
hostmask = hostmask >> 1
|
56
|
+
if (maskI ^hostmask != NetAddr::F32)
|
57
|
+
raise ValidationError, "#{mask} is invalid. It contains '1' bits in its host portion."
|
58
|
+
end
|
59
|
+
break
|
60
|
+
end
|
61
|
+
hostmask = (hostmask << 1) | 1
|
62
|
+
i = i>>1
|
63
|
+
prefix -= 1
|
64
|
+
end
|
65
|
+
return Mask32.new(prefix)
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
# extended returns the Mask32 in extended format (eg. x.x.x.x)
|
70
|
+
def extended()
|
71
|
+
Util.int_to_IPv4(@mask)
|
72
|
+
end
|
73
|
+
|
74
|
+
#cmp compares equality with another Mask32. Return:
|
75
|
+
#* 1 if this Mask128 is larger in capacity
|
76
|
+
#* 0 if the two are equal
|
77
|
+
#* -1 if this Mask128 is smaller in capacity
|
78
|
+
def cmp(other)
|
79
|
+
if (!other.kind_of?(Mask32))
|
80
|
+
raise ArgumentError, "Expected an Mask32 object for 'other' but got a #{other.class}."
|
81
|
+
end
|
82
|
+
if (self.prefix_len < other.prefix_len)
|
83
|
+
return 1
|
84
|
+
elsif (self.prefix_len > other.prefix_len)
|
85
|
+
return -1
|
86
|
+
end
|
87
|
+
return 0
|
88
|
+
end
|
89
|
+
|
90
|
+
#len returns the number of IP addresses in this network. It will always return 0 for /0 networks.
|
91
|
+
def len()
|
92
|
+
if (self.prefix_len == 0)
|
93
|
+
return 0
|
94
|
+
end
|
95
|
+
return (self.mask ^ NetAddr::F32) + 1 # bit flip the netmask and add 1
|
96
|
+
end
|
97
|
+
|
98
|
+
# to_s returns the Mask32 as a String
|
99
|
+
def to_s()
|
100
|
+
return "/#{@prefix_len}"
|
101
|
+
end
|
102
|
+
|
103
|
+
end # end class Mask32
|
104
|
+
|
105
|
+
end # end module
|
data/lib/netaddr.rb
CHANGED
@@ -1,24 +1,126 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
require_relative "eui48.rb"
|
2
|
+
require_relative "eui64.rb"
|
3
|
+
require_relative "ipv4.rb"
|
4
|
+
require_relative "ipv4net.rb"
|
5
|
+
require_relative "ipv6.rb"
|
6
|
+
require_relative "ipv6net.rb"
|
7
|
+
require_relative "mask32.rb"
|
8
|
+
require_relative "mask128.rb"
|
9
|
+
require_relative "util.rb"
|
10
10
|
|
11
11
|
module NetAddr
|
12
|
+
# Constants
|
13
|
+
|
14
|
+
# 32 bits worth of '1'
|
15
|
+
F32 = 2**32-1
|
16
|
+
|
17
|
+
# 128 bits worth of '1'
|
18
|
+
F128 = 2**128-1
|
19
|
+
|
12
20
|
|
13
|
-
|
21
|
+
# ValidationError is thrown when a method fails a validation test.
|
22
|
+
class ValidationError < StandardError
|
14
23
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
|
25
|
+
# ipv4_prefix_len returns the prefix length needed to hold the number of IP addresses specified by "size".
|
26
|
+
def ipv4_prefix_len(size)
|
27
|
+
prefix_len = 32
|
28
|
+
32.downto(0) do |i|
|
29
|
+
hostbits = 32 - prefix_len
|
30
|
+
max = 1 << hostbits
|
31
|
+
if (size <= max)
|
32
|
+
break
|
33
|
+
end
|
34
|
+
prefix_len -= 1
|
35
|
+
end
|
36
|
+
return prefix_len
|
37
|
+
end
|
38
|
+
module_function :ipv4_prefix_len
|
39
|
+
|
40
|
+
## parse_ip parses a string into an IPv4 or IPv6
|
41
|
+
def parse_ip(ip)
|
42
|
+
if (ip.include?(":"))
|
43
|
+
return IPv6.parse(ip)
|
44
|
+
end
|
45
|
+
return IPv4.parse(ip)
|
46
|
+
end
|
47
|
+
module_function :parse_ip
|
48
|
+
|
49
|
+
## parse_net parses a string into an IPv4Net or IPv6Net
|
50
|
+
def parse_net(net)
|
51
|
+
if (net.include?(":"))
|
52
|
+
return IPv6Net.parse(net)
|
53
|
+
end
|
54
|
+
return IPv4Net.parse(net)
|
55
|
+
end
|
56
|
+
module_function :parse_net
|
57
|
+
|
58
|
+
# sort_IPv4 sorts a list of IPv4 objects in ascending order.
|
59
|
+
# It will return a new list with any non IPv4 objects removed.
|
60
|
+
def sort_IPv4(list)
|
61
|
+
if ( !list.kind_of?(Array) )
|
62
|
+
raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
|
63
|
+
end
|
64
|
+
filtered = Util.filter_IPv4(list)
|
65
|
+
return Util.quick_sort(filtered)
|
66
|
+
end
|
67
|
+
module_function :sort_IPv4
|
68
|
+
|
69
|
+
# sort_IPv6 sorts a list of IPv6 objects in ascending order.
|
70
|
+
# It will return a new list with any non IPv6 objects removed.
|
71
|
+
def sort_IPv6(list)
|
72
|
+
if ( !list.kind_of?(Array) )
|
73
|
+
raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
|
74
|
+
end
|
75
|
+
filtered = Util.filter_IPv6(list)
|
76
|
+
return Util.quick_sort(filtered)
|
77
|
+
end
|
78
|
+
module_function :sort_IPv6
|
79
|
+
|
80
|
+
# sort_IPv4Net sorts a list of IPv4Net objects in ascending order.
|
81
|
+
# It will return a new list with any non IPv4Net objects removed.
|
82
|
+
def sort_IPv4Net(list)
|
83
|
+
if ( !list.kind_of?(Array) )
|
84
|
+
raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
|
85
|
+
end
|
86
|
+
filtered = Util.filter_IPv4Net(list)
|
87
|
+
return Util.quick_sort(filtered)
|
88
|
+
end
|
89
|
+
module_function :sort_IPv4Net
|
90
|
+
|
91
|
+
# sort_IPv6Net sorts a list of IPv6Net objects in ascending order.
|
92
|
+
# It will return a new list with any non IPv6Net objects removed.
|
93
|
+
def sort_IPv6Net(list)
|
94
|
+
if ( !list.kind_of?(Array) )
|
95
|
+
raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
|
96
|
+
end
|
97
|
+
filtered = Util.filter_IPv6Net(list)
|
98
|
+
return Util.quick_sort(filtered)
|
99
|
+
end
|
100
|
+
module_function :sort_IPv6Net
|
101
|
+
|
102
|
+
# summ_IPv4Net summarizes a list of IPv4Net objects as much as possible.
|
103
|
+
# It will return a new list with any non IPv4Net objects removed.
|
104
|
+
def summ_IPv4Net(list)
|
105
|
+
list = Util.filter_IPv4Net(list)
|
106
|
+
if (list.length>1)
|
107
|
+
list = Util.discard_subnets(list)
|
108
|
+
return Util.summ_peers(list)
|
109
|
+
end
|
110
|
+
return [].concat(list)
|
111
|
+
end
|
112
|
+
module_function :summ_IPv4Net
|
113
|
+
|
114
|
+
# summ_IPv6Net summarizes a list of IPv6Net objects as much as possible.
|
115
|
+
# It will return a new list with any non IPv6Net objects removed.
|
116
|
+
def summ_IPv6Net(list)
|
117
|
+
list = Util.filter_IPv6Net(list)
|
118
|
+
if (list.length>1)
|
119
|
+
list = Util.discard_subnets(list)
|
120
|
+
return Util.summ_peers(list)
|
121
|
+
end
|
122
|
+
return [].concat(list)
|
123
|
+
end
|
124
|
+
module_function :summ_IPv6Net
|
125
|
+
|
126
|
+
end # end module
|
data/lib/util.rb
ADDED
@@ -0,0 +1,373 @@
|
|
1
|
+
module NetAddr
|
2
|
+
|
3
|
+
# Contains various internal util functions
|
4
|
+
class Util
|
5
|
+
private
|
6
|
+
|
7
|
+
# backfill generates subnets between given IPv4Net/IPv6Net and the limit address.
|
8
|
+
# limit should be < ipnet. will create subnets up to and including limit.
|
9
|
+
def Util.backfill(ipnet,limit)
|
10
|
+
nets = []
|
11
|
+
cur = ipnet
|
12
|
+
while true do
|
13
|
+
net = cur.prev
|
14
|
+
if (net == nil || net.network.addr < limit)
|
15
|
+
break
|
16
|
+
end
|
17
|
+
nets.unshift(net)
|
18
|
+
cur = net
|
19
|
+
end
|
20
|
+
return nets
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# discard_subnets returns a copy of the IPv4NetList with any entries which are subnets of other entries removed.
|
25
|
+
def Util.discard_subnets(list)
|
26
|
+
keepers = []
|
27
|
+
last = list[list.length-1]
|
28
|
+
keep_last = true
|
29
|
+
list.each do |net|
|
30
|
+
rel = last.rel(net)
|
31
|
+
if (!rel) # keep unrelated nets
|
32
|
+
keepers.push(net)
|
33
|
+
elsif (rel == -1) # keep supernets, but do not keep last
|
34
|
+
keepers.push(net)
|
35
|
+
keep_last = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# recursively clean up keepers
|
40
|
+
if (keepers.length > 0)
|
41
|
+
keepers = discard_subnets(keepers)
|
42
|
+
end
|
43
|
+
if keep_last
|
44
|
+
keepers.unshift(last)
|
45
|
+
end
|
46
|
+
return keepers
|
47
|
+
end
|
48
|
+
|
49
|
+
# fill returns a copy of the given Array, stripped of any networks which are not subnets of ipnet
|
50
|
+
# and with any missing gaps filled in.
|
51
|
+
def Util.fill(ipnet,list)
|
52
|
+
# sort & get rid of non subnets
|
53
|
+
subs = []
|
54
|
+
discard_subnets(list).each do |sub|
|
55
|
+
r = ipnet.rel(sub)
|
56
|
+
if (r == 1)
|
57
|
+
subs.push(sub)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
subs = quick_sort(subs)
|
61
|
+
|
62
|
+
filled = []
|
63
|
+
if (subs.length > 0)
|
64
|
+
# bottom fill if base missing
|
65
|
+
base = ipnet.network.addr
|
66
|
+
if (subs[0].network.addr != base)
|
67
|
+
filled = backfill(subs[0],base)
|
68
|
+
end
|
69
|
+
|
70
|
+
# fill gaps between subnets
|
71
|
+
0.upto(subs.length-1) do |i|
|
72
|
+
sub = subs[i]
|
73
|
+
if (i+1 < subs.length)
|
74
|
+
filled.concat( fwdfill(sub,ipnet,subs[i+1]) )
|
75
|
+
else
|
76
|
+
filled.concat( fwdfill(sub,ipnet,nil) )
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
return filled
|
81
|
+
end
|
82
|
+
|
83
|
+
# filter_IPv4 returns a copy of list with only IPv4 objects
|
84
|
+
def Util.filter_IPv4(list)
|
85
|
+
filtered = []
|
86
|
+
list.each do |ip|
|
87
|
+
if (ip.kind_of?(IPv4))
|
88
|
+
filtered.push(ip)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
return filtered
|
92
|
+
end
|
93
|
+
|
94
|
+
# filter_IPv4Net returns a copy of list with only IPv4Net objects
|
95
|
+
def Util.filter_IPv4Net(list)
|
96
|
+
filtered = []
|
97
|
+
list.each do |ip|
|
98
|
+
if (ip.kind_of?(IPv4Net))
|
99
|
+
filtered.push(ip)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
return filtered
|
103
|
+
end
|
104
|
+
|
105
|
+
# filter_IPv6 returns a copy of list with only IPv6 objects
|
106
|
+
def Util.filter_IPv6(list)
|
107
|
+
filtered = []
|
108
|
+
list.each do |ip|
|
109
|
+
if (ip.kind_of?(IPv6))
|
110
|
+
filtered.push(ip)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
return filtered
|
114
|
+
end
|
115
|
+
|
116
|
+
# filter_IPv6Net returns a copy of list with only IPv4Net objects
|
117
|
+
def Util.filter_IPv6Net(list)
|
118
|
+
filtered = []
|
119
|
+
list.each do |ip|
|
120
|
+
if (ip.kind_of?(IPv6Net))
|
121
|
+
filtered.push(ip)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
return filtered
|
125
|
+
end
|
126
|
+
|
127
|
+
# fwdfill returns subnets between given IPv4Net/IPv6Nett and the limit address. limit should be > ipnet.
|
128
|
+
def Util.fwdfill(ipnet,supernet,limit)
|
129
|
+
nets = [ipnet]
|
130
|
+
cur = ipnet
|
131
|
+
if (limit != nil) # if limit, then fill gaps between net and limit
|
132
|
+
while true do
|
133
|
+
nextSub = cur.next()
|
134
|
+
# ensure we've not exceed the total address space
|
135
|
+
if (nextSub == nil)
|
136
|
+
break
|
137
|
+
end
|
138
|
+
# ensure we've not exceeded the address space of supernet
|
139
|
+
if (supernet.rel(nextSub) == nil)
|
140
|
+
break
|
141
|
+
end
|
142
|
+
# ensure we've not hit limit
|
143
|
+
if (nextSub.network.addr == limit.network.addr)
|
144
|
+
break
|
145
|
+
end
|
146
|
+
|
147
|
+
# check relationship to limit
|
148
|
+
if (nextSub.rel(limit) != nil) # if related, then nextSub must be a supernet of limit. we need to shrink it.
|
149
|
+
prefixLen = nextSub.netmask.prefix_len
|
150
|
+
while true do
|
151
|
+
prefixLen += 1
|
152
|
+
if (nextSub.kind_of?(IPv4Net))
|
153
|
+
nextSub = IPv4Net.new(nextSub.network, Mask32.new(prefixLen))
|
154
|
+
else
|
155
|
+
nextSub = IPv6Net.new(nextSub.network, Mask128.new(prefixLen))
|
156
|
+
end
|
157
|
+
if (nextSub.rel(limit) == nil) # stop when we no longer overlap with limit
|
158
|
+
break
|
159
|
+
end
|
160
|
+
end
|
161
|
+
else # otherwise, if unrelated then grow until we hit the limit
|
162
|
+
prefixLen = nextSub.netmask.prefix_len
|
163
|
+
mask = nextSub.netmask.mask
|
164
|
+
while true do
|
165
|
+
prefixLen -= 1
|
166
|
+
if (prefixLen == supernet.netmask.prefix_len) # break if we've hit the supernet boundary
|
167
|
+
break
|
168
|
+
end
|
169
|
+
mask = mask << 1
|
170
|
+
if (nextSub.network.addr|mask != mask) # break when bit boundary crossed (there are '1' bits in the host portion)
|
171
|
+
break
|
172
|
+
end
|
173
|
+
if (nextSub.kind_of?(IPv4Net))
|
174
|
+
grown = IPv4Net.new(nextSub.network, Mask32.new(prefixLen))
|
175
|
+
else
|
176
|
+
grown = IPv6Net.new(nextSub.network, Mask128.new(prefixLen))
|
177
|
+
end
|
178
|
+
if (grown.rel(limit) != nil) # if we've overlapped with limit in any way, then break
|
179
|
+
break
|
180
|
+
end
|
181
|
+
nextSub = grown
|
182
|
+
end
|
183
|
+
end
|
184
|
+
nets.push(nextSub)
|
185
|
+
cur = nextSub
|
186
|
+
end
|
187
|
+
else # if no limit, then get next largest sibs until we've exceeded supernet
|
188
|
+
while true do
|
189
|
+
nextSub = cur.next()
|
190
|
+
# ensure we've not exceed the total address space
|
191
|
+
if (nextSub == nil)
|
192
|
+
break
|
193
|
+
end
|
194
|
+
# ensure we've not exceeded the address space of supernet
|
195
|
+
if (supernet.rel(nextSub) == nil)
|
196
|
+
break
|
197
|
+
end
|
198
|
+
nets.push(nextSub)
|
199
|
+
cur = nextSub
|
200
|
+
end
|
201
|
+
end
|
202
|
+
return nets
|
203
|
+
end
|
204
|
+
|
205
|
+
# int_to_IPv4 converts an Integer into an IPv4 address String
|
206
|
+
def Util.int_to_IPv4(i)
|
207
|
+
octets = []
|
208
|
+
3.downto(0) do |x|
|
209
|
+
octet = (i >> 8*x) & 0xFF
|
210
|
+
octets.push(octet.to_s)
|
211
|
+
end
|
212
|
+
return octets.join('.')
|
213
|
+
end
|
214
|
+
|
215
|
+
# parse_IPv4 parses an IPv4 address String into an Integer
|
216
|
+
def Util.parse_IPv4(ip)
|
217
|
+
# check that only valid characters are present
|
218
|
+
if (ip =~ /[^0-9\.]/)
|
219
|
+
raise ValidationError, "#{ip} contains invalid characters."
|
220
|
+
end
|
221
|
+
|
222
|
+
octets = ip.strip.split('.')
|
223
|
+
if (octets.length != 4)
|
224
|
+
raise ValidationError, "IPv4 requires (4) octets."
|
225
|
+
end
|
226
|
+
|
227
|
+
ipInt = 0
|
228
|
+
i = 4
|
229
|
+
octets.each do |octet|
|
230
|
+
octetI = octet.to_i()
|
231
|
+
if ( (octetI < 0) || (octetI >= 256) )
|
232
|
+
raise ValidationError, "#{ip} is out of bounds for IPv4."
|
233
|
+
end
|
234
|
+
i -= 1
|
235
|
+
ipInt = ipInt | (octetI << 8*i)
|
236
|
+
end
|
237
|
+
return ipInt
|
238
|
+
end
|
239
|
+
|
240
|
+
# parse_IPv6 parses an IPv6 address String into an Integer
|
241
|
+
def Util.parse_IPv6(ip)
|
242
|
+
# check that only valid characters are present
|
243
|
+
if (ip =~ /[^0-9a-fA-F\:.]/)
|
244
|
+
raise ValidationError, "#{ip} contains invalid characters."
|
245
|
+
end
|
246
|
+
|
247
|
+
ip = ip.strip
|
248
|
+
if (ip == "::")
|
249
|
+
return 0 # zero address
|
250
|
+
end
|
251
|
+
ipv4Int = nil
|
252
|
+
if (ip.include?(".")) # check for ipv4 embedded addresses
|
253
|
+
words = ip.split(":")
|
254
|
+
begin
|
255
|
+
ipv4Int = Util.parse_IPv4(words.last)
|
256
|
+
rescue
|
257
|
+
raise ValidationError, "IPv4-embedded IPv6 address is invalid."
|
258
|
+
end
|
259
|
+
ip = ip.sub(words.last,"0:0") # temporarily remove the ipv4 portion
|
260
|
+
end
|
261
|
+
words = []
|
262
|
+
if (ip.include?("::")) # short format
|
263
|
+
if (ip =~ /:{3,}/) # make sure only i dont have ":::"
|
264
|
+
raise ValidationError, "#{ip} contains invalid field separator."
|
265
|
+
end
|
266
|
+
if (ip.scan(/::/).length != 1)
|
267
|
+
raise ValidationError, "#{ip} contains multiple '::' sequences."
|
268
|
+
end
|
269
|
+
|
270
|
+
halves = ip.split("::")
|
271
|
+
if (halves[0] == nil) # cases such as ::1
|
272
|
+
halves[0] = "0"
|
273
|
+
end
|
274
|
+
if (halves[1] == nil) # cases such as 1::
|
275
|
+
halves[1] = "0"
|
276
|
+
end
|
277
|
+
upHalf = halves[0].split(":")
|
278
|
+
loHalf = halves[1].split(":")
|
279
|
+
numWords = upHalf.length + loHalf.length
|
280
|
+
if (numWords > 8)
|
281
|
+
raise ValidationError, "#{ip} is too long."
|
282
|
+
end
|
283
|
+
words = upHalf
|
284
|
+
(8-numWords).downto(1) do |i|
|
285
|
+
words.push("0")
|
286
|
+
end
|
287
|
+
words.concat(loHalf)
|
288
|
+
else
|
289
|
+
words = ip.split(":")
|
290
|
+
if (words.length > 8)
|
291
|
+
raise ValidationError, "#{ip} is too long."
|
292
|
+
elsif (words.length < 8)
|
293
|
+
raise ValidationError, "#{ip} is too short."
|
294
|
+
end
|
295
|
+
end
|
296
|
+
ipInt = 0
|
297
|
+
i = 8
|
298
|
+
words.each do |word|
|
299
|
+
i -= 1
|
300
|
+
word = word.to_i(16) << (16*i)
|
301
|
+
ipInt = ipInt | word
|
302
|
+
end
|
303
|
+
if ipv4Int # re-add ipv4 portion if present
|
304
|
+
ipInt = ipInt | ipv4Int
|
305
|
+
end
|
306
|
+
return ipInt
|
307
|
+
end
|
308
|
+
|
309
|
+
# quick_sort will return a sorted copy of the provided Array.
|
310
|
+
# The array must contain only objects which implement a cmp method and which are comparable to each other.
|
311
|
+
def Util.quick_sort(list)
|
312
|
+
if (list.length <= 1)
|
313
|
+
return [].concat(list)
|
314
|
+
end
|
315
|
+
|
316
|
+
final_list = []
|
317
|
+
lt_list = []
|
318
|
+
gt_list = []
|
319
|
+
eq_list = []
|
320
|
+
pivot = list[list.length-1]
|
321
|
+
list.each do |ip|
|
322
|
+
cmp = pivot.cmp(ip)
|
323
|
+
if (cmp == 1)
|
324
|
+
lt_list.push(ip)
|
325
|
+
elsif (cmp == -1)
|
326
|
+
gt_list.push(ip)
|
327
|
+
else
|
328
|
+
eq_list.push(ip)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
final_list.concat( quick_sort(lt_list) )
|
332
|
+
final_list.concat(eq_list)
|
333
|
+
final_list.concat( quick_sort(gt_list) )
|
334
|
+
return final_list
|
335
|
+
end
|
336
|
+
|
337
|
+
# summ_peers returns a copy of the list with any merge-able subnets summ'd together.
|
338
|
+
def Util.summ_peers(list)
|
339
|
+
summd = quick_sort(list)
|
340
|
+
while true do
|
341
|
+
list_len = summd.length
|
342
|
+
last = list_len - 1
|
343
|
+
tmp_list = []
|
344
|
+
i = 0
|
345
|
+
while (i < list_len) do
|
346
|
+
net = summd[i]
|
347
|
+
next_net = i+1
|
348
|
+
if (i != last)
|
349
|
+
# if this net and next_net summarize then discard them & keep summary
|
350
|
+
new_net = net.summ(summd[next_net])
|
351
|
+
if (new_net) # can summ. keep summary
|
352
|
+
tmp_list.push(new_net)
|
353
|
+
i += 1 # skip next_net
|
354
|
+
else # cant summ. keep existing
|
355
|
+
tmp_list.push(net)
|
356
|
+
end
|
357
|
+
else
|
358
|
+
tmp_list.push(net) # keep last
|
359
|
+
end
|
360
|
+
i += 1
|
361
|
+
end
|
362
|
+
|
363
|
+
# stop when list stops getting shorter
|
364
|
+
if (tmp_list.length == list_len)
|
365
|
+
break
|
366
|
+
end
|
367
|
+
summd = tmp_list
|
368
|
+
end
|
369
|
+
return summd
|
370
|
+
end
|
371
|
+
|
372
|
+
end # end class
|
373
|
+
end # end module
|