netaddr 1.0.0
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/Errors +13 -0
- data/README +202 -0
- data/lib/cidr.rb +1925 -0
- data/lib/eui.rb +390 -0
- data/lib/methods.rb +1273 -0
- data/lib/net_addr.rb +26 -0
- data/lib/tree.rb +880 -0
- data/tests/cidr_test.rb +405 -0
- data/tests/eui_test.rb +70 -0
- data/tests/methods_test.rb +303 -0
- data/tests/tree_test.rb +304 -0
- metadata +55 -0
data/lib/eui.rb
ADDED
@@ -0,0 +1,390 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Copyright (c) 2006 Dustin Spinhirne (www.spinhirne.com)
|
3
|
+
|
4
|
+
Licensed under the same terms as Ruby, No Warranty is provided.
|
5
|
+
=end
|
6
|
+
|
7
|
+
module NetAddr
|
8
|
+
|
9
|
+
#=EUI - Extended Unique Identifier
|
10
|
+
#
|
11
|
+
#A class & series of methods for creating and manipulating Extended Unique Identifier
|
12
|
+
#(EUI) addresses. Two types of address formats are supported EUI-48 and EUI-64. The
|
13
|
+
#most common use for this class will be to manipulate MAC addresses (which are essentially
|
14
|
+
#a type of EUI-48 address).
|
15
|
+
#
|
16
|
+
#EUI addresses are separated into two parts, the
|
17
|
+
#Organizationally Unique Identifier (OUI) and the Extended Identifier (EI). The OUI
|
18
|
+
#is assigned by the IEEE and is used to identify a particular hardware manufacturer.
|
19
|
+
#The EI is assigned by the hardware manufacturer as a per device unique address.
|
20
|
+
#
|
21
|
+
#Probably the most useful feature of this class, and thus the reason it was created,
|
22
|
+
#is to help automate certain address assignments within IP. For example, IPv6
|
23
|
+
#Link Local addresses use MAC addresses for IP auto-assignment and multicast MAC addresses
|
24
|
+
#are determined based on the multicast IP address.
|
25
|
+
#
|
26
|
+
class EUI
|
27
|
+
|
28
|
+
private_class_method :new
|
29
|
+
|
30
|
+
#==============================================================================#
|
31
|
+
# create()
|
32
|
+
#==============================================================================#
|
33
|
+
|
34
|
+
#===Synopsis
|
35
|
+
#Create a new EUI48 or EUI64 object.
|
36
|
+
#
|
37
|
+
# addr = NetAddr::EUI.new('aa-bb-cc-dd-ee-ff')
|
38
|
+
# addr = NetAddr::EUI.new('aa:bb:cc:dd:ee:ff')
|
39
|
+
# addr = NetAddr::EUI.new('aabb.ccdd.eeff')
|
40
|
+
# addr = NetAddr::EUI.new('aa-bb-cc-dd-ee-ff-00-01')
|
41
|
+
#
|
42
|
+
#===Arguments
|
43
|
+
#* EUI as a String
|
44
|
+
#
|
45
|
+
#===Returns
|
46
|
+
#* EUI48 or EUI64 object
|
47
|
+
#
|
48
|
+
def EUI.create(eui)
|
49
|
+
if (!eui.kind_of? String)
|
50
|
+
raise ArgumentError, "Expected String, but #{eui.class} provided."
|
51
|
+
end
|
52
|
+
|
53
|
+
# validate
|
54
|
+
NetAddr.validate_eui(eui)
|
55
|
+
|
56
|
+
# remove formatting characters
|
57
|
+
eui.gsub!(/[\.\:\-]/, '')
|
58
|
+
|
59
|
+
# split into oui & ei, pack, and store
|
60
|
+
if (eui.length == 12)
|
61
|
+
eui = NetAddr::EUI48.new(eui.to_i(16))
|
62
|
+
else
|
63
|
+
eui = NetAddr::EUI64.new(eui.to_i(16))
|
64
|
+
end
|
65
|
+
|
66
|
+
return(eui)
|
67
|
+
end
|
68
|
+
|
69
|
+
#==============================================================================#
|
70
|
+
# address()
|
71
|
+
#==============================================================================#
|
72
|
+
|
73
|
+
#===Synopsis
|
74
|
+
# Returns EUI address. The default address format is xxxx.xxxx.xxxx
|
75
|
+
#
|
76
|
+
# puts addr.address(:Delimiter => '.') --> 'aabb.ccdd.eeff'
|
77
|
+
#
|
78
|
+
#===Arguments:
|
79
|
+
#* Optional Hash with the following fields:
|
80
|
+
# :Delimiter -- delimitation character. valid values are (-,:,and .) (optional)
|
81
|
+
#
|
82
|
+
#===Returns:
|
83
|
+
#* String
|
84
|
+
#
|
85
|
+
def address(options=nil)
|
86
|
+
known_args = [:Delimiter]
|
87
|
+
delimiter = '-'
|
88
|
+
|
89
|
+
octets = []
|
90
|
+
octets.concat(unpack_oui)
|
91
|
+
octets.concat(unpack_ei)
|
92
|
+
|
93
|
+
if (options)
|
94
|
+
if (!options.kind_of? Hash)
|
95
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
96
|
+
end
|
97
|
+
NetAddr.validate_args(options.keys,known_args)
|
98
|
+
|
99
|
+
if (options.has_key?(:Delimiter))
|
100
|
+
delimiter = options[:Delimiter]
|
101
|
+
delimiter = '-' if (delimiter != '-' && delimiter != ':' && delimiter != '.' )
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if (delimiter == '-' || delimiter == ':')
|
106
|
+
address = octets.join(delimiter)
|
107
|
+
elsif (delimiter == '.')
|
108
|
+
toggle = 0
|
109
|
+
octets.each do |x|
|
110
|
+
if (!address)
|
111
|
+
address = x
|
112
|
+
toggle = 1
|
113
|
+
elsif (toggle == 0)
|
114
|
+
address = address << '.' << x
|
115
|
+
toggle = 1
|
116
|
+
else
|
117
|
+
address = address << x
|
118
|
+
toggle = 0
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
return(address)
|
125
|
+
end
|
126
|
+
|
127
|
+
#==============================================================================#
|
128
|
+
# ei()
|
129
|
+
#==============================================================================#
|
130
|
+
|
131
|
+
#===Synopsis
|
132
|
+
#Returns Extended Identifier portion of an EUI address (the vendor assigned ID).
|
133
|
+
#The default address format is xx-xx-xx
|
134
|
+
#
|
135
|
+
# puts addr.ei(:Delimiter => '-')
|
136
|
+
#
|
137
|
+
#===Arguments:
|
138
|
+
#* Optional Hash with the following fields:
|
139
|
+
# :Delimiter -- delimitation character. valid values are (-, and :) (optional)
|
140
|
+
#
|
141
|
+
#===Returns:
|
142
|
+
#* String
|
143
|
+
#
|
144
|
+
def ei(options=nil)
|
145
|
+
known_args = [:Delimiter]
|
146
|
+
octets = unpack_ei()
|
147
|
+
delimiter = '-'
|
148
|
+
|
149
|
+
if (options)
|
150
|
+
if (!options.kind_of? Hash)
|
151
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
152
|
+
end
|
153
|
+
NetAddr.validate_args(options.keys,known_args)
|
154
|
+
|
155
|
+
if (options.has_key?(:Delimiter))
|
156
|
+
if (options[:Delimiter] == ':')
|
157
|
+
delimiter = options[:Delimiter]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
ei = octets.join(delimiter)
|
162
|
+
|
163
|
+
return(ei)
|
164
|
+
end
|
165
|
+
|
166
|
+
#==============================================================================#
|
167
|
+
# link_local()
|
168
|
+
#==============================================================================#
|
169
|
+
|
170
|
+
#===Synopsis
|
171
|
+
# Provide an IPv6 Link Local address based on the current EUI address.
|
172
|
+
#
|
173
|
+
# puts addr.link_local()
|
174
|
+
#
|
175
|
+
#===Arguments:
|
176
|
+
#* Optional Hash with the following fields:
|
177
|
+
# :Short -- if true, return IPv6 addresses in short-hand notation (optional)
|
178
|
+
# :Objectify -- if true, return CIDR objects (optional)
|
179
|
+
#
|
180
|
+
#===Returns:
|
181
|
+
#* CIDR address String or an NetAddr::CIDR object
|
182
|
+
#
|
183
|
+
def link_local(options=nil)
|
184
|
+
known_args = [:Short, :Objectify]
|
185
|
+
objectify = false
|
186
|
+
short = false
|
187
|
+
|
188
|
+
if (options)
|
189
|
+
if (!options.kind_of? Hash)
|
190
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
191
|
+
end
|
192
|
+
NetAddr.validate_args(options.keys,known_args)
|
193
|
+
|
194
|
+
if (options.has_key?(:Objectify) && options[:Objectify] == true)
|
195
|
+
objectify = true
|
196
|
+
end
|
197
|
+
|
198
|
+
if (options.has_key?(:Short) && options[:Short] == true)
|
199
|
+
short = true
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
if (self.kind_of?(NetAddr::EUI64))
|
204
|
+
link_local = @ei | (@oui << 40)
|
205
|
+
else
|
206
|
+
link_local = @ei | 0xfffe000000 | (@oui << 40)
|
207
|
+
end
|
208
|
+
link_local = link_local | (0xfe80 << 112)
|
209
|
+
|
210
|
+
if (!objectify)
|
211
|
+
link_local = NetAddr.unpack_ip_addr(link_local, :Version => 6)
|
212
|
+
link_local = NetAddr.shorten(link_local) if (short)
|
213
|
+
else
|
214
|
+
link_local = NetAddr::CIDR.create(link_local, :Version => 6)
|
215
|
+
end
|
216
|
+
|
217
|
+
return(link_local)
|
218
|
+
end
|
219
|
+
|
220
|
+
#==============================================================================#
|
221
|
+
# oui()
|
222
|
+
#==============================================================================#
|
223
|
+
|
224
|
+
#===Synopsis
|
225
|
+
#Returns Organizationally Unique Identifier portion of an EUI address (the vendor ID).
|
226
|
+
#The default address format is xx-xx-xx.
|
227
|
+
#
|
228
|
+
# puts addr.oui(:Delimiter => '-')
|
229
|
+
#
|
230
|
+
#===Arguments:
|
231
|
+
#* Optional Hash with the following fields:
|
232
|
+
# :Delimiter -- delimitation character. valid values are (-, and :) (optional)
|
233
|
+
#
|
234
|
+
#===Returns:
|
235
|
+
#* String
|
236
|
+
#
|
237
|
+
def oui(options=nil)
|
238
|
+
known_args = [:Delimiter]
|
239
|
+
octets = unpack_oui()
|
240
|
+
delimiter = '-'
|
241
|
+
|
242
|
+
if (options)
|
243
|
+
if (!options.kind_of? Hash)
|
244
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
245
|
+
end
|
246
|
+
NetAddr.validate_args(options.keys,known_args)
|
247
|
+
|
248
|
+
if (options.has_key?(:Delimiter))
|
249
|
+
if (options[:Delimiter] == ':')
|
250
|
+
delimiter = options[:Delimiter]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
oui = octets.join(delimiter)
|
255
|
+
|
256
|
+
return(oui)
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
# PRIVATE METHODS
|
261
|
+
private
|
262
|
+
|
263
|
+
#==============================================================================#
|
264
|
+
# unpack_ei()
|
265
|
+
#==============================================================================#
|
266
|
+
|
267
|
+
def unpack_ei()
|
268
|
+
hex = @ei
|
269
|
+
octets = []
|
270
|
+
|
271
|
+
if (self.kind_of?(NetAddr::EUI64))
|
272
|
+
length = 64
|
273
|
+
else
|
274
|
+
length = 48
|
275
|
+
end
|
276
|
+
|
277
|
+
loop_count = (length - 24)/8
|
278
|
+
loop_count.times do
|
279
|
+
octet = (hex & 0xff).to_s(16)
|
280
|
+
octet = '0' << octet if (octet.length != 2)
|
281
|
+
octets.unshift(octet)
|
282
|
+
hex = hex >> 8
|
283
|
+
end
|
284
|
+
return(octets)
|
285
|
+
end
|
286
|
+
|
287
|
+
#==============================================================================#
|
288
|
+
# unpack_oui()
|
289
|
+
#==============================================================================#
|
290
|
+
|
291
|
+
def unpack_oui()
|
292
|
+
hex = @oui
|
293
|
+
octets = []
|
294
|
+
3.times do
|
295
|
+
octet = (hex & 0xff).to_s(16)
|
296
|
+
octet = '0' << octet if (octet.length != 2)
|
297
|
+
octets.unshift(octet)
|
298
|
+
hex = hex >> 8
|
299
|
+
end
|
300
|
+
return(octets)
|
301
|
+
end
|
302
|
+
|
303
|
+
end # class EUI
|
304
|
+
|
305
|
+
# EUI-48 Address - Inherits all methods from NetAddr::EUI.
|
306
|
+
# Addresses of this class have a 24-bit OUI and a 24-bit EI.
|
307
|
+
class EUI48 < EUI
|
308
|
+
|
309
|
+
public_class_method :new
|
310
|
+
|
311
|
+
#==============================================================================#
|
312
|
+
# initialize()
|
313
|
+
#==============================================================================#
|
314
|
+
|
315
|
+
#===Synopsis
|
316
|
+
#Return an EUI48 object. PackedEUI takes precedence over EUI.
|
317
|
+
#
|
318
|
+
# addr = NetAddr::EUI48.new('aa-bb-cc-dd-ee-ff')
|
319
|
+
# addr = NetAddr::EUI48.new('aa:bb:cc:dd:ee:ff')
|
320
|
+
# addr = NetAddr::EUI48.new('aabb.ccdd.eeff')
|
321
|
+
#
|
322
|
+
#===Arguments:
|
323
|
+
#* EUI as a String or Integer
|
324
|
+
#
|
325
|
+
def initialize(eui)
|
326
|
+
|
327
|
+
if (eui.kind_of?(Integer))
|
328
|
+
@oui = eui >> 24
|
329
|
+
@ei = eui & 0xffffff
|
330
|
+
|
331
|
+
elsif(eui.kind_of?(String))
|
332
|
+
# validate
|
333
|
+
NetAddr.validate_eui(eui)
|
334
|
+
|
335
|
+
# remove formatting characters
|
336
|
+
eui.gsub!(/[\.\:\-]/, '')
|
337
|
+
|
338
|
+
# split into oui & ei, pack, and store
|
339
|
+
@oui = eui.slice!(0..5).to_i(16)
|
340
|
+
@ei = eui.to_i(16)
|
341
|
+
else
|
342
|
+
raise ArgumentError, "Expected String or Integer, but #{eui.class} provided."
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
347
|
+
|
348
|
+
# EUI-64 Address - Inherits all methods from NetAddr::EUI.
|
349
|
+
# Addresses of this class have a 24-bit OUI and a 40-bit EI.
|
350
|
+
class EUI64 < EUI
|
351
|
+
|
352
|
+
public_class_method :new
|
353
|
+
|
354
|
+
#==============================================================================#
|
355
|
+
# initialize()
|
356
|
+
#==============================================================================#
|
357
|
+
|
358
|
+
#===Synopsis
|
359
|
+
#Return an EUI object. PackedEUI takes precedence over EUI.
|
360
|
+
#
|
361
|
+
# addr = NetAddr::EUI64.new('aa-bb-cc-dd-ee-ff-00-01')
|
362
|
+
#
|
363
|
+
#===Arguments:
|
364
|
+
#* EUI as a String or Integer
|
365
|
+
#
|
366
|
+
def initialize(eui)
|
367
|
+
|
368
|
+
if (eui.kind_of?(Integer))
|
369
|
+
@oui = eui >> 40
|
370
|
+
@ei = eui & 0xffffffffffd
|
371
|
+
|
372
|
+
elsif(eui.kind_of?(String))
|
373
|
+
# validate
|
374
|
+
NetAddr.validate_eui(eui)
|
375
|
+
|
376
|
+
# remove formatting characters
|
377
|
+
eui.gsub!(/[\.\:\-]/, '')
|
378
|
+
|
379
|
+
# split into oui & ei, pack, and store
|
380
|
+
@oui = eui.slice!(0..5).to_i(16)
|
381
|
+
@ei = eui.to_i(16)
|
382
|
+
else
|
383
|
+
raise ArgumentError, "Expected String or Integer, but #{eui.class} provided."
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
|
389
|
+
end # module NetAddr
|
390
|
+
__END__
|
data/lib/methods.rb
ADDED
@@ -0,0 +1,1273 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Copyright (c) 2006 Dustin Spinhirne (www.spinhirne.com)
|
3
|
+
|
4
|
+
Licensed under the same terms as Ruby, No Warranty is provided.
|
5
|
+
=end
|
6
|
+
|
7
|
+
module NetAddr
|
8
|
+
|
9
|
+
#==============================================================================#
|
10
|
+
# merge()
|
11
|
+
#==============================================================================#
|
12
|
+
|
13
|
+
#===Synopsis
|
14
|
+
#Given a list of CIDR addresses or NetAddr::CIDR objects of the same version,
|
15
|
+
#merge (summarize) them in the most efficient way possible. Summarization
|
16
|
+
#will only occur when the newly created supernets will not result in the
|
17
|
+
#'creation' of additional space. For example the following blocks
|
18
|
+
#(192.168.0.0/24, 192.168.1.0/24, and 192.168.2.0/24) would be summarized into
|
19
|
+
#192.168.0.0/23 and 192.168.2.0/24 rather than into 192.168.0.0/22
|
20
|
+
#
|
21
|
+
#I have designed this with enough flexibility that you can pass in CIDR
|
22
|
+
#addresses that arent even related (ex. 192.168.1.0/26, 192.168.1.64/27, 192.168.1.96/27
|
23
|
+
#10.1.0.0/26, 10.1.0.64/26) and they will be merged properly (ie 192.168.1.0/25,
|
24
|
+
#and 10.1.0.0/25 would be returned).
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
# supernets = NetAddr.merge(['192.168.1.0/27','192.168.1.32/27'])
|
28
|
+
# supernets = NetAddr.merge([cidr1,cidr2])
|
29
|
+
# supernets = NetAddr.merge(['192.168.1.0/27','192.168.1.32/27'], :Short => true)
|
30
|
+
#
|
31
|
+
#===Arguments:
|
32
|
+
#* Array of CIDR addresses as Strings, or an Array of NetAddr::CIDR objects
|
33
|
+
#* Optional Hash with the following keys:
|
34
|
+
# :Objectify -- if true, return NetAddr::CIDR objects (optional)
|
35
|
+
# :Short -- if true, return IPv6 addresses in short-hand notation (optional)
|
36
|
+
#
|
37
|
+
#===Returns:
|
38
|
+
#* Array of CIDR addresses as Strings, or an Array of NetAddr::CIDR objects
|
39
|
+
#
|
40
|
+
def merge(list,options=nil)
|
41
|
+
known_args = [:Objectify, :Short]
|
42
|
+
version = nil
|
43
|
+
all_f = nil
|
44
|
+
short = false
|
45
|
+
objectify = false
|
46
|
+
|
47
|
+
# validate list
|
48
|
+
raise ArgumentError, "Array expected for argument 'list' but #{list.class} provided." if (!list.kind_of?(Array) )
|
49
|
+
|
50
|
+
# validate options
|
51
|
+
if (options)
|
52
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash) )
|
53
|
+
NetAddr.validate_args(options.keys,known_args)
|
54
|
+
|
55
|
+
if (options.has_key?(:Short) && options[:Short] == true)
|
56
|
+
short = true
|
57
|
+
end
|
58
|
+
|
59
|
+
if (options.has_key?(:Objectify) && options[:Objectify] == true)
|
60
|
+
objectify = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# make sure all are valid types of the same IP version
|
65
|
+
supernet_list = []
|
66
|
+
list.each do |obj|
|
67
|
+
if (!obj.kind_of?(NetAddr::CIDR))
|
68
|
+
begin
|
69
|
+
obj = NetAddr::CIDR.create(obj)
|
70
|
+
rescue Exception => error
|
71
|
+
raise ArgumentError, "An array element of :List raised the following " +
|
72
|
+
"errors: #{error}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
version = obj.version if (!version)
|
77
|
+
all_f = obj.all_f if (!all_f)
|
78
|
+
if (!obj.version == version)
|
79
|
+
raise VersionError, "Provided objects must be of the same IP version."
|
80
|
+
end
|
81
|
+
supernet_list.push(obj)
|
82
|
+
end
|
83
|
+
|
84
|
+
# merge subnets by removing them from 'supernet_list',
|
85
|
+
# and categorizing them into hash of arrays ({packed_netmask => [packed_network,packed_network,etc...] )
|
86
|
+
# within each categorization we merge contiguous subnets
|
87
|
+
# and then remove them from that category & put them back into
|
88
|
+
# 'supernet_list'. we do this until supernet_list stops getting any shorter
|
89
|
+
categories = {}
|
90
|
+
supernet_list_length = 0
|
91
|
+
until (supernet_list_length == supernet_list.length)
|
92
|
+
supernet_list_length = supernet_list.length
|
93
|
+
|
94
|
+
# categorize
|
95
|
+
supernet_list.each do |cidr|
|
96
|
+
netmask = cidr.packed_netmask
|
97
|
+
network = cidr.packed_network
|
98
|
+
if (categories.has_key?(netmask) )
|
99
|
+
categories[netmask].push(network)
|
100
|
+
else
|
101
|
+
categories[netmask] = [network]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
supernet_list.clear
|
105
|
+
|
106
|
+
ordered_cats = categories.keys.sort
|
107
|
+
ordered_cats.each do |netmask|
|
108
|
+
nets = categories[netmask].sort
|
109
|
+
bitstep = (all_f + 1) - netmask
|
110
|
+
|
111
|
+
until (nets.length == 0)
|
112
|
+
# take the first network & create its supernet. this
|
113
|
+
# supernet will have x number of subnets, so we'll look
|
114
|
+
# & see if we have those subnets. if so, keep supernet & delete subnets.
|
115
|
+
to_merge = []
|
116
|
+
multiplier = 1
|
117
|
+
network1 = nets[0]
|
118
|
+
num_required = 2**multiplier
|
119
|
+
supermask = (netmask << multiplier) & all_f
|
120
|
+
supernet = supermask & network1
|
121
|
+
if (network1 == supernet)
|
122
|
+
# we have the first half of the new supernet
|
123
|
+
expected = network1
|
124
|
+
nets.each do |network|
|
125
|
+
if (network == expected)
|
126
|
+
to_merge.push(network)
|
127
|
+
expected = expected + bitstep
|
128
|
+
if ( (to_merge.length == num_required) && (nets.length > num_required) )
|
129
|
+
# we have all of our subnets for this round, but still have
|
130
|
+
# more to look at
|
131
|
+
multiplier += 1
|
132
|
+
num_required = 2**multiplier
|
133
|
+
supermask = (netmask << multiplier) & all_f
|
134
|
+
supernet = supermask & network1
|
135
|
+
end
|
136
|
+
else
|
137
|
+
break
|
138
|
+
end
|
139
|
+
end
|
140
|
+
else
|
141
|
+
# we have the second half of the new supernet only
|
142
|
+
to_merge.push(network1)
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
if (to_merge.length != num_required)
|
147
|
+
# we dont have all of our subnets, so backstep 1 bit
|
148
|
+
multiplier -= 1
|
149
|
+
supermask = (netmask << multiplier) & all_f
|
150
|
+
supernet = supermask & network1
|
151
|
+
end
|
152
|
+
|
153
|
+
# save new supernet
|
154
|
+
supernet_list.push(NetAddr::CIDR.create(supernet,
|
155
|
+
:PackedNetmask => supermask,
|
156
|
+
:Version => version))
|
157
|
+
|
158
|
+
# delete the subnets of the new supernet
|
159
|
+
(2**multiplier).times {nets.delete(to_merge.shift)}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
categories.clear
|
163
|
+
supernet_list = NetAddr.sort(supernet_list)
|
164
|
+
end
|
165
|
+
|
166
|
+
# decide what to return
|
167
|
+
if (!objectify)
|
168
|
+
supernets = []
|
169
|
+
supernet_list.each {|entry| supernets.push(entry.desc(:Short => short))}
|
170
|
+
return(supernets)
|
171
|
+
else
|
172
|
+
return(supernet_list)
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
module_function :merge
|
177
|
+
|
178
|
+
#==============================================================================#
|
179
|
+
# minimum_size()
|
180
|
+
#==============================================================================#
|
181
|
+
|
182
|
+
#===Synopsis
|
183
|
+
#Given the number of IP addresses required in a subnet, return the minimum
|
184
|
+
#netmask (in bits) required for that subnet. IP version is assumed to be 4 unless specified otherwise.
|
185
|
+
#
|
186
|
+
# Example:
|
187
|
+
# netmask = NetAddr.minumum_size(14)
|
188
|
+
# netmask = NetAddr.minumum_size(65536, :Version => 6)
|
189
|
+
#
|
190
|
+
#===Arguments:
|
191
|
+
#* IP count as an Integer
|
192
|
+
#* Optional Hash with the following keys:
|
193
|
+
# :Version -- IP version - Integer(optional)
|
194
|
+
#
|
195
|
+
#===Returns:
|
196
|
+
#* Integer
|
197
|
+
#
|
198
|
+
def minimum_size(ipcount, options=nil)
|
199
|
+
version = 4
|
200
|
+
known_args = [:Version]
|
201
|
+
|
202
|
+
# validate ipcount
|
203
|
+
raise ArgumentError, "Integer expected for argument 'ipcount' but #{ipcount.class} provided." if (!ipcount.kind_of?(Integer))
|
204
|
+
|
205
|
+
# validate options
|
206
|
+
if (options)
|
207
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
208
|
+
|
209
|
+
NetAddr.validate_args(options.keys,known_args)
|
210
|
+
|
211
|
+
if (options.has_key?(:Version))
|
212
|
+
version = options[:Version]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
if (version == 4)
|
217
|
+
max_bits = 32
|
218
|
+
else
|
219
|
+
max_bits = 128
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
if (ipcount > 2**max_bits)
|
224
|
+
raise BoundaryError, "Required IP count exceeds number of IP addresses available " +
|
225
|
+
"for IPv#{version}."
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
bits_needed = 0
|
230
|
+
until (2**bits_needed >= ipcount)
|
231
|
+
bits_needed += 1
|
232
|
+
end
|
233
|
+
subnet_bits = max_bits - bits_needed
|
234
|
+
|
235
|
+
return(subnet_bits)
|
236
|
+
end
|
237
|
+
module_function :minimum_size
|
238
|
+
|
239
|
+
#==============================================================================#
|
240
|
+
# pack_ip_addr()
|
241
|
+
#==============================================================================#
|
242
|
+
|
243
|
+
#===Synopsis
|
244
|
+
#Convert IP addresses into an Integer. Will attempt to auto-detect IP version if not provided.
|
245
|
+
#
|
246
|
+
# Example:
|
247
|
+
# pack_ip_addr('192.168.1.1')
|
248
|
+
# pack_ip_addr(ffff::1', :Version => 6)
|
249
|
+
# pack_ip_addr(::192.168.1.1')
|
250
|
+
#
|
251
|
+
#===Arguments:
|
252
|
+
#* IP address as a String
|
253
|
+
#* Optional Hash with the following keys:
|
254
|
+
# :Version -- IP version - Integer
|
255
|
+
#
|
256
|
+
#===Returns:
|
257
|
+
#* Integer
|
258
|
+
#
|
259
|
+
def pack_ip_addr(ip, options=nil)
|
260
|
+
known_args = [:Version]
|
261
|
+
to_validate = {}
|
262
|
+
|
263
|
+
# validate options
|
264
|
+
if (options)
|
265
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
266
|
+
NetAddr.validate_args(options.keys,known_args)
|
267
|
+
|
268
|
+
if (options.has_key?(:Version))
|
269
|
+
version = options[:Version]
|
270
|
+
to_validate[:Version] = version
|
271
|
+
if (version != 4 && version != 6)
|
272
|
+
raise VersionError, ":Version should be 4 or 6, but was '#{version}'."
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
if ( ip.kind_of?(String) )
|
278
|
+
|
279
|
+
# validate
|
280
|
+
NetAddr.validate_ip_addr(ip, to_validate)
|
281
|
+
|
282
|
+
# determine version if not provided
|
283
|
+
if (!version)
|
284
|
+
if ( ip =~ /\./ && ip !~ /:/ )
|
285
|
+
version = 4
|
286
|
+
else
|
287
|
+
version = 6
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
packed_ip = 0
|
292
|
+
if ( version == 4)
|
293
|
+
octets = ip.split('.')
|
294
|
+
(0..3).each do |x|
|
295
|
+
octet = octets.pop.to_i
|
296
|
+
octet = octet << 8*x
|
297
|
+
packed_ip = packed_ip | octet
|
298
|
+
end
|
299
|
+
|
300
|
+
else
|
301
|
+
# if ipv4-mapped ipv6 addr
|
302
|
+
if (ip =~ /\./)
|
303
|
+
dotted_dec = true
|
304
|
+
end
|
305
|
+
|
306
|
+
# split up by ':'
|
307
|
+
fields = []
|
308
|
+
if (ip =~ /::/)
|
309
|
+
shrthnd = ip.split( /::/ )
|
310
|
+
if (shrthnd.length == 0)
|
311
|
+
return(0)
|
312
|
+
else
|
313
|
+
first_half = shrthnd[0].split( /:/ ) if (shrthnd[0])
|
314
|
+
sec_half = shrthnd[1].split( /:/ ) if (shrthnd[1])
|
315
|
+
first_half = [] if (!first_half)
|
316
|
+
sec_half = [] if (!sec_half)
|
317
|
+
end
|
318
|
+
missing_fields = 8 - first_half.length - sec_half.length
|
319
|
+
missing_fields -= 1 if dotted_dec
|
320
|
+
fields = fields.concat(first_half)
|
321
|
+
missing_fields.times {fields.push('0')}
|
322
|
+
fields = fields.concat(sec_half)
|
323
|
+
|
324
|
+
else
|
325
|
+
fields = ip.split(':')
|
326
|
+
end
|
327
|
+
|
328
|
+
if (dotted_dec)
|
329
|
+
ipv4_addr = fields.pop
|
330
|
+
packed_v4 = NetAddr.pack_ip_addr(ipv4_addr, :Version => 4)
|
331
|
+
octets = []
|
332
|
+
2.times do
|
333
|
+
octet = packed_v4 & 0xFFFF
|
334
|
+
octets.unshift(octet.to_s(16))
|
335
|
+
packed_v4 = packed_v4 >> 16
|
336
|
+
end
|
337
|
+
fields.concat(octets)
|
338
|
+
end
|
339
|
+
|
340
|
+
# pack
|
341
|
+
(0..7).each do |x|
|
342
|
+
field = fields.pop.to_i(16)
|
343
|
+
field = field << 16*x
|
344
|
+
packed_ip = packed_ip | field
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
else
|
350
|
+
raise ArgumentError, "String expected for argument 'ip' but #{ip.class} provided."
|
351
|
+
end
|
352
|
+
|
353
|
+
return(packed_ip)
|
354
|
+
end
|
355
|
+
module_function :pack_ip_addr
|
356
|
+
|
357
|
+
#==============================================================================#
|
358
|
+
# pack_ip_netmask()
|
359
|
+
#==============================================================================#
|
360
|
+
|
361
|
+
#===Synopsis
|
362
|
+
#Convert IP netmask into an Integer. Netmask may be in either CIDR (/yy) or
|
363
|
+
#extended (y.y.y.y) format. CIDR formatted netmasks may either
|
364
|
+
#be a String or an Integer. IP version defaults to 4. It may be necessary
|
365
|
+
#to specify the version if an IPv6 netmask of /32 or smaller is provided.
|
366
|
+
#
|
367
|
+
# Example:
|
368
|
+
# packed = NetAddr.pack_ip_netmask('255.255.255.0')
|
369
|
+
# packed = NetAddr.pack_ip_netmask('24')
|
370
|
+
# packed = NetAddr.pack_ip_netmask(24)
|
371
|
+
# packed = NetAddr.pack_ip_netmask('/24')
|
372
|
+
# packed = NetAddr.pack_ip_netmask('32', :Version => 6)
|
373
|
+
#
|
374
|
+
#===Arguments
|
375
|
+
#* Netmask as a String or Integer
|
376
|
+
#* Optional Hash with the following keys:
|
377
|
+
# :Version -- IP version - Integer (optional)
|
378
|
+
#
|
379
|
+
#===Returns:
|
380
|
+
#* Integer
|
381
|
+
#
|
382
|
+
def pack_ip_netmask(netmask, options=nil)
|
383
|
+
known_args = [:Version]
|
384
|
+
all_f = 2**32-1
|
385
|
+
to_validate = {}
|
386
|
+
|
387
|
+
# validate options
|
388
|
+
if (options)
|
389
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
390
|
+
NetAddr.validate_args(options.keys,known_args)
|
391
|
+
|
392
|
+
if (options.has_key?(:Version))
|
393
|
+
version = options[:Version]
|
394
|
+
if (version != 4 && version != 6)
|
395
|
+
raise VersionError, ":Version should be 4 or 6, but was '#{version}'."
|
396
|
+
elsif (version == 6)
|
397
|
+
all_f = 2**128-1
|
398
|
+
else
|
399
|
+
all_f = 2**32-1
|
400
|
+
end
|
401
|
+
to_validate[:Version] = version
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
if (netmask.kind_of?(String))
|
406
|
+
NetAddr.validate_ip_netmask(netmask, to_validate)
|
407
|
+
|
408
|
+
if(netmask =~ /\./)
|
409
|
+
packed_netmask = NetAddr.pack_ip_addr(netmask)
|
410
|
+
|
411
|
+
else
|
412
|
+
# remove '/' if present
|
413
|
+
if (netmask =~ /^\// )
|
414
|
+
netmask[0] = " "
|
415
|
+
netmask.lstrip!
|
416
|
+
end
|
417
|
+
netmask = netmask.to_i
|
418
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
419
|
+
end
|
420
|
+
|
421
|
+
elsif (netmask.kind_of?(Integer))
|
422
|
+
to_validate[:Packed] = true
|
423
|
+
NetAddr.validate_ip_netmask(netmask, to_validate)
|
424
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
425
|
+
|
426
|
+
else
|
427
|
+
raise ArgumentError, "String or Integer expected for argument 'netmask', " +
|
428
|
+
"but #{netmask.class} provided." if (!netmask.kind_of?(Integer) && !netmask.kind_of?(String))
|
429
|
+
end
|
430
|
+
|
431
|
+
return(packed_netmask)
|
432
|
+
end
|
433
|
+
module_function :pack_ip_netmask
|
434
|
+
|
435
|
+
#==============================================================================#
|
436
|
+
# range()
|
437
|
+
#==============================================================================#
|
438
|
+
|
439
|
+
#===Synopsis
|
440
|
+
#Given two CIDR addresses or NetAddr::CIDR objects of the same version,
|
441
|
+
#return all IP addresses between them. NetAddr.range will use the original IP
|
442
|
+
#address passed during the initialization of the NetAddr::CIDR objects, or the
|
443
|
+
#base address of any CIDR addresses passed. The default behavior is to be
|
444
|
+
#non-inclusive (dont include boundaries as part of returned data)
|
445
|
+
#
|
446
|
+
# Example:
|
447
|
+
# list = NetAddr.range(cidr1,cidr2, :Limit => 10)
|
448
|
+
# list = NetAddr.range('192.168.1.0','192.168.1.10', :Inclusive => true)
|
449
|
+
#
|
450
|
+
#===Arguments:
|
451
|
+
#* Lower boundary CIDR as a String or NetAddr::CIDR object
|
452
|
+
#* Upper boundary CIDR as a String or NetAddr::CIDR object
|
453
|
+
#* Optional Hash with the following keys:
|
454
|
+
# :Bitstep -- enumerate in X sized steps - Integer (optional)
|
455
|
+
# :Inclusive -- if true, include boundaries in returned data
|
456
|
+
# :Limit -- limit returned list to X number of items - Integer (optional)
|
457
|
+
# :Objectify -- if true, return CIDR objects (optional)
|
458
|
+
# :Short -- if true, return IPv6 addresses in short-hand notation (optional)
|
459
|
+
#
|
460
|
+
#===Returns:
|
461
|
+
#* Array of Strings, or Array of NetAddr::CIDR objects
|
462
|
+
#
|
463
|
+
def range(cidr1, cidr2, options=nil)
|
464
|
+
known_args = [:Bitstep, :Inclusive, :Limit, :Objectify, :Short]
|
465
|
+
list = []
|
466
|
+
bitstep = 1
|
467
|
+
objectify = false
|
468
|
+
short = false
|
469
|
+
inclusive = false
|
470
|
+
limit = nil
|
471
|
+
|
472
|
+
# if cidr1/cidr2 are not CIDR objects, then attempt to create
|
473
|
+
# cidr objects from them
|
474
|
+
if ( !cidr1.kind_of?(NetAddr::CIDR) )
|
475
|
+
begin
|
476
|
+
cidr1 = NetAddr::CIDR.create(cidr1)
|
477
|
+
rescue Exception => error
|
478
|
+
raise ArgumentError, "Argument 'cidr1' raised the following " +
|
479
|
+
"errors: #{error}"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
if ( !cidr2.kind_of?(NetAddr::CIDR))
|
484
|
+
begin
|
485
|
+
cidr2 = NetAddr::CIDR.create(cidr2)
|
486
|
+
rescue Exception => error
|
487
|
+
raise ArgumentError, "Argument 'cidr2' raised the following " +
|
488
|
+
"errors: #{error}"
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
# validate options
|
493
|
+
if (options)
|
494
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
495
|
+
NetAddr.validate_args(options.keys,known_args)
|
496
|
+
|
497
|
+
if( options.has_key?(:Bitstep) )
|
498
|
+
bitstep = options[:Bitstep]
|
499
|
+
end
|
500
|
+
|
501
|
+
if( options.has_key?(:Objectify) && options[:Objectify] == true )
|
502
|
+
objectify = true
|
503
|
+
end
|
504
|
+
|
505
|
+
if( options.has_key?(:Short) && options[:Short] == true )
|
506
|
+
short = true
|
507
|
+
end
|
508
|
+
|
509
|
+
if( options.has_key?(:Inclusive) && options[:Inclusive] == true )
|
510
|
+
inclusive = true
|
511
|
+
end
|
512
|
+
|
513
|
+
if( options.has_key?(:Limit) )
|
514
|
+
limit = options[:Limit]
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
# check version, store & sort
|
519
|
+
if (cidr1.version == cidr2.version)
|
520
|
+
version = cidr1.version
|
521
|
+
boundaries = [cidr1.packed_ip, cidr2.packed_ip]
|
522
|
+
boundaries.sort
|
523
|
+
else
|
524
|
+
raise VersionError, "Provided NetAddr::CIDR objects are of different IP versions."
|
525
|
+
end
|
526
|
+
|
527
|
+
# dump our range
|
528
|
+
if (!inclusive)
|
529
|
+
my_ip = boundaries[0] + 1
|
530
|
+
end_ip = boundaries[1]
|
531
|
+
else
|
532
|
+
my_ip = boundaries[0]
|
533
|
+
end_ip = boundaries[1] + 1
|
534
|
+
end
|
535
|
+
|
536
|
+
until (my_ip >= end_ip)
|
537
|
+
if (!objectify)
|
538
|
+
my_ip_s = NetAddr.unpack_ip_addr(my_ip, :Version => version)
|
539
|
+
my_ips = NetAddr.shorten(my_ips) if (short && version == 6)
|
540
|
+
list.push(my_ip_s)
|
541
|
+
else
|
542
|
+
list.push( NetAddr::CIDR.create(my_ip, :Version => version) )
|
543
|
+
end
|
544
|
+
|
545
|
+
my_ip = my_ip + bitstep
|
546
|
+
if (limit)
|
547
|
+
limit = limit -1
|
548
|
+
break if (limit == 0)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
return(list)
|
553
|
+
end
|
554
|
+
module_function :range
|
555
|
+
|
556
|
+
#==============================================================================#
|
557
|
+
# shorten()
|
558
|
+
#==============================================================================#
|
559
|
+
|
560
|
+
#===Synopsis
|
561
|
+
#Take a standard IPv6 address, and format it in short-hand notation.
|
562
|
+
#The address should not contain a netmask.
|
563
|
+
#
|
564
|
+
# Example:
|
565
|
+
# short = NetAddr.shorten('fec0:0000:0000:0000:0000:0000:0000:0001')
|
566
|
+
#
|
567
|
+
#===Arguments:
|
568
|
+
#* String
|
569
|
+
#
|
570
|
+
#===Returns:
|
571
|
+
#* String
|
572
|
+
#
|
573
|
+
def shorten(addr)
|
574
|
+
|
575
|
+
# is this a string?
|
576
|
+
if (!addr.kind_of? String)
|
577
|
+
raise ArgumentError, "Expected String, but #{addr.class} provided."
|
578
|
+
end
|
579
|
+
|
580
|
+
validate_ip_addr(addr, :Version => 6)
|
581
|
+
|
582
|
+
# make sure this isnt already shorthand
|
583
|
+
if (addr =~ /::/)
|
584
|
+
return(addr)
|
585
|
+
end
|
586
|
+
|
587
|
+
# split into fields
|
588
|
+
fields = addr.split(":")
|
589
|
+
|
590
|
+
# check last field for ipv4-mapped addr
|
591
|
+
if (fields.last() =~ /\./ )
|
592
|
+
ipv4_mapped = fields.pop()
|
593
|
+
end
|
594
|
+
|
595
|
+
# look for most consecutive '0' fields
|
596
|
+
start_field,end_field = nil,nil
|
597
|
+
start_end = []
|
598
|
+
consecutive,longest = 0,0
|
599
|
+
|
600
|
+
(0..(fields.length-1)).each do |x|
|
601
|
+
fields[x] = fields[x].to_i(16)
|
602
|
+
|
603
|
+
if (fields[x] == 0)
|
604
|
+
if (!start_field)
|
605
|
+
start_field = x
|
606
|
+
end_field = x
|
607
|
+
else
|
608
|
+
end_field = x
|
609
|
+
end
|
610
|
+
consecutive += 1
|
611
|
+
else
|
612
|
+
if (start_field)
|
613
|
+
if (consecutive > longest)
|
614
|
+
longest = consecutive
|
615
|
+
start_end = [start_field,end_field]
|
616
|
+
start_field,end_field = nil,nil
|
617
|
+
end
|
618
|
+
consecutive = 0
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
fields[x] = fields[x].to_s(16)
|
623
|
+
end
|
624
|
+
|
625
|
+
# if our longest set of 0's is at the end, then start & end fields
|
626
|
+
# are already set. if not, then make start & end fields the ones we've
|
627
|
+
# stored away in start_end
|
628
|
+
if (consecutive > longest)
|
629
|
+
longest = consecutive
|
630
|
+
else
|
631
|
+
start_field = start_end[0]
|
632
|
+
end_field = start_end[1]
|
633
|
+
end
|
634
|
+
|
635
|
+
if (longest > 1)
|
636
|
+
fields[start_field] = ''
|
637
|
+
start_field += 1
|
638
|
+
fields.slice!(start_field..end_field)
|
639
|
+
end
|
640
|
+
fields.push(ipv4_mapped) if (ipv4_mapped)
|
641
|
+
short = fields.join(':')
|
642
|
+
short << ':' if (short =~ /:$/)
|
643
|
+
|
644
|
+
return(short)
|
645
|
+
end
|
646
|
+
module_function :shorten
|
647
|
+
|
648
|
+
#==============================================================================#
|
649
|
+
# sort()
|
650
|
+
#==============================================================================#
|
651
|
+
|
652
|
+
#===Synopsis
|
653
|
+
#Given a list of CIDR addresses or NetAddr::CIDR objects,
|
654
|
+
#sort them from lowest to highest by Network/Netmask. NetAddr.sort will use the
|
655
|
+
#original IP address passed during the initialization of any NetAddr::CIDR
|
656
|
+
#objects, or the base address of any CIDR addresses passed
|
657
|
+
#
|
658
|
+
# Example:
|
659
|
+
# sorted = NetAddr.sort([cidr1,cidr2])
|
660
|
+
# sorted = NetAddr.sort(['192.168.1.32/27','192.168.1.0/27','192.168.2.0/24'])
|
661
|
+
#
|
662
|
+
#===Arguments:
|
663
|
+
#* Array of CIDR addresses as Strings, or Array of NetAddr::CIDR objects
|
664
|
+
#
|
665
|
+
#===Returns:
|
666
|
+
#* Array of Strings, or Array of NetAddr::CIDR objects
|
667
|
+
#
|
668
|
+
def sort(list)
|
669
|
+
|
670
|
+
# make sure list is an array
|
671
|
+
if ( !list.kind_of?(Array) )
|
672
|
+
raise ArgumentError, "Array of NetAddr::CIDR or NetStruct " +
|
673
|
+
"objects expected, but #{list.class} provided."
|
674
|
+
end
|
675
|
+
|
676
|
+
# make sure all are valid types of the same IP version
|
677
|
+
version = nil
|
678
|
+
cidr_hash = {}
|
679
|
+
list.each do |cidr|
|
680
|
+
if (!cidr.kind_of?(NetAddr::CIDR))
|
681
|
+
begin
|
682
|
+
new_cidr = NetAddr::CIDR.create(cidr)
|
683
|
+
rescue Exception => error
|
684
|
+
raise ArgumentError, "An element of the provided Array " +
|
685
|
+
"raised the following errors: #{error}"
|
686
|
+
end
|
687
|
+
else
|
688
|
+
new_cidr = cidr
|
689
|
+
end
|
690
|
+
cidr_hash[new_cidr] = cidr
|
691
|
+
|
692
|
+
version = new_cidr.version if (!version)
|
693
|
+
unless (new_cidr.version == version)
|
694
|
+
raise VersionError, "Provided CIDR addresses must all be of the same IP version."
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
# sort by network. if networks are equal, sort by netmask.
|
699
|
+
sorted_list = []
|
700
|
+
cidr_hash.each_key do |entry|
|
701
|
+
index = 0
|
702
|
+
sorted_list.each do
|
703
|
+
if(entry.packed_network < (sorted_list[index]).packed_network)
|
704
|
+
break
|
705
|
+
elsif (entry.packed_network == (sorted_list[index]).packed_network)
|
706
|
+
if (entry.packed_netmask < (sorted_list[index]).packed_netmask)
|
707
|
+
break
|
708
|
+
end
|
709
|
+
end
|
710
|
+
index += 1
|
711
|
+
end
|
712
|
+
sorted_list.insert(index, entry)
|
713
|
+
end
|
714
|
+
|
715
|
+
# return original values passed
|
716
|
+
ret_list = []
|
717
|
+
sorted_list.each {|x| ret_list.push(cidr_hash[x])}
|
718
|
+
|
719
|
+
return(ret_list)
|
720
|
+
end
|
721
|
+
module_function :sort
|
722
|
+
|
723
|
+
#==============================================================================#
|
724
|
+
# unpack_ip_addr()
|
725
|
+
#==============================================================================#
|
726
|
+
|
727
|
+
#===Synopsis
|
728
|
+
#Unack a packed IP address back into a printable string. Will attempt
|
729
|
+
#to auto-detect IP version if not provided.
|
730
|
+
#
|
731
|
+
# Example:
|
732
|
+
# unpacked = NetAddr.unpack_ip_addr(3232235906)
|
733
|
+
# unpacked = NetAddr.unpack_ip_addr(packed, :Version => 6)
|
734
|
+
#
|
735
|
+
#===Arguments:
|
736
|
+
#* Packed IP address as an Integer
|
737
|
+
#* Optional Hash with the following keys:
|
738
|
+
# :Version -- IP version - Integer (optional)
|
739
|
+
# :IPv4Mapped -- if true, unpack IPv6 as an IPv4 mapped address (optional)
|
740
|
+
#
|
741
|
+
#===Returns:
|
742
|
+
#* String
|
743
|
+
#
|
744
|
+
def unpack_ip_addr(packed_ip, options=nil)
|
745
|
+
known_args = [:Version, :IPv4Mapped]
|
746
|
+
ipv4_mapped = false
|
747
|
+
to_validate = {}
|
748
|
+
|
749
|
+
# validate options
|
750
|
+
if (options)
|
751
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
752
|
+
NetAddr.validate_args(options.keys,known_args)
|
753
|
+
|
754
|
+
if (options.has_key?(:Version))
|
755
|
+
version = options[:Version]
|
756
|
+
to_validate[:Version] = version
|
757
|
+
if (version != 4 && version != 6)
|
758
|
+
raise VersionError, ":Version should be 4 or 6, but was '#{version}'."
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
if (options.has_key?(:IPv4Mapped) && options[:IPv4Mapped] == true)
|
763
|
+
ipv4_mapped = true
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
# validate
|
768
|
+
raise ArgumentError, "Integer expected for argument 'packed_ip', " +
|
769
|
+
"but #{packed_ip.class} provided." if (!packed_ip.kind_of?(Integer))
|
770
|
+
NetAddr.validate_ip_addr(packed_ip, to_validate)
|
771
|
+
|
772
|
+
# set version if not set
|
773
|
+
if (!version)
|
774
|
+
if (packed_ip < 2**32)
|
775
|
+
version = 4
|
776
|
+
else
|
777
|
+
version = 6
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
if (version == 4)
|
782
|
+
octets = []
|
783
|
+
4.times do
|
784
|
+
octet = packed_ip & 0xFF
|
785
|
+
octets.unshift(octet.to_s)
|
786
|
+
packed_ip = packed_ip >> 8
|
787
|
+
end
|
788
|
+
ip = octets.join('.')
|
789
|
+
else
|
790
|
+
fields = []
|
791
|
+
if (!ipv4_mapped)
|
792
|
+
loop_count = 8
|
793
|
+
else
|
794
|
+
loop_count = 6
|
795
|
+
packed_v4 = packed_ip & 0xffffffff
|
796
|
+
ipv4_addr = NetAddr.unpack_ip_addr(packed_v4, :Version => 4)
|
797
|
+
fields.unshift(ipv4_addr)
|
798
|
+
packed_ip = packed_ip >> 32
|
799
|
+
end
|
800
|
+
|
801
|
+
loop_count.times do
|
802
|
+
octet = packed_ip & 0xFFFF
|
803
|
+
octet = octet.to_s(16)
|
804
|
+
packed_ip = packed_ip >> 16
|
805
|
+
|
806
|
+
# if octet < 4 characters, then pad with 0's
|
807
|
+
(4 - octet.length).times do
|
808
|
+
octet = '0' << octet
|
809
|
+
end
|
810
|
+
fields.unshift(octet)
|
811
|
+
end
|
812
|
+
ip = fields.join(':')
|
813
|
+
end
|
814
|
+
|
815
|
+
|
816
|
+
return(ip)
|
817
|
+
end
|
818
|
+
module_function :unpack_ip_addr
|
819
|
+
|
820
|
+
#==============================================================================#
|
821
|
+
# unpack_ip_netmask()
|
822
|
+
#==============================================================================#
|
823
|
+
|
824
|
+
#===Synopsis
|
825
|
+
#Unpack a packed IP netmask into an Integer representing the number of
|
826
|
+
#bits in the CIDR mask.
|
827
|
+
#
|
828
|
+
# Example:
|
829
|
+
# unpacked = NetAddr.unpack_ip_netmask(0xfffffffe)
|
830
|
+
#
|
831
|
+
#===Arguments:
|
832
|
+
#* Packed netmask as an Integer
|
833
|
+
#
|
834
|
+
#===Returns:
|
835
|
+
#* Integer
|
836
|
+
#
|
837
|
+
def unpack_ip_netmask(packed_netmask)
|
838
|
+
|
839
|
+
# validate packed_netmask
|
840
|
+
raise ArgumentError, "Integer expected for argument 'packed_netmask', " +
|
841
|
+
"but #{packed_netmask.class} provided." if (!packed_netmask.kind_of?(Integer))
|
842
|
+
|
843
|
+
if (packed_netmask < 2**32)
|
844
|
+
mask = 32
|
845
|
+
NetAddr.validate_ip_netmask(packed_netmask, :Packed => true, :Version => 4)
|
846
|
+
else
|
847
|
+
NetAddr.validate_ip_netmask(packed_netmask, :Packed => true, :Version => 6)
|
848
|
+
mask = 128
|
849
|
+
end
|
850
|
+
|
851
|
+
|
852
|
+
mask.times do
|
853
|
+
if ( (packed_netmask & 1) == 1)
|
854
|
+
break
|
855
|
+
end
|
856
|
+
packed_netmask = packed_netmask >> 1
|
857
|
+
mask = mask - 1
|
858
|
+
end
|
859
|
+
|
860
|
+
return(mask)
|
861
|
+
end
|
862
|
+
module_function :unpack_ip_netmask
|
863
|
+
|
864
|
+
#==============================================================================#
|
865
|
+
# unshorten()
|
866
|
+
#==============================================================================#
|
867
|
+
|
868
|
+
#===Synopsis
|
869
|
+
#Take an IPv6 address in short-hand format, and expand it into standard
|
870
|
+
#notation. The address should not contain a netmask.
|
871
|
+
#
|
872
|
+
# Example:
|
873
|
+
# long = NetAddr.unshorten('fec0::1')
|
874
|
+
#
|
875
|
+
#===Arguments:
|
876
|
+
#* CIDR address as a String
|
877
|
+
#
|
878
|
+
#===Returns:
|
879
|
+
#* String
|
880
|
+
#
|
881
|
+
def unshorten(ip)
|
882
|
+
|
883
|
+
# is this a string?
|
884
|
+
if (!ip.kind_of? String)
|
885
|
+
raise ArgumentError, "Expected String, but #{ip.class} provided."
|
886
|
+
end
|
887
|
+
|
888
|
+
validate_ip_addr(ip, :Version => 6)
|
889
|
+
ipv4_mapped = true if (ip =~ /\./)
|
890
|
+
|
891
|
+
packed = pack_ip_addr(ip, :Version => 6)
|
892
|
+
if (!ipv4_mapped)
|
893
|
+
long = unpack_ip_addr(packed, :Version => 6)
|
894
|
+
else
|
895
|
+
long = unpack_ip_addr(packed, :Version => 6, :IPv4Mapped => true)
|
896
|
+
end
|
897
|
+
|
898
|
+
return(long)
|
899
|
+
end
|
900
|
+
module_function :unshorten
|
901
|
+
|
902
|
+
#==============================================================================#
|
903
|
+
# validate_eui()
|
904
|
+
#==============================================================================#
|
905
|
+
|
906
|
+
#===Synopsis
|
907
|
+
#Validate an EUI-48 or EUI-64 address. Raises NetAddr::ValidationError on validation failure.
|
908
|
+
#
|
909
|
+
# Example:
|
910
|
+
# NetAddr.validate_eui('01-00-5e-12-34-56')
|
911
|
+
#
|
912
|
+
# - Arguments
|
913
|
+
#* EUI address as a String
|
914
|
+
#
|
915
|
+
#===Returns:
|
916
|
+
#* True
|
917
|
+
#
|
918
|
+
def validate_eui(eui)
|
919
|
+
if (eui.kind_of?(String))
|
920
|
+
# check for invalid characters
|
921
|
+
if (eui =~ /[^0-9a-fA-f\.\-\:]/)
|
922
|
+
raise ValidationError, "#{eui} is invalid (contains invalid characters)."
|
923
|
+
end
|
924
|
+
|
925
|
+
# split on formatting characters & check lengths
|
926
|
+
if (eui =~ /\-/)
|
927
|
+
fields = eui.split('-')
|
928
|
+
if (fields.length != 6 && fields.length != 8)
|
929
|
+
raise ValidationError, "#{eui} is invalid (unrecognized formatting)."
|
930
|
+
end
|
931
|
+
fields.each {|x| raise ValidationError, "#{eui} is invalid (missing characters)." if (x.length != 2)}
|
932
|
+
elsif (eui =~ /\:/)
|
933
|
+
fields = eui.split(':')
|
934
|
+
if (fields.length != 6 && fields.length != 8)
|
935
|
+
raise ValidationError, "#{eui} is invalid (unrecognized formatting)."
|
936
|
+
end
|
937
|
+
fields.each {|x| raise ValidationError, "#{eui} is invalid (missing characters)." if (x.length != 2)}
|
938
|
+
elsif (eui =~ /\./)
|
939
|
+
fields = eui.split('.')
|
940
|
+
if (fields.length != 3 && fields.length != 4)
|
941
|
+
raise ValidationError, "#{eui} is invalid (unrecognized formatting)."
|
942
|
+
end
|
943
|
+
fields.each {|x| raise ValidationError, "#{eui} is invalid (missing characters)." if (x.length != 4)}
|
944
|
+
else
|
945
|
+
raise ValidationError, "#{eui} is invalid (unrecognized formatting)."
|
946
|
+
end
|
947
|
+
|
948
|
+
else
|
949
|
+
raise ArgumentError, "EUI address should be a String, but was a#{eui.class}."
|
950
|
+
end
|
951
|
+
return(true)
|
952
|
+
end
|
953
|
+
module_function :validate_eui
|
954
|
+
|
955
|
+
#==============================================================================#
|
956
|
+
# validate_ip_addr()
|
957
|
+
#==============================================================================#
|
958
|
+
|
959
|
+
#===Synopsis
|
960
|
+
#Validate an IP address. The address should not contain a netmask.
|
961
|
+
#IP version will attempt to be auto-detected if not provided.
|
962
|
+
#Raises NetAddr::ValidationError on validation failure.
|
963
|
+
#
|
964
|
+
# Example:
|
965
|
+
# NetAddr.validate_ip_addr('192.168.1.1')
|
966
|
+
# NetAddr.validate_ip_addr('ffff::1', :Version => 6)
|
967
|
+
# NetAddr.validate_ip_addr('::192.168.1.1')
|
968
|
+
# NetAddr.validate_ip_addr(0xFFFFFF)
|
969
|
+
# NetAddr.validate_ip_addr(2**128-1)
|
970
|
+
# NetAddr.validate_ip_addr(2**32-1, :Version => 4)
|
971
|
+
#
|
972
|
+
#===Arguments
|
973
|
+
#* IP address as a String or Integer
|
974
|
+
#* Optional Hash with the following keys:
|
975
|
+
# :Version -- IP version - Integer (optional)
|
976
|
+
#
|
977
|
+
#===Returns:
|
978
|
+
#* True
|
979
|
+
#
|
980
|
+
def validate_ip_addr(ip, options=nil)
|
981
|
+
known_args = [:Version]
|
982
|
+
|
983
|
+
# validate options
|
984
|
+
if (options)
|
985
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
986
|
+
NetAddr.validate_args(options.keys,known_args)
|
987
|
+
|
988
|
+
if (options.has_key?(:Version))
|
989
|
+
version = options[:Version]
|
990
|
+
if (version != 4 && version != 6)
|
991
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
992
|
+
end
|
993
|
+
end
|
994
|
+
end
|
995
|
+
|
996
|
+
if ( ip.kind_of?(String) )
|
997
|
+
|
998
|
+
# check validity of charaters
|
999
|
+
if (ip =~ /[^0-9a-fA-F\.:]/)
|
1000
|
+
raise ValidationError, "#{ip} is invalid (contains invalid characters)."
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
# determine version if not specified
|
1004
|
+
if (!version && (ip =~ /\./ && ip !~ /:/ ) )
|
1005
|
+
version = 4
|
1006
|
+
elsif (!version && ip =~ /:/)
|
1007
|
+
version = 6
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
if (version == 4)
|
1011
|
+
octets = ip.split('.')
|
1012
|
+
raise ValidationError, "#{ip} is invalid (IPv4 requires (4) octets)." if (octets.length != 4)
|
1013
|
+
|
1014
|
+
# are octets in range 0..255?
|
1015
|
+
octets.each do |octet|
|
1016
|
+
raise ValidationError, "#{ip} is invalid (IPv4 dotted-decimal format " +
|
1017
|
+
"should not contain non-numeric characters)." if (octet =~ /[^0-9]/ )
|
1018
|
+
octet = octet.to_i()
|
1019
|
+
if ( (octet < 0) || (octet >= 256) )
|
1020
|
+
raise ValidationError, "#{ip} is invalid (IPv4 octets should be between 0 and 255)."
|
1021
|
+
end
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
elsif (version == 6)
|
1025
|
+
# make sure we only have at most (2) colons in a row, and then only
|
1026
|
+
# (1) instance of that
|
1027
|
+
if ( (ip =~ /:{3,}/) || (ip.split("::").length > 2) )
|
1028
|
+
raise ValidationError, "#{ip} is invalid (IPv6 field separators (:) are bad)."
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
# set flags
|
1032
|
+
shorthand = false
|
1033
|
+
if (ip =~ /\./)
|
1034
|
+
dotted_dec = true
|
1035
|
+
else
|
1036
|
+
dotted_dec = false
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
# split up by ':'
|
1040
|
+
fields = []
|
1041
|
+
if (ip =~ /::/)
|
1042
|
+
shorthand = true
|
1043
|
+
ip.split('::').each do |x|
|
1044
|
+
fields.concat( x.split(':') )
|
1045
|
+
end
|
1046
|
+
else
|
1047
|
+
fields.concat( ip.split(':') )
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
# make sure we have the correct number of fields
|
1051
|
+
if (shorthand)
|
1052
|
+
if ( (dotted_dec && fields.length > 6) || (!dotted_dec && fields.length > 7) )
|
1053
|
+
raise ValidationError, "#{ip} is invalid (IPv6 shorthand notation has " +
|
1054
|
+
"incorrect number of fields)."
|
1055
|
+
end
|
1056
|
+
else
|
1057
|
+
if ( (dotted_dec && fields.length != 7 ) || (!dotted_dec && fields.length != 8) )
|
1058
|
+
raise ValidationError, "#{ip} is invalid (IPv6 address has " +
|
1059
|
+
"incorrect number of fields)."
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
# if dotted_dec then validate the last field
|
1064
|
+
if (dotted_dec)
|
1065
|
+
dotted = fields.pop()
|
1066
|
+
octets = dotted.split('.')
|
1067
|
+
raise ValidationError, "#{ip} is invalid (Legacy IPv4 portion of IPv6 " +
|
1068
|
+
"address should contain (4) octets)." if (octets.length != 4)
|
1069
|
+
octets.each do |x|
|
1070
|
+
raise ValidationError, "#{ip} is invalid (egacy IPv4 portion of IPv6 " +
|
1071
|
+
"address should not contain non-numeric characters)." if (x =~ /[^0-9]/ )
|
1072
|
+
x = x.to_i
|
1073
|
+
if ( (x < 0) || (x >= 256) )
|
1074
|
+
raise ValidationError, "#{ip} is invalid (Octets of a legacy IPv4 portion of IPv6 " +
|
1075
|
+
"address should be between 0 and 255)."
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
# validate hex fields
|
1081
|
+
fields.each do |x|
|
1082
|
+
if (x =~ /[^0-9a-fA-F]/)
|
1083
|
+
raise ValidationError, "#{ip} is invalid (IPv6 address contains invalid hex characters)."
|
1084
|
+
else
|
1085
|
+
x = x.to_i(16)
|
1086
|
+
if ( (x < 0) || (x >= 2**16) )
|
1087
|
+
raise ValidationError, "#{ip} is invalid (Fields of an IPv6 address " +
|
1088
|
+
"should be between 0x0 and 0xFFFF)."
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
else
|
1094
|
+
raise ValidationError, "#{ip} is invalid (Did you mean to pass an Integer instead of a String?)."
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
elsif ( ip.kind_of?(Integer) )
|
1098
|
+
if (version && version == 4)
|
1099
|
+
raise ValidationError, "#{ip} is invalid for IPv4 (Integer is out of bounds)." if ( (ip < 0) || (ip > 2**32-1) )
|
1100
|
+
else
|
1101
|
+
raise ValidationError, "#{ip} is invalid for IPv6 (Integer is out of bounds)." if ( (ip < 0) || (ip > 2**128-1) )
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
else
|
1105
|
+
raise ArgumentError, "Integer or String expected for argument 'ip' but " +
|
1106
|
+
"#{ip.class} provided." if (!ip.kind_of?(String) && !ip.kind_of?(Integer))
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
|
1110
|
+
return(true)
|
1111
|
+
end
|
1112
|
+
module_function :validate_ip_addr
|
1113
|
+
|
1114
|
+
#==============================================================================#
|
1115
|
+
# validate_ip_netmask()
|
1116
|
+
#==============================================================================#
|
1117
|
+
|
1118
|
+
#===Synopsis
|
1119
|
+
#Validate IP Netmask.Version defaults to 4 if not specified.
|
1120
|
+
#Raises NetAddr::ValidationError on validation failure.
|
1121
|
+
#
|
1122
|
+
# Examples:
|
1123
|
+
# NetAddr.validate_ip_netmask('/32')
|
1124
|
+
# NetAddr.validate_ip_netmask(32)
|
1125
|
+
# NetAddr.validate_ip_netmask(0xffffffff, :Packed => true)
|
1126
|
+
#
|
1127
|
+
#===Arguments:
|
1128
|
+
#* Netmask as a String or Integer
|
1129
|
+
#* Optional Hash with the following keys:
|
1130
|
+
# :Packed -- if true, the provided Netmask is a packed Integer
|
1131
|
+
# :Version -- IP version - Integer (optional)
|
1132
|
+
#
|
1133
|
+
#===Returns:
|
1134
|
+
#* True
|
1135
|
+
#
|
1136
|
+
def validate_ip_netmask(netmask, options=nil)
|
1137
|
+
known_args = [:Packed, :Version]
|
1138
|
+
packed = false
|
1139
|
+
version = 4
|
1140
|
+
max_bits = 32
|
1141
|
+
|
1142
|
+
# validate options
|
1143
|
+
if (options)
|
1144
|
+
raise ArgumentError, "Hash expected for argument 'options' but #{options.class} provided." if (!options.kind_of?(Hash))
|
1145
|
+
NetAddr.validate_args(options.keys,known_args)
|
1146
|
+
|
1147
|
+
if (options.has_key?(:Packed) && options[:Packed] == true)
|
1148
|
+
packed = true
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
if (options.has_key?(:Version))
|
1152
|
+
version = options[:Version]
|
1153
|
+
if (version != 4 && version != 6)
|
1154
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
1155
|
+
elsif (version == 6)
|
1156
|
+
max_bits = 128
|
1157
|
+
else
|
1158
|
+
max_bits = 32
|
1159
|
+
end
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
if (netmask.kind_of?(String))
|
1164
|
+
if(netmask =~ /\./)
|
1165
|
+
all_f = 2**32-1
|
1166
|
+
packed_netmask = 0
|
1167
|
+
|
1168
|
+
# validate & pack extended mask
|
1169
|
+
begin
|
1170
|
+
validate_ip_addr(netmask)
|
1171
|
+
packed_netmask = pack_ip_addr(netmask)
|
1172
|
+
rescue Exception
|
1173
|
+
raise ValidationError, "#{netmask} is an improperly formed IPv4 address."
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
# cycle through the bits of hostmask and compare
|
1177
|
+
# with packed_mask. when we hit the firt '1' within
|
1178
|
+
# packed_mask (our netmask boundary), xor hostmask and
|
1179
|
+
# packed_mask. the result should be all 1's. this whole
|
1180
|
+
# process is in place to make sure that we dont have
|
1181
|
+
# and crazy masks such as 255.254.255.0
|
1182
|
+
hostmask = 1
|
1183
|
+
32.times do
|
1184
|
+
check = packed_netmask & hostmask
|
1185
|
+
if ( check != 0)
|
1186
|
+
hostmask = hostmask >> 1
|
1187
|
+
unless ( (packed_netmask ^ hostmask) == all_f)
|
1188
|
+
raise ValidationError, "#{netmask} contains '1' bits within the host portion of the netmask."
|
1189
|
+
end
|
1190
|
+
break
|
1191
|
+
else
|
1192
|
+
hostmask = hostmask << 1
|
1193
|
+
hostmask = hostmask | 1
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
else
|
1198
|
+
# remove '/' if present
|
1199
|
+
if (netmask =~ /^\// )
|
1200
|
+
netmask[0] = " "
|
1201
|
+
netmask.lstrip!
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
# check if we have any non numeric characters
|
1205
|
+
if (netmask =~ /\D/)
|
1206
|
+
raise ValidationError, "#{netmask} contains invalid characters."
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
netmask = netmask.to_i
|
1210
|
+
if (netmask > max_bits || netmask == 0 )
|
1211
|
+
raise ValidationError, "Netmask, #{netmask}, is out of bounds for IPv#{version}."
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
elsif (netmask.kind_of?(Integer) )
|
1217
|
+
if (!packed)
|
1218
|
+
if (netmask > max_bits || netmask == 0 )
|
1219
|
+
raise ValidationError, "Netmask, #{netmask}, is out of bounds for IPv#{version}."
|
1220
|
+
end
|
1221
|
+
else
|
1222
|
+
if (netmask >= 2**max_bits || netmask == 0 )
|
1223
|
+
raise ValidationError, "Packed netmask, #{netmask}, is out of bounds for IPv#{version}."
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
else
|
1228
|
+
raise ArgumentError, "Integer or String expected for argument 'netmask' but " +
|
1229
|
+
"#{netmask.class} provided." if (!netmask.kind_of?(String) && !netmask.kind_of?(Integer))
|
1230
|
+
end
|
1231
|
+
|
1232
|
+
return(true)
|
1233
|
+
end
|
1234
|
+
module_function :validate_ip_netmask
|
1235
|
+
|
1236
|
+
#==============================================================================#
|
1237
|
+
# NetStruct
|
1238
|
+
#==============================================================================#
|
1239
|
+
|
1240
|
+
#===NetStruct
|
1241
|
+
# Struct object used internally by NetAddr. It is not likely directly useful
|
1242
|
+
# to anyone.
|
1243
|
+
#
|
1244
|
+
# Description of fields:
|
1245
|
+
# * cidr - NetAddr::CIDR object
|
1246
|
+
# * parent - parent NetStruct in tree
|
1247
|
+
# * children - Array of children NetStruct objects
|
1248
|
+
#
|
1249
|
+
NetStruct = Struct.new(:cidr, :parent, :children)
|
1250
|
+
|
1251
|
+
|
1252
|
+
# PRIVATE METHODS
|
1253
|
+
private
|
1254
|
+
|
1255
|
+
|
1256
|
+
#==============================================================================#
|
1257
|
+
# validate_args()
|
1258
|
+
#==============================================================================#
|
1259
|
+
|
1260
|
+
# validate options hash
|
1261
|
+
#
|
1262
|
+
def validate_args(to_validate,known_args)
|
1263
|
+
to_validate.each do |x|
|
1264
|
+
raise ArgumentError, "Unrecognized argument #{x}. Valid arguments are " +
|
1265
|
+
"#{known_args.join(',')}" if (!known_args.include?(x))
|
1266
|
+
end
|
1267
|
+
end
|
1268
|
+
module_function :validate_args
|
1269
|
+
|
1270
|
+
end # module NetAddr
|
1271
|
+
|
1272
|
+
__END__
|
1273
|
+
|