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/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
+