netaddr 1.5.3 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of netaddr might be problematic. Click here for more details.

data/lib/mask32.rb ADDED
@@ -0,0 +1,99 @@
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.strip!
30
+ if (mask.start_with?("/")) # cidr format
31
+ return Mask32.new(mask[1..-1].to_i) # remove "/"
32
+ elsif (!mask.include?("."))
33
+ return Mask32.new(mask.to_i)
34
+ end
35
+
36
+ # for extended netmask
37
+ # determine length of netmask by cycling through bit by bit and looking
38
+ # for the first '1' bit, tracking the length as we go. we also want to verify
39
+ # that the mask is valid (ie. not something like 255.254.255.0). we do this
40
+ # by creating a hostmask which covers the '0' bits of the mask. once we have
41
+ # separated the net vs host mask we xor them together. the result should be that
42
+ # all bits are now '1'. if not then we know we have an invalid netmask.
43
+ maskI = Util.parse_IPv4(mask)
44
+ prefix = 32
45
+ hostmask = 1
46
+ i = maskI
47
+ 32.downto(1) do
48
+ if (i&1 == 1)
49
+ hostmask = hostmask >> 1
50
+ if (maskI ^hostmask != NetAddr::F32)
51
+ raise ValidationError, "#{mask} is invalid. It contains '1' bits in its host portion."
52
+ end
53
+ break
54
+ end
55
+ hostmask = (hostmask << 1) | 1
56
+ i = i>>1
57
+ prefix -= 1
58
+ end
59
+ return Mask32.new(prefix)
60
+
61
+ end
62
+
63
+ # extended returns the Mask32 in extended format (eg. x.x.x.x)
64
+ def extended()
65
+ Util.intToMask32(@mask)
66
+ end
67
+
68
+ #cmp compares equality with another Mask32. Return:
69
+ #* 1 if this Mask128 is larger in capacity
70
+ #* 0 if the two are equal
71
+ #* -1 if this Mask128 is smaller in capacity
72
+ def cmp(other)
73
+ if (!other.kind_of?(Mask32))
74
+ raise ArgumentError, "Expected an Mask32 object for 'other' but got a #{other.class}."
75
+ end
76
+ if (self.prefix_len < other.prefix_len)
77
+ return 1
78
+ elsif (self.prefix_len > other.prefix_len)
79
+ return -1
80
+ end
81
+ return 0
82
+ end
83
+
84
+ #len returns the number of IP addresses in this network. It will always return 0 for /0 networks.
85
+ def len()
86
+ if (self.prefix_len == 0)
87
+ return 0
88
+ end
89
+ return (self.mask ^ NetAddr::F32) + 1 # bit flip the netmask and add 1
90
+ end
91
+
92
+ # to_s returns the Mask32 as a String
93
+ def to_s()
94
+ return "/#{@prefix_len}"
95
+ end
96
+
97
+ end # end class Mask32
98
+
99
+ end # end module
data/lib/netaddr.rb CHANGED
@@ -1,24 +1,108 @@
1
- require 'time'
2
- require 'digest/sha1'
3
- require File.join(File.dirname(__FILE__), 'validation_shortcuts.rb')
4
- require File.join(File.dirname(__FILE__), 'ip_math.rb')
5
- require File.join(File.dirname(__FILE__), 'cidr_shortcuts.rb')
6
- require File.join(File.dirname(__FILE__), 'methods.rb')
7
- require File.join(File.dirname(__FILE__), 'cidr.rb')
8
- require File.join(File.dirname(__FILE__), 'tree.rb')
9
- require File.join(File.dirname(__FILE__), 'eui.rb')
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
- class BoundaryError < StandardError #:nodoc:
21
+ # ValidationError is thrown when a method fails a validation test.
22
+ class ValidationError < StandardError
14
23
  end
15
-
16
- class ValidationError < StandardError #:nodoc:
17
- end
18
-
19
- class VersionError < StandardError #:nodoc:
20
- end
21
-
22
- end # module NetAddr
23
-
24
- __END__
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
+ # sort_IPv4 sorts a list of IPv4 objects in ascending order.
41
+ # It will return a new list with any non IPv4 objects removed.
42
+ def sort_IPv4(list)
43
+ if ( !list.kind_of?(Array) )
44
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
45
+ end
46
+ filtered = Util.filter_IPv4(list)
47
+ return Util.quick_sort(filtered)
48
+ end
49
+ module_function :sort_IPv4
50
+
51
+ # sort_IPv6 sorts a list of IPv6 objects in ascending order.
52
+ # It will return a new list with any non IPv6 objects removed.
53
+ def sort_IPv6(list)
54
+ if ( !list.kind_of?(Array) )
55
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
56
+ end
57
+ filtered = Util.filter_IPv6(list)
58
+ return Util.quick_sort(filtered)
59
+ end
60
+ module_function :sort_IPv6
61
+
62
+ # sort_IPv4Net sorts a list of IPv4Net objects in ascending order.
63
+ # It will return a new list with any non IPv4Net objects removed.
64
+ def sort_IPv4Net(list)
65
+ if ( !list.kind_of?(Array) )
66
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
67
+ end
68
+ filtered = Util.filter_IPv4Net(list)
69
+ return Util.quick_sort(filtered)
70
+ end
71
+ module_function :sort_IPv4Net
72
+
73
+ # sort_IPv6Net sorts a list of IPv6Net objects in ascending order.
74
+ # It will return a new list with any non IPv6Net objects removed.
75
+ def sort_IPv6Net(list)
76
+ if ( !list.kind_of?(Array) )
77
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
78
+ end
79
+ filtered = Util.filter_IPv6Net(list)
80
+ return Util.quick_sort(filtered)
81
+ end
82
+ module_function :sort_IPv6Net
83
+
84
+ # summ_IPv4Net summarizes a list of IPv4Net objects as much as possible.
85
+ # It will return a new list with any non IPv4Net objects removed.
86
+ def summ_IPv4Net(list)
87
+ list = Util.filter_IPv4Net(list)
88
+ if (list.length>1)
89
+ list = Util.discard_subnets(list)
90
+ return Util.summ_peers(list)
91
+ end
92
+ return [].concat(list)
93
+ end
94
+ module_function :summ_IPv4Net
95
+
96
+ # summ_IPv6Net summarizes a list of IPv6Net objects as much as possible.
97
+ # It will return a new list with any non IPv6Net objects removed.
98
+ def summ_IPv6Net(list)
99
+ list = Util.filter_IPv6Net(list)
100
+ if (list.length>1)
101
+ list = Util.discard_subnets(list)
102
+ return Util.summ_peers(list)
103
+ end
104
+ return [].concat(list)
105
+ end
106
+ module_function :summ_IPv6Net
107
+
108
+ end # end module
data/lib/util.rb ADDED
@@ -0,0 +1,310 @@
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
+ unrelated = []
27
+ supernets = []
28
+ last = list[list.length-1]
29
+ list.each do |net|
30
+ rel = last.rel(net)
31
+ if (!rel)
32
+ unrelated.push(net)
33
+ elsif (rel == -1) # last is subnet of net
34
+ supernets.push(net)
35
+ end
36
+ end
37
+
38
+ cleaned = []
39
+ if (supernets.length > 0)
40
+ cleaned = discard_subnets(supernets)
41
+ else
42
+ cleaned.push(last)
43
+ end
44
+
45
+ if (unrelated.length > 0)
46
+ cleaned.concat( discard_subnets(unrelated) )
47
+ end
48
+ return cleaned
49
+ end
50
+
51
+ # fill returns a copy of the given Array, stripped of any networks which are not subnets of ipnet
52
+ # and with any missing gaps filled in.
53
+ def Util.fill(ipnet,list)
54
+ # sort & git rid of non subnets
55
+ subs = []
56
+ discard_subnets(list).each do |sub|
57
+ r = ipnet.rel(sub)
58
+ if (r == 1)
59
+ subs.push(sub)
60
+ end
61
+ end
62
+ subs = quick_sort(subs)
63
+
64
+ filled = []
65
+ if (subs.length > 0)
66
+ # bottom fill if base missing
67
+ base = ipnet.network.addr
68
+ if (subs[0].network.addr != base)
69
+ filled = backfill(subs[0],base)
70
+ end
71
+
72
+ # fill gaps
73
+ sib = ipnet.next_sib()
74
+ ceil = NetAddr::F32
75
+ if (sib != nil)
76
+ ceil = sib.network.addr
77
+ end
78
+
79
+ 0.upto(subs.length-1) do |i|
80
+ sub = subs[i]
81
+ filled.push(sub)
82
+ limit = ceil
83
+ if (i+1 < subs.length)
84
+ limit = subs[i+1].network.addr
85
+ end
86
+ filled.concat( fwdfill(sub,limit) )
87
+ end
88
+ end
89
+ return filled
90
+ end
91
+
92
+ # filter_IPv4 returns a copy of list with only IPv4 objects
93
+ def Util.filter_IPv4(list)
94
+ filtered = []
95
+ list.each do |ip|
96
+ if (ip.kind_of?(IPv4))
97
+ filtered.push(ip)
98
+ end
99
+ end
100
+ return filtered
101
+ end
102
+
103
+ # filter_IPv4Net returns a copy of list with only IPv4Net objects
104
+ def Util.filter_IPv4Net(list)
105
+ filtered = []
106
+ list.each do |ip|
107
+ if (ip.kind_of?(IPv4Net))
108
+ filtered.push(ip)
109
+ end
110
+ end
111
+ return filtered
112
+ end
113
+
114
+ # filter_IPv6 returns a copy of list with only IPv6 objects
115
+ def Util.filter_IPv6(list)
116
+ filtered = []
117
+ list.each do |ip|
118
+ if (ip.kind_of?(IPv6))
119
+ filtered.push(ip)
120
+ end
121
+ end
122
+ return filtered
123
+ end
124
+
125
+ # filter_IPv6Net returns a copy of list with only IPv4Net objects
126
+ def Util.filter_IPv6Net(list)
127
+ filtered = []
128
+ list.each do |ip|
129
+ if (ip.kind_of?(IPv6Net))
130
+ filtered.push(ip)
131
+ end
132
+ end
133
+ return filtered
134
+ end
135
+
136
+ # fwdfill returns subnets between given IPv4Net/IPv6Nett and the limit address.
137
+ # limit should be > ipnet. will create subnets up to limit.
138
+ def Util.fwdfill(ipnet,limit)
139
+ nets = []
140
+ cur = ipnet
141
+ while true do
142
+ net = cur.next
143
+ if (net == nil || net.network.addr >= limit)
144
+ break
145
+ end
146
+ nets.push(net)
147
+ cur = net
148
+ end
149
+ return nets
150
+ end
151
+
152
+ # int_to_IPv4 converts an Integer into an IPv4 address String
153
+ def Util.int_to_IPv4(i)
154
+ octets = []
155
+ 3.downto(0) do |x|
156
+ octet = (i >> 8*x) & 0xFF
157
+ octets.push(octet.to_s)
158
+ end
159
+ return octets.join('.')
160
+ end
161
+
162
+ # parse_IPv4 parses an IPv4 address String into an Integer
163
+ def Util.parse_IPv4(ip)
164
+ # check that only valid characters are present
165
+ if (ip =~ /[^0-9\.]/)
166
+ raise ValidationError, "#{ip} contains invalid characters."
167
+ end
168
+
169
+ ip.strip!
170
+ octets = ip.split('.')
171
+ if (octets.length != 4)
172
+ raise ValidationError, "IPv4 requires (4) octets."
173
+ end
174
+
175
+ ipInt = 0
176
+ i = 4
177
+ octets.each do |octet|
178
+ octetI = octet.to_i()
179
+ if ( (octetI < 0) || (octetI >= 256) )
180
+ raise ValidationError, "#{ip} is out of bounds for IPv4."
181
+ end
182
+ i -= 1
183
+ ipInt = ipInt | (octetI << 8*i)
184
+ end
185
+ return ipInt
186
+ end
187
+
188
+ # parse_IPv6 parses an IPv6 address String into an Integer
189
+ def Util.parse_IPv6(ip)
190
+ # check that only valid characters are present
191
+ if (ip =~ /[^0-9a-fA-F\:]/)
192
+ raise ValidationError, "#{ip} contains invalid characters."
193
+ end
194
+
195
+ ip.strip!
196
+ if (ip == "::")
197
+ return 0 # zero address
198
+ end
199
+ words = []
200
+ if (ip.include?("::")) # short format
201
+ if (ip =~ /:{3,}/) # make sure only i dont have ":::"
202
+ raise ValidationError, "#{ip} contains invalid field separator."
203
+ end
204
+ if (ip.scan(/::/).length != 1)
205
+ raise ValidationError, "#{ip} contains multiple '::' sequences."
206
+ end
207
+
208
+ halves = ip.split("::")
209
+ if (halves[0] == nil) # cases such as ::1
210
+ halves[0] = "0"
211
+ end
212
+ if (halves[1] == nil) # cases such as 1::
213
+ halves[1] = "0"
214
+ end
215
+ upHalf = halves[0].split(":")
216
+ loHalf = halves[1].split(":")
217
+ numWords = upHalf.length + loHalf.length
218
+ if (numWords > 8)
219
+ raise ValidationError, "#{ip} is too long."
220
+ end
221
+ words = upHalf
222
+ (8-numWords).downto(1) do |i|
223
+ words.push("0")
224
+ end
225
+ words.concat(loHalf)
226
+ else
227
+ words = ip.split(":")
228
+ if (words.length > 8)
229
+ raise ValidationError, "#{ip} is too long."
230
+ elsif (words.length < 8)
231
+ raise ValidationError, "#{ip} is too short."
232
+ end
233
+ end
234
+
235
+ ipInt = 0
236
+ i = 8
237
+ words.each do |word|
238
+ i -= 1
239
+ word = word.to_i(16) << (16*i)
240
+ ipInt = ipInt | word
241
+ end
242
+
243
+ return ipInt
244
+ end
245
+
246
+ # quick_sort will return a sorted copy of the provided Array.
247
+ # The array must contain only objects which implement a cmp method and which are comparable to each other.
248
+ def Util.quick_sort(list)
249
+ if (list.length <= 1)
250
+ return [].concat(list)
251
+ end
252
+
253
+ final_list = []
254
+ lt_list = []
255
+ gt_list = []
256
+ eq_list = []
257
+ pivot = list[list.length-1]
258
+ list.each do |ip|
259
+ cmp = pivot.cmp(ip)
260
+ if (cmp == 1)
261
+ lt_list.push(ip)
262
+ elsif (cmp == -1)
263
+ gt_list.push(ip)
264
+ else
265
+ eq_list.push(ip)
266
+ end
267
+ end
268
+ final_list.concat( quick_sort(lt_list) )
269
+ final_list.concat(eq_list)
270
+ final_list.concat( quick_sort(gt_list) )
271
+ return final_list
272
+ end
273
+
274
+ # summ_peers returns a copy of the list with any merge-able subnets summ'd together.
275
+ def Util.summ_peers(list)
276
+ summd = quick_sort(list)
277
+ while true do
278
+ list_len = summd.length
279
+ last = list_len - 1
280
+ tmp_list = []
281
+ i = 0
282
+ while (i < list_len) do
283
+ net = summd[i]
284
+ next_net = i+1
285
+ if (i != last)
286
+ # if this net and next_net summarize then discard them & keep summary
287
+ new_net = net.summ(summd[next_net])
288
+ if (new_net) # can summ. keep summary
289
+ tmp_list.push(new_net)
290
+ i += 1 # skip next_net
291
+ else # cant summ. keep existing
292
+ tmp_list.push(net)
293
+ end
294
+ else
295
+ tmp_list.push(net) # keep last
296
+ end
297
+ i += 1
298
+ end
299
+
300
+ # stop when list stops getting shorter
301
+ if (tmp_list.length == list_len)
302
+ break
303
+ end
304
+ summd = tmp_list
305
+ end
306
+ return summd
307
+ end
308
+
309
+ end # end class
310
+ end # end module
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require_relative "../lib/netaddr.rb"
4
+ require 'test/unit'
5
+
6
+ class TestEUI48 < Test::Unit::TestCase
7
+ def test_new
8
+ eui = NetAddr::EUI48.new(0)
9
+ assert_equal("00-00-00-00-00-00", eui.to_s)
10
+
11
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI48.new(2**48) }
12
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI48.new(-1) }
13
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI48.new("00-00-00-00-00-00") } # string
14
+ end
15
+
16
+ def test_parse
17
+ assert_equal("aa-bb-cc-dd-ee-ff", NetAddr::EUI48.parse("aa-bb-cc-dd-ee-ff").to_s)
18
+ assert_equal("aa-bb-cc-dd-ee-ff", NetAddr::EUI48.parse("aa:bb:cc:dd:ee:ff").to_s)
19
+ assert_equal("aa-bb-cc-dd-ee-ff", NetAddr::EUI48.parse("aabb.ccdd.eeff").to_s)
20
+ assert_equal("aa-bb-cc-dd-ee-ff", NetAddr::EUI48.parse("aabbccddeeff").to_s)
21
+
22
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI48.parse("aa-bb-cc-dd-ee-ff-00-11") }
23
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI48.parse("aa-bb-cc-dd-ee-gg") }
24
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI48.parse("aa;bb;cc;dd;ee;ff") }
25
+ end
26
+
27
+ def test_to_eui64
28
+ assert_equal("aa-bb-cc-ff-fe-dd-ee-ff", NetAddr::EUI48.parse("aa-bb-cc-dd-ee-ff").to_eui64.to_s)
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require_relative "../lib/netaddr.rb"
4
+ require 'test/unit'
5
+
6
+ class TestEUI64 < Test::Unit::TestCase
7
+ def test_new
8
+ eui = NetAddr::EUI64.new(0)
9
+ assert_equal("00-00-00-00-00-00-00-00", eui.to_s)
10
+
11
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI64.new(2**64) }
12
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI64.new(-1) }
13
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI64.new("00-00-00-00-00-00-00-00") } # string
14
+ end
15
+
16
+ def test_parse
17
+ assert_equal("aa-bb-cc-dd-ee-ff-00-11", NetAddr::EUI64.parse("aa-bb-cc-dd-ee-ff-00-11").to_s)
18
+ assert_equal("aa-bb-cc-dd-ee-ff-00-11", NetAddr::EUI64.parse("aa:bb:cc:dd:ee:ff:00:11").to_s)
19
+ assert_equal("aa-bb-cc-dd-ee-ff-00-11", NetAddr::EUI64.parse("aabb.ccdd.eeff.0011").to_s)
20
+ assert_equal("aa-bb-cc-dd-ee-ff-00-11", NetAddr::EUI64.parse("aabbccddeeff0011").to_s)
21
+
22
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI64.parse("aa-bb-cc-dd-ee-ff-00-11-22") }
23
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI64.parse("aa-bb-cc-dd-ee-ff-gg") }
24
+ assert_raise(NetAddr::ValidationError){ NetAddr::EUI64.parse("aa;bb;cc;dd;ee;ff;00;11") }
25
+ end
26
+
27
+ def test_to_ipv6
28
+ net = NetAddr::IPv6Net.parse("fe80::/64")
29
+ eui = NetAddr::EUI64.parse("aa-bb-cc-dd-ee-ff-00-11")
30
+ assert_equal("fe80::a8bb:ccdd:eeff:11", eui.to_ipv6(net).to_s)
31
+ end
32
+ end