ipadmin 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +77 -13
- data/lib/cidr.rb +1459 -0
- data/lib/ip_admin.rb +3 -3682
- data/lib/methods.rb +2168 -0
- data/lib/tree.rb +735 -0
- data/tests/cidr_test.rb +34 -2
- data/tests/methods_test.rb +151 -102
- metadata +5 -2
data/lib/methods.rb
ADDED
@@ -0,0 +1,2168 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Copyright (c) 2006 Dustin Spinhirne -
|
3
|
+
Licensed under the same terms as Ruby, No Warranty is provided.
|
4
|
+
=end
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
module IPAdmin
|
9
|
+
|
10
|
+
#==============================================================================#
|
11
|
+
# compare()
|
12
|
+
#==============================================================================#
|
13
|
+
|
14
|
+
# Compare CIDR or NetStruct objects, and determine if one
|
15
|
+
# is the supernet of the other.
|
16
|
+
#
|
17
|
+
# - Arguments:
|
18
|
+
# * Two CIDR or NetStruct objects
|
19
|
+
#
|
20
|
+
# - Returns:
|
21
|
+
# * if one object is a subnet of another, then return an array in order of
|
22
|
+
# [supernet,subnet]
|
23
|
+
# * if both are equal, return 1
|
24
|
+
# * if neither is a supernet of the other, return nil
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
# supernet,subnet = IPAdmin.compare(cidr1,cidr2)
|
28
|
+
#
|
29
|
+
|
30
|
+
def compare(obj1,obj2)
|
31
|
+
ret_val = nil
|
32
|
+
|
33
|
+
# validate arguments
|
34
|
+
if ( !obj1.kind_of?(IPAdmin::CIDR) &&
|
35
|
+
!obj1.kind_of?(IPAdmin::NetStruct) )
|
36
|
+
raise ArgumentError, "Expected IPAdmin::CIDR or NetStruct " +
|
37
|
+
"as first argument, but #{obj1.class} provided."
|
38
|
+
end
|
39
|
+
|
40
|
+
if ( !obj2.kind_of?(IPAdmin::CIDR) &&
|
41
|
+
!obj2.kind_of?(IPAdmin::NetStruct) )
|
42
|
+
raise ArgumentError, "Expected IPAdmin::CIDR or NetStruct " +
|
43
|
+
"as second argument, but #{obj2.class} provided."
|
44
|
+
end
|
45
|
+
|
46
|
+
# make sure both are same version
|
47
|
+
if (obj1.version != obj2.version )
|
48
|
+
raise ArgumentError, "Provided objects are of different IP versions."
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# get network/netmask of each
|
53
|
+
objects = [obj1,obj2]
|
54
|
+
networks = []
|
55
|
+
netmasks = []
|
56
|
+
for obj in objects
|
57
|
+
if ( obj.kind_of?(IPAdmin::CIDR) )
|
58
|
+
networks.push(obj.packed_network)
|
59
|
+
netmasks.push(obj.packed_netmask)
|
60
|
+
|
61
|
+
elsif ( obj.kind_of?(IPAdmin::NetStruct) )
|
62
|
+
networks.push(obj.network)
|
63
|
+
netmasks.push(obj.netmask)
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# return 1's if objects are equal otherwise
|
69
|
+
# whichever netmask is smaller will be the supernet.
|
70
|
+
# if we '&' both networks by the supernet, and they are
|
71
|
+
# equal, then the supernet is the parent of the other network
|
72
|
+
if ( (networks[0] == networks[1]) && (netmasks[0] == netmasks[1]) )
|
73
|
+
ret_val = 1
|
74
|
+
elsif (netmasks[0] < netmasks[1])
|
75
|
+
if ( (netmasks[0] & networks[0]) == (netmasks[0] & networks[1]) )
|
76
|
+
ret_val = [obj1,obj2]
|
77
|
+
end
|
78
|
+
elsif (netmasks[1] < netmasks[0])
|
79
|
+
if ( (netmasks[1] & networks[0]) == (netmasks[1] & networks[1]) )
|
80
|
+
ret_val = [obj2,obj1]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return(ret_val)
|
85
|
+
end
|
86
|
+
module_function :compare
|
87
|
+
|
88
|
+
#======================================#
|
89
|
+
#
|
90
|
+
#======================================#
|
91
|
+
|
92
|
+
|
93
|
+
#==============================================================================#
|
94
|
+
# create_net_struct()
|
95
|
+
#==============================================================================#
|
96
|
+
|
97
|
+
# Create a NetStruct object from an CIDR or NetStruct object.
|
98
|
+
# This type of Struct is used internally for various tasks, and is not likely
|
99
|
+
# to be useful to anyone.
|
100
|
+
#
|
101
|
+
# - Arguments:
|
102
|
+
# * CIDR or NetStruct object
|
103
|
+
#
|
104
|
+
# - Returns:
|
105
|
+
# * NetStruct object
|
106
|
+
#
|
107
|
+
# Example:
|
108
|
+
# net_struct = IPAdmin.create_net_struct(object)
|
109
|
+
#
|
110
|
+
def create_net_struct(object)
|
111
|
+
|
112
|
+
if ( object.kind_of?(IPAdmin::CIDR) )
|
113
|
+
network = object.packed_ip
|
114
|
+
netmask = object.packed_netmask
|
115
|
+
|
116
|
+
elsif ( object.kind_of?(IPAdmin::NetStruct) )
|
117
|
+
network = object.network
|
118
|
+
netmask = object.netmask
|
119
|
+
else
|
120
|
+
raise ArgumentError, "Expected IPAdmin::CIDR or NetStruct "+
|
121
|
+
"object, but #{object.class} provided."
|
122
|
+
end
|
123
|
+
|
124
|
+
version = object.version
|
125
|
+
net_struct = NetStruct.new(network,netmask,version,object,[])
|
126
|
+
|
127
|
+
return(net_struct)
|
128
|
+
end
|
129
|
+
module_function :create_net_struct
|
130
|
+
|
131
|
+
#======================================#
|
132
|
+
#
|
133
|
+
#======================================#
|
134
|
+
|
135
|
+
|
136
|
+
#==============================================================================#
|
137
|
+
# merge()
|
138
|
+
#==============================================================================#
|
139
|
+
|
140
|
+
# Given a list of CIDR or NetStruct objects
|
141
|
+
# merge (supernet) them in the most efficient way possible. Supernetting
|
142
|
+
# will only occur when the newly created supernet will not result in the
|
143
|
+
# 'creation' of additional space. For example the following blocks
|
144
|
+
# (192.168.0.0/24, 192.168.1.0/24, and 192.168.2.0/24) would merge into
|
145
|
+
# 192.168.0.0/23 and 192.168.2.0/24 rather than into 192.168.0.0/22
|
146
|
+
#
|
147
|
+
# - Arguments:
|
148
|
+
# * Hash with the following fields:
|
149
|
+
# - :List -- Array of CIDR or NetStruct objects
|
150
|
+
# - :NetStruct -- if true, return IPAdmin::NetStruct objects (optional)
|
151
|
+
# - :Objectify -- if true, return IPAdmin::CIDR objects (optional)
|
152
|
+
# - :Short -- if true, return IPv6 addresses in short-hand notation (optional)
|
153
|
+
#
|
154
|
+
# - Returns:
|
155
|
+
# * Array of CIDR or NetStruct objects
|
156
|
+
#
|
157
|
+
# Example:
|
158
|
+
# supernets = IPAdmin.merge(:List => list)
|
159
|
+
#
|
160
|
+
def merge(options)
|
161
|
+
version = nil
|
162
|
+
short = false
|
163
|
+
objectify = false
|
164
|
+
|
165
|
+
# validate options
|
166
|
+
if ( !options.kind_of?(Hash) )
|
167
|
+
raise ArgumentError, "Hash expected but #{options.class} provided."
|
168
|
+
end
|
169
|
+
|
170
|
+
if (!options.has_key?(:List))
|
171
|
+
raise ArgumentError, "Missing argument: List."
|
172
|
+
end
|
173
|
+
|
174
|
+
if (options.has_key?(:Short))
|
175
|
+
short = true
|
176
|
+
end
|
177
|
+
|
178
|
+
if (options.has_key?(:Objectify))
|
179
|
+
objectify = true
|
180
|
+
end
|
181
|
+
|
182
|
+
list = options[:List]
|
183
|
+
ret_struct = 1 if (options.has_key?(:NetStruct))
|
184
|
+
|
185
|
+
# make sure list is an array
|
186
|
+
if ( !list.kind_of?(Array) )
|
187
|
+
raise ArgumentError, "Expected Array for option :List, " +
|
188
|
+
"but #{list.class} provided."
|
189
|
+
end
|
190
|
+
|
191
|
+
# make sure all are valid types of the same IP version
|
192
|
+
list.each do |obj|
|
193
|
+
if (!obj.kind_of?(IPAdmin::CIDR) && !obj.kind_of?(IPAdmin::NetStruct) )
|
194
|
+
raise ArgumentError, "Expected IPAdmin::CIDR or NetStruct " +
|
195
|
+
"object but #{obj.class} provided."
|
196
|
+
end
|
197
|
+
|
198
|
+
version = obj.version if (!version)
|
199
|
+
if (!obj.version == version)
|
200
|
+
raise "Provided objects must be of the same IP version."
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# make all_f
|
205
|
+
if (version == 4)
|
206
|
+
all_f = 2**32 - 1
|
207
|
+
else
|
208
|
+
all_f = 2**128 - 1
|
209
|
+
end
|
210
|
+
|
211
|
+
# make sure that our list is in order, and contains no duplicates
|
212
|
+
list = IPAdmin.sort(list)
|
213
|
+
index = 0
|
214
|
+
(list.length).times do
|
215
|
+
if ((index > 0)&&(IPAdmin.compare(list[index],list[index-1]) == 1))
|
216
|
+
list.delete_at(index)
|
217
|
+
else
|
218
|
+
index += 1
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# create supernet_list from list
|
223
|
+
supernet_list = []
|
224
|
+
list.each do |obj|
|
225
|
+
if (obj.kind_of?(IPAdmin::CIDR))
|
226
|
+
supernet_list.push(IPAdmin.create_net_struct(obj))
|
227
|
+
|
228
|
+
elsif ( obj.kind_of?(IPAdmin::NetStruct) )
|
229
|
+
if (obj.subnets && obj.subnets.length > 0)
|
230
|
+
# get all child subnets of this branch entry
|
231
|
+
children = merge(:List => obj.subnets, :NetStruct => 1)
|
232
|
+
|
233
|
+
# if any child subnets are equal to the parent subnet
|
234
|
+
# then copy the grandchild subnets and delete the child
|
235
|
+
children.each do |child|
|
236
|
+
if ( (obj.network == child.network) &&
|
237
|
+
(obj.netmask == child.netmask) )
|
238
|
+
if (child.subnets && child.subnets.length > 0)
|
239
|
+
grandchildren = child.subnets
|
240
|
+
children.concat(grandchildren)
|
241
|
+
end
|
242
|
+
children.delete(child)
|
243
|
+
children = IPAdmin.sort(children)
|
244
|
+
break
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
obj.subnets.clear
|
249
|
+
obj.subnets.concat(children)
|
250
|
+
end
|
251
|
+
|
252
|
+
supernet_list.push(obj)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# merge subnets of this new branch by removing them from 'supernet_list',
|
257
|
+
# and categorizing them into hash of arrays (key=netmask)
|
258
|
+
# within each categorization we merge contiguous subnets
|
259
|
+
# and then remove them from that category & put them back into
|
260
|
+
# 'supernet_list'. we do this until our list stops getting any shorter
|
261
|
+
categories = {}
|
262
|
+
supernet_list_length = 0
|
263
|
+
until (supernet_list_length == supernet_list.length)
|
264
|
+
supernet_list_length = supernet_list.length
|
265
|
+
|
266
|
+
# categorize
|
267
|
+
supernet_list.each do |entry|
|
268
|
+
netmask = entry.netmask
|
269
|
+
if (categories.has_key?(netmask) )
|
270
|
+
(categories[netmask]).push(entry)
|
271
|
+
else
|
272
|
+
categories[netmask] = [entry]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
supernet_list.clear
|
276
|
+
|
277
|
+
categories.each_key do |netmask|
|
278
|
+
entries = categories[netmask]
|
279
|
+
bitstep = (all_f + 1) - netmask
|
280
|
+
|
281
|
+
# this entire process depends on the list being in order
|
282
|
+
until (entries.length == 0)
|
283
|
+
to_merge = []
|
284
|
+
multiplier = 1
|
285
|
+
first = entries[0]
|
286
|
+
num_required = 2**multiplier
|
287
|
+
supermask = (netmask << multiplier) & all_f
|
288
|
+
supernet = supermask & first.network
|
289
|
+
if (first.network == supernet)
|
290
|
+
# take first entry and use it to form a base
|
291
|
+
# supernet address. this supernet should have
|
292
|
+
# x number of subnets in it, so we look for
|
293
|
+
# those subnets & if found store them
|
294
|
+
expected = first.network
|
295
|
+
entries.each do |entry|
|
296
|
+
if ( (entry.network == expected) && (first.network == supernet) )
|
297
|
+
to_merge.push(entry)
|
298
|
+
expected = expected + bitstep
|
299
|
+
if ( (to_merge.length == num_required) &&
|
300
|
+
(entries.length > num_required ) )
|
301
|
+
multiplier += 1
|
302
|
+
num_required = 2**multiplier
|
303
|
+
supermask = (netmask << multiplier) & all_f
|
304
|
+
supernet = supermask & first.network
|
305
|
+
end
|
306
|
+
else
|
307
|
+
# if entry is a duplicate, then kill it
|
308
|
+
if (IPAdmin.compare(entry,to_merge.last) == 1)
|
309
|
+
entries.delete(entry)
|
310
|
+
end
|
311
|
+
break
|
312
|
+
end
|
313
|
+
end
|
314
|
+
else
|
315
|
+
to_merge.push(first)
|
316
|
+
end
|
317
|
+
|
318
|
+
# make sure we actually have all of our subnets
|
319
|
+
# create our new supernet
|
320
|
+
if (to_merge.length != num_required)
|
321
|
+
multiplier -= 1
|
322
|
+
supermask = (netmask << multiplier) & all_f
|
323
|
+
supernet = supermask & first.network
|
324
|
+
end
|
325
|
+
net_struct = NetStruct.new(supernet,supermask,version,nil,[])
|
326
|
+
|
327
|
+
# now that we have the child members of our new supernet
|
328
|
+
# take any grandchild subnets that they may have and
|
329
|
+
# add them to the new supernet. then kill the children
|
330
|
+
(2**multiplier).times do
|
331
|
+
to_kill = to_merge.shift
|
332
|
+
net_struct.subnets.concat(to_kill.subnets) if (to_kill.subnets)
|
333
|
+
entries.delete(to_kill)
|
334
|
+
end
|
335
|
+
supernet_list.push(net_struct)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
categories.clear
|
339
|
+
supernet_list = IPAdmin.sort(supernet_list)
|
340
|
+
end
|
341
|
+
|
342
|
+
# if '!ret_struct', return CIDR's
|
343
|
+
if (!ret_struct)
|
344
|
+
supernets = []
|
345
|
+
supernet_list.each do |entry|
|
346
|
+
if (!objectify)
|
347
|
+
network = IPAdmin.unpack_ip_addr(:Integer => entry.network, :Version => version)
|
348
|
+
network = IPAdmin.shorten(network) if (short && version == 6)
|
349
|
+
netmask = IPAdmin.unpack_ip_netmask(:Integer => entry.netmask, :Version => version)
|
350
|
+
supernets.push("#{network}/#{netmask}")
|
351
|
+
else
|
352
|
+
supernets.push(IPAdmin::CIDR.new(:PackedIP => entry.network,
|
353
|
+
:PackedNetmask => entry.netmask,
|
354
|
+
:Version => version))
|
355
|
+
end
|
356
|
+
end
|
357
|
+
else
|
358
|
+
supernets = supernet_list
|
359
|
+
end
|
360
|
+
|
361
|
+
return(supernets)
|
362
|
+
end
|
363
|
+
module_function :merge
|
364
|
+
|
365
|
+
#======================================#
|
366
|
+
#
|
367
|
+
#======================================#
|
368
|
+
|
369
|
+
|
370
|
+
#==============================================================================#
|
371
|
+
# minimum_size()
|
372
|
+
#==============================================================================#
|
373
|
+
|
374
|
+
# Given the number of IP addresses required in a subnet, return the minimum
|
375
|
+
# netmask required for that subnet.
|
376
|
+
#
|
377
|
+
# - Arguments:
|
378
|
+
# * Hash with the following fields:
|
379
|
+
# - :IPCount -- the number of IP addresses required - Integer
|
380
|
+
# - :Version -- IP version - Integer(optional)
|
381
|
+
#
|
382
|
+
# - Returns:
|
383
|
+
# * Integer
|
384
|
+
#
|
385
|
+
# - Notes:
|
386
|
+
# * Version is assumed to be 4 unless specified otherwise.
|
387
|
+
#
|
388
|
+
# Example:
|
389
|
+
# netmask = IPAdmin.minumum_size(:IPCount => 14) -> 28
|
390
|
+
#
|
391
|
+
def minimum_size(options)
|
392
|
+
version = 4
|
393
|
+
|
394
|
+
if ( !options.kind_of?(Hash) )
|
395
|
+
raise ArgumentError, "Hash expected but #{options.class} provided."
|
396
|
+
end
|
397
|
+
|
398
|
+
if ( !options.has_key?(:IPCount) )
|
399
|
+
raise ArgumentError, "Missing argument: List."
|
400
|
+
end
|
401
|
+
ipcount = options[:IPCount]
|
402
|
+
|
403
|
+
if (options.has_key?(:Version))
|
404
|
+
version = options[:Version]
|
405
|
+
end
|
406
|
+
|
407
|
+
if (version == 4)
|
408
|
+
max_bits = 32
|
409
|
+
else
|
410
|
+
max_bits = 128
|
411
|
+
end
|
412
|
+
|
413
|
+
|
414
|
+
if (ipcount > 2**max_bits)
|
415
|
+
raise "Required IP count exceeds number of IP addresses available " +
|
416
|
+
"for IPv#{version}."
|
417
|
+
end
|
418
|
+
|
419
|
+
|
420
|
+
bits_needed = 0
|
421
|
+
until (2**bits_needed >= ipcount)
|
422
|
+
bits_needed += 1
|
423
|
+
end
|
424
|
+
subnet_bits = max_bits - bits_needed
|
425
|
+
|
426
|
+
return(subnet_bits)
|
427
|
+
end
|
428
|
+
module_function :minimum_size
|
429
|
+
|
430
|
+
#======================================#
|
431
|
+
#
|
432
|
+
#======================================#
|
433
|
+
|
434
|
+
|
435
|
+
#==============================================================================#
|
436
|
+
# pack_ip_addr()
|
437
|
+
#==============================================================================#
|
438
|
+
|
439
|
+
# Convert IP addresses into an integer. No attempt at
|
440
|
+
# validation is performed.
|
441
|
+
#
|
442
|
+
# - Arguments:
|
443
|
+
# * Hash with the following fields:
|
444
|
+
# - :IP -- IP address - String
|
445
|
+
# - :Version -- IP version - Integer
|
446
|
+
#
|
447
|
+
# - Returns:
|
448
|
+
# * Integer
|
449
|
+
#
|
450
|
+
# - Notes:
|
451
|
+
# * Will attempt to auto-detect IP version if not provided
|
452
|
+
#
|
453
|
+
# Example:
|
454
|
+
# pack_ip_addr(IP => '192.168.1.1')
|
455
|
+
# pack_ip_addr(IP => 'ffff::1')
|
456
|
+
# pack_ip_addr(IP => '::192.168.1.1')
|
457
|
+
#
|
458
|
+
def pack_ip_addr(options)
|
459
|
+
|
460
|
+
if (!options.kind_of?(Hash))
|
461
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
462
|
+
end
|
463
|
+
|
464
|
+
if (!options.has_key?(:IP))
|
465
|
+
raise ArgumentError, "Missing argument: IP."
|
466
|
+
end
|
467
|
+
ip = options[:IP]
|
468
|
+
|
469
|
+
if (options.has_key?(:Version))
|
470
|
+
version = options[:Version]
|
471
|
+
if (version != 4 && version != 6)
|
472
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
if ( ip.kind_of?(String) )
|
478
|
+
|
479
|
+
# determine version if not provided
|
480
|
+
if (!version)
|
481
|
+
if ( ip =~ /\./ && ip !~ /:/ )
|
482
|
+
version = 4
|
483
|
+
else
|
484
|
+
version = 6
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
packed_ip = 0
|
489
|
+
if ( version == 4)
|
490
|
+
octets = ip.split('.')
|
491
|
+
(0..3).each do |x|
|
492
|
+
octet = octets.pop.to_i
|
493
|
+
octet = octet << 8*x
|
494
|
+
packed_ip = packed_ip | octet
|
495
|
+
end
|
496
|
+
|
497
|
+
else
|
498
|
+
# if ipv4-mapped ipv6 addr
|
499
|
+
if (ip =~ /\./)
|
500
|
+
dotted_dec = true
|
501
|
+
end
|
502
|
+
|
503
|
+
# split up by ':'
|
504
|
+
fields = []
|
505
|
+
if (ip =~ /::/)
|
506
|
+
shrthnd = ip.split( /::/ )
|
507
|
+
if (shrthnd.length == 0)
|
508
|
+
return(0)
|
509
|
+
else
|
510
|
+
first_half = shrthnd[0].split( /:/ ) if (shrthnd[0])
|
511
|
+
sec_half = shrthnd[1].split( /:/ ) if (shrthnd[1])
|
512
|
+
first_half = [] if (!first_half)
|
513
|
+
sec_half = [] if (!sec_half)
|
514
|
+
end
|
515
|
+
missing_fields = 8 - first_half.length - sec_half.length
|
516
|
+
missing_fields -= 1 if dotted_dec
|
517
|
+
fields = fields.concat(first_half)
|
518
|
+
missing_fields.times {fields.push('0')}
|
519
|
+
fields = fields.concat(sec_half)
|
520
|
+
|
521
|
+
else
|
522
|
+
fields = ip.split(':')
|
523
|
+
end
|
524
|
+
|
525
|
+
if (dotted_dec)
|
526
|
+
ipv4_addr = fields.pop
|
527
|
+
packed_v4 = IPAdmin.pack_ip_addr(:IP => ipv4_addr, :Version => 4)
|
528
|
+
octets = []
|
529
|
+
2.times do
|
530
|
+
octet = packed_v4 & 0xFFFF
|
531
|
+
octets.unshift(octet.to_s(16))
|
532
|
+
packed_v4 = packed_v4 >> 16
|
533
|
+
end
|
534
|
+
fields.concat(octets)
|
535
|
+
end
|
536
|
+
|
537
|
+
# pack
|
538
|
+
(0..7).each do |x|
|
539
|
+
field = fields.pop.to_i(16)
|
540
|
+
field = field << 16*x
|
541
|
+
packed_ip = packed_ip | field
|
542
|
+
end
|
543
|
+
|
544
|
+
end
|
545
|
+
|
546
|
+
else
|
547
|
+
raise ArgumentError, "Argument :IP should be a String, but is a #{ip.class}."
|
548
|
+
end
|
549
|
+
|
550
|
+
return(packed_ip)
|
551
|
+
end
|
552
|
+
module_function :pack_ip_addr
|
553
|
+
|
554
|
+
#======================================#
|
555
|
+
#
|
556
|
+
#======================================#
|
557
|
+
|
558
|
+
|
559
|
+
#==============================================================================#
|
560
|
+
# pack_ip_netmask()
|
561
|
+
#==============================================================================#
|
562
|
+
|
563
|
+
# Convert IP netmask into an integer. No validation is performed. Netmask
|
564
|
+
# may be in either CIDR (/yy) or extended (y.y.y.y) format. CIDR formatted
|
565
|
+
# netmasks may either be a String or an Integer.
|
566
|
+
#
|
567
|
+
# - Arguments
|
568
|
+
# * Hash with the following fields:
|
569
|
+
# - :Netmask -- Netmask - String or Integer
|
570
|
+
# - :Version -- IP version - Integer (optional)
|
571
|
+
#
|
572
|
+
# - Returns:
|
573
|
+
# * Integer
|
574
|
+
#
|
575
|
+
# - Notes:
|
576
|
+
# * Version defaults to 4
|
577
|
+
#
|
578
|
+
# Example:
|
579
|
+
# packed = IPAdmin.pack_ip_netmask(Netmask => '255.255.255.0')
|
580
|
+
# packed = IPAdmin.pack_ip_netmask(Netmask => '24')
|
581
|
+
# packed = IPAdmin.pack_ip_netmask(Netmask => 24)
|
582
|
+
# packed = IPAdmin.pack_ip_netmask(Netmask => '/24')
|
583
|
+
# packed = IPAdmin.pack_ip_netmask(Netmask => '64', :Version => 6)
|
584
|
+
#
|
585
|
+
def pack_ip_netmask(options)
|
586
|
+
if (!options.kind_of?(Hash))
|
587
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
588
|
+
end
|
589
|
+
|
590
|
+
if (!options.has_key?(:Netmask))
|
591
|
+
raise ArgumentError, "Missing argument: Netmask."
|
592
|
+
end
|
593
|
+
netmask = options[:Netmask]
|
594
|
+
|
595
|
+
if (options.has_key?(:Version))
|
596
|
+
version = options[:Version]
|
597
|
+
if (version != 4 && version != 6)
|
598
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
599
|
+
elsif (version == 6)
|
600
|
+
all_f = 2**128-1
|
601
|
+
else
|
602
|
+
all_f = 2**32-1
|
603
|
+
end
|
604
|
+
else
|
605
|
+
all_f = 2**32-1
|
606
|
+
end
|
607
|
+
|
608
|
+
|
609
|
+
if (netmask.kind_of?(String))
|
610
|
+
if(netmask =~ /\./)
|
611
|
+
packed_netmask = IPAdmin.pack_ip_addr(:IP => netmask)
|
612
|
+
|
613
|
+
else
|
614
|
+
# remove '/' if present
|
615
|
+
if (netmask =~ /^\// )
|
616
|
+
netmask[0] = " "
|
617
|
+
netmask.lstrip!
|
618
|
+
end
|
619
|
+
netmask = netmask.to_i
|
620
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
621
|
+
end
|
622
|
+
elsif (netmask.kind_of?(Integer))
|
623
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
624
|
+
|
625
|
+
else
|
626
|
+
raise ArgumentError, "Argument :Netmask should be a String or Integer, but is a #{netmask.class}."
|
627
|
+
|
628
|
+
end
|
629
|
+
|
630
|
+
return(packed_netmask)
|
631
|
+
end
|
632
|
+
module_function :pack_ip_netmask
|
633
|
+
|
634
|
+
#======================================#
|
635
|
+
#
|
636
|
+
#======================================#
|
637
|
+
|
638
|
+
|
639
|
+
#==============================================================================#
|
640
|
+
# range()
|
641
|
+
#==============================================================================#
|
642
|
+
|
643
|
+
# Given two IPAdmin::CIDR objects of the same version, return all IP
|
644
|
+
# addresses between them (non-inclusive).
|
645
|
+
#
|
646
|
+
# - Arguments:
|
647
|
+
# * Hash with the following fields:
|
648
|
+
# - :Bitstep -- enumerate in X sized steps - Integer (optional)
|
649
|
+
# - :Boundaries -- array of (2) CIDR objects
|
650
|
+
# - :Limit -- limit returned list to X number of items - Integer (optional)
|
651
|
+
# - :Objectify -- if true, return CIDR objects (optional)
|
652
|
+
# - :Short -- if true, return IPv6 addresses in short-hand notation (optional)
|
653
|
+
#
|
654
|
+
# - Returns:
|
655
|
+
# * Array of Strings or CIDR objects
|
656
|
+
#
|
657
|
+
# - Notes:
|
658
|
+
# * IPAdmin.range will use the original IP address passed during the initialization
|
659
|
+
# of the CIDR objects. This IP can be found using the CIDR.ip() method.
|
660
|
+
#
|
661
|
+
# Example:
|
662
|
+
# list = IPAdmin.range(:Boundaries => [cidr1,cidr2])
|
663
|
+
#
|
664
|
+
def range(options)
|
665
|
+
list = []
|
666
|
+
bitstep = 1
|
667
|
+
objectify = false
|
668
|
+
short = false
|
669
|
+
limit = nil
|
670
|
+
|
671
|
+
# check options
|
672
|
+
if (options)
|
673
|
+
if ( !options.has_key?(:Boundaries) )
|
674
|
+
raise ArgumentError, "Missing argument: Boundaries."
|
675
|
+
end
|
676
|
+
|
677
|
+
if (options[:Boundaries].length == 2)
|
678
|
+
(cidr1,cidr2) = options[:Boundaries]
|
679
|
+
else
|
680
|
+
raise ArgumentError, "Two IPAdmin::CIDR ojects are required. " +
|
681
|
+
"as Boundaries."
|
682
|
+
end
|
683
|
+
|
684
|
+
if( options.has_key?(:Bitstep) )
|
685
|
+
bitstep = options[:Bitstep]
|
686
|
+
end
|
687
|
+
|
688
|
+
if( options.has_key?(:Objectify) )
|
689
|
+
objectify = true
|
690
|
+
end
|
691
|
+
|
692
|
+
if( options.has_key?(:Short) )
|
693
|
+
short = true
|
694
|
+
end
|
695
|
+
|
696
|
+
if( options.has_key?(:Limit) )
|
697
|
+
limit = options[:Limit]
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
# check our objects
|
702
|
+
if (!cidr1.kind_of?(IPAdmin::CIDR) && !cidr2.kind_of?(IPAdmin::CIDR))
|
703
|
+
raise ArgumentError, "One or more values provided under :Boundaries "+
|
704
|
+
"is not a valid IPAdmin::CIDR object."
|
705
|
+
end
|
706
|
+
|
707
|
+
# check version, store & sort
|
708
|
+
if (cidr1.version == cidr2.version)
|
709
|
+
version = cidr1.version
|
710
|
+
boundaries = [cidr1.packed_ip, cidr2.packed_ip]
|
711
|
+
boundaries.sort
|
712
|
+
else
|
713
|
+
raise ArgumentError, "Provided IPAdmin::CIDR objects are of different IP versions."
|
714
|
+
end
|
715
|
+
|
716
|
+
# dump our range
|
717
|
+
my_ip = boundaries[0] + 1
|
718
|
+
until (my_ip >= boundaries[1])
|
719
|
+
if (!objectify)
|
720
|
+
my_ip_s = IPAdmin.unpack_ip_addr(:Integer => my_ip, :Version => version)
|
721
|
+
my_ips = IPAdmin.shorten(my_ips) if (short && version == 6)
|
722
|
+
list.push(my_ip_s)
|
723
|
+
else
|
724
|
+
list.push( IPAdmin::CIDR.new(:PackedIP => my_ip, :Version => version) )
|
725
|
+
end
|
726
|
+
|
727
|
+
my_ip = my_ip + bitstep
|
728
|
+
if (limit)
|
729
|
+
limit = limit -1
|
730
|
+
break if (limit == 0)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
return(list)
|
735
|
+
end
|
736
|
+
module_function :range
|
737
|
+
|
738
|
+
#======================================#
|
739
|
+
#
|
740
|
+
#======================================#
|
741
|
+
|
742
|
+
|
743
|
+
#==============================================================================#
|
744
|
+
# shorten()
|
745
|
+
#==============================================================================#
|
746
|
+
|
747
|
+
# Take a standard IPv6 address, and format it in short-hand notation.
|
748
|
+
# The address should not contain a netmask.
|
749
|
+
#
|
750
|
+
# - Arguments:
|
751
|
+
# * String
|
752
|
+
#
|
753
|
+
# - Returns:
|
754
|
+
# * String
|
755
|
+
#
|
756
|
+
# Example:
|
757
|
+
# short = IPAdmin.shorten('fec0:0000:0000:0000:0000:0000:0000:0001') --> 'fec0::1'
|
758
|
+
#
|
759
|
+
def shorten(addr)
|
760
|
+
|
761
|
+
# is this a string?
|
762
|
+
if (!addr.kind_of? String)
|
763
|
+
raise ArgumentError, "Expected String, but #{addr.class} provided."
|
764
|
+
end
|
765
|
+
|
766
|
+
validate_ip_addr(:IP => addr, :Version => 6)
|
767
|
+
|
768
|
+
# make sure this isnt already shorthand
|
769
|
+
if (addr =~ /::/)
|
770
|
+
return(addr)
|
771
|
+
end
|
772
|
+
|
773
|
+
# split into fields
|
774
|
+
fields = addr.split(":")
|
775
|
+
|
776
|
+
# check last field for ipv4-mapped addr
|
777
|
+
if (fields.last() =~ /\./ )
|
778
|
+
ipv4_mapped = fields.pop()
|
779
|
+
end
|
780
|
+
|
781
|
+
# look for most consecutive '0' fields
|
782
|
+
start_field,end_field = nil,nil
|
783
|
+
start_end = []
|
784
|
+
consecutive,longest = 0,0
|
785
|
+
|
786
|
+
(0..(fields.length-1)).each do |x|
|
787
|
+
fields[x] = fields[x].to_i(16)
|
788
|
+
|
789
|
+
if (fields[x] == 0)
|
790
|
+
if (!start_field)
|
791
|
+
start_field = x
|
792
|
+
end_field = x
|
793
|
+
else
|
794
|
+
end_field = x
|
795
|
+
end
|
796
|
+
consecutive += 1
|
797
|
+
else
|
798
|
+
if (start_field)
|
799
|
+
if (consecutive > longest)
|
800
|
+
longest = consecutive
|
801
|
+
start_end = [start_field,end_field]
|
802
|
+
start_field,end_field = nil,nil
|
803
|
+
end
|
804
|
+
consecutive = 0
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
fields[x] = fields[x].to_s(16)
|
809
|
+
end
|
810
|
+
|
811
|
+
# if our longest set of 0's is at the end, then start & end fields
|
812
|
+
# are already set. if not, then make start & end fields the ones we've
|
813
|
+
# stored away in start_end
|
814
|
+
if (consecutive > longest)
|
815
|
+
longest = consecutive
|
816
|
+
else
|
817
|
+
start_field = start_end[0]
|
818
|
+
end_field = start_end[1]
|
819
|
+
end
|
820
|
+
|
821
|
+
if (longest > 1)
|
822
|
+
fields[start_field] = ''
|
823
|
+
start_field += 1
|
824
|
+
fields.slice!(start_field..end_field)
|
825
|
+
end
|
826
|
+
fields.push(ipv4_mapped) if (ipv4_mapped)
|
827
|
+
short = fields.join(':')
|
828
|
+
short << ':' if (short =~ /:$/)
|
829
|
+
|
830
|
+
return(short)
|
831
|
+
end
|
832
|
+
module_function :shorten
|
833
|
+
|
834
|
+
#======================================#
|
835
|
+
#
|
836
|
+
#======================================#
|
837
|
+
|
838
|
+
|
839
|
+
#==============================================================================#
|
840
|
+
# sort()
|
841
|
+
#==============================================================================#
|
842
|
+
|
843
|
+
# Given a list of IPAdmin::CIDR or NetStruct objects
|
844
|
+
# sort them from lowest to highest by Network/Netmask.
|
845
|
+
#
|
846
|
+
# - Arguments:
|
847
|
+
# * Array of CIDR or NetStruct objects
|
848
|
+
#
|
849
|
+
# - Returns:
|
850
|
+
# * Array
|
851
|
+
#
|
852
|
+
# - Notes:
|
853
|
+
# * IPAdmin.sort will use the original IP address passed during the initialization
|
854
|
+
# of the CIDR objects. This IP can be found using the CIDR.ip() method.
|
855
|
+
#
|
856
|
+
# Example:
|
857
|
+
# sorted = IPAdmin.sort(list)
|
858
|
+
#
|
859
|
+
def sort(list)
|
860
|
+
|
861
|
+
# make sure list is an array
|
862
|
+
if ( !list.kind_of?(Array) )
|
863
|
+
raise ArgumentError, "Array of IPAdmin::CIDR or NetStruct " +
|
864
|
+
"objects expected, but #{list.class} provided."
|
865
|
+
end
|
866
|
+
|
867
|
+
# make sure all are valid types of the same IP version
|
868
|
+
version = nil
|
869
|
+
list.each do |obj|
|
870
|
+
unless (obj.kind_of?(IPAdmin::CIDR) || obj.kind_of?(IPAdmin::NetStruct) )
|
871
|
+
raise ArgumentError, "Expected IPAdmin::CIDR or NetStruct " +
|
872
|
+
"object but #{obj.class} provided."
|
873
|
+
end
|
874
|
+
|
875
|
+
version = obj.version if (!version)
|
876
|
+
unless (obj.version == version)
|
877
|
+
raise "Provided objects must all be of the same IP version."
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
# create unsorted_list from list
|
882
|
+
unsorted_list = []
|
883
|
+
list.each do |obj|
|
884
|
+
unsorted_list.push(IPAdmin.create_net_struct(obj))
|
885
|
+
end
|
886
|
+
|
887
|
+
# sort by network. if networks are equal, sort by netmask.
|
888
|
+
sorted_list = []
|
889
|
+
unsorted_list.each do |entry|
|
890
|
+
index = 0
|
891
|
+
sorted_list.each do
|
892
|
+
if(entry.network < (sorted_list[index]).network)
|
893
|
+
break
|
894
|
+
elsif (entry.network == (sorted_list[index]).network)
|
895
|
+
if (entry.netmask < (sorted_list[index]).netmask)
|
896
|
+
break
|
897
|
+
end
|
898
|
+
end
|
899
|
+
index += 1
|
900
|
+
end
|
901
|
+
sorted_list.insert(index, entry)
|
902
|
+
end
|
903
|
+
|
904
|
+
# replace sorted_list entries with their .object
|
905
|
+
index = 0
|
906
|
+
sorted_list.length.times do
|
907
|
+
sorted_list[index] = (sorted_list[index]).object
|
908
|
+
index += 1
|
909
|
+
end
|
910
|
+
|
911
|
+
return(sorted_list)
|
912
|
+
end
|
913
|
+
module_function :sort
|
914
|
+
|
915
|
+
#======================================#
|
916
|
+
#
|
917
|
+
#======================================#
|
918
|
+
|
919
|
+
|
920
|
+
#==============================================================================#
|
921
|
+
# unpack_ip_addr()
|
922
|
+
#==============================================================================#
|
923
|
+
|
924
|
+
# Unack a packed IP address back into a printable string. No attempt at
|
925
|
+
# validation is performed.
|
926
|
+
#
|
927
|
+
# - Arguments:
|
928
|
+
# * Hash with the following fields:
|
929
|
+
# - :Integer -- Integer representaion of an IP address
|
930
|
+
# - :Version -- IP version - Integer (optional)
|
931
|
+
# - :IPv4Mapped -- if true, unpack IPv6 as an IPv4 mapped address (optional)
|
932
|
+
#
|
933
|
+
# - Returns:
|
934
|
+
# * String
|
935
|
+
#
|
936
|
+
# - Notes:
|
937
|
+
# * IP version will attempt to be auto-detected if not provided
|
938
|
+
#
|
939
|
+
# Example:
|
940
|
+
# unpacked = IPAdmin.unpack_ip_addr(:Integer => packed)
|
941
|
+
#
|
942
|
+
def unpack_ip_addr(options)
|
943
|
+
ipv4_mapped = false
|
944
|
+
|
945
|
+
if (!options.kind_of?(Hash))
|
946
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
947
|
+
end
|
948
|
+
|
949
|
+
if (!options.has_key?(:Integer))
|
950
|
+
raise ArgumentError, "Missing argument: Integer."
|
951
|
+
end
|
952
|
+
packed_ip = options[:Integer]
|
953
|
+
|
954
|
+
if (!packed_ip.kind_of?(Integer))
|
955
|
+
raise ArgumentError, "Expected Integer, but #{options.class} provided for argument: Integer."
|
956
|
+
end
|
957
|
+
|
958
|
+
if (options.has_key?(:Version))
|
959
|
+
version = options[:Version]
|
960
|
+
if (version != 4 && version != 6)
|
961
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
965
|
+
if (options.has_key?(:IPv4Mapped) && options[:IPv4Mapped] == true)
|
966
|
+
ipv4_mapped = true
|
967
|
+
end
|
968
|
+
|
969
|
+
# set version if not set
|
970
|
+
if (!version)
|
971
|
+
if (packed_ip < 2**32)
|
972
|
+
version = 4
|
973
|
+
else
|
974
|
+
version = 6
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
if (version == 4)
|
979
|
+
octets = []
|
980
|
+
4.times do
|
981
|
+
octet = packed_ip & 0xFF
|
982
|
+
octets.unshift(octet.to_s)
|
983
|
+
packed_ip = packed_ip >> 8
|
984
|
+
end
|
985
|
+
ip = octets.join('.')
|
986
|
+
else
|
987
|
+
fields = []
|
988
|
+
if (!ipv4_mapped)
|
989
|
+
loop_count = 8
|
990
|
+
else
|
991
|
+
loop_count = 6
|
992
|
+
packed_v4 = packed_ip & 0xffffffff
|
993
|
+
ipv4_addr = IPAdmin.unpack_ip_addr(:Integer => packed_v4, :Version => 4)
|
994
|
+
fields.unshift(ipv4_addr)
|
995
|
+
packed_ip = packed_ip >> 32
|
996
|
+
end
|
997
|
+
|
998
|
+
loop_count.times do
|
999
|
+
octet = packed_ip & 0xFFFF
|
1000
|
+
octet = octet.to_s(16)
|
1001
|
+
packed_ip = packed_ip >> 16
|
1002
|
+
|
1003
|
+
# if octet < 4 characters, then pad with 0's
|
1004
|
+
(4 - octet.length).times do
|
1005
|
+
octet = '0' << octet
|
1006
|
+
end
|
1007
|
+
fields.unshift(octet)
|
1008
|
+
end
|
1009
|
+
ip = fields.join(':')
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
|
1013
|
+
return(ip)
|
1014
|
+
end
|
1015
|
+
module_function :unpack_ip_addr
|
1016
|
+
|
1017
|
+
#======================================#
|
1018
|
+
#
|
1019
|
+
#======================================#
|
1020
|
+
|
1021
|
+
|
1022
|
+
#==============================================================================#
|
1023
|
+
# unpack_ip_netmask()
|
1024
|
+
#==============================================================================#
|
1025
|
+
|
1026
|
+
# Unack a packed IP netmask into a integer representing the number of
|
1027
|
+
# bits in the CIDR mask. No attempt at validation is performed.
|
1028
|
+
#
|
1029
|
+
# - Arguments:
|
1030
|
+
# * Hash with the following fields:
|
1031
|
+
# - :Integer -- Integer representation of an IP Netmask
|
1032
|
+
#
|
1033
|
+
# - Returns:
|
1034
|
+
# * Integer
|
1035
|
+
#
|
1036
|
+
# Example:
|
1037
|
+
# unpacked = IPAdmin.unpack_ip_netmask(:Integer => packed)
|
1038
|
+
#
|
1039
|
+
def unpack_ip_netmask(options)
|
1040
|
+
if (!options.kind_of?(Hash))
|
1041
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
if (!options.has_key?(:Integer))
|
1045
|
+
raise ArgumentError, "Missing argument: Integer."
|
1046
|
+
end
|
1047
|
+
packed_netmask = options[:Integer]
|
1048
|
+
|
1049
|
+
if (!packed_netmask.kind_of?(Integer))
|
1050
|
+
raise ArgumentError, "Argument :Integer should be an Integer, but is a #{packed_netmask.class}."
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
if (packed_netmask < 2**32)
|
1054
|
+
mask = 32
|
1055
|
+
else
|
1056
|
+
mask = 128
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
mask.times do
|
1060
|
+
if ( (packed_netmask & 1) == 1)
|
1061
|
+
break
|
1062
|
+
end
|
1063
|
+
packed_netmask = packed_netmask >> 1
|
1064
|
+
mask = mask - 1
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
return(mask)
|
1068
|
+
end
|
1069
|
+
module_function :unpack_ip_netmask
|
1070
|
+
|
1071
|
+
#======================================#
|
1072
|
+
#
|
1073
|
+
#======================================#
|
1074
|
+
|
1075
|
+
|
1076
|
+
#==============================================================================#
|
1077
|
+
# unshorten()
|
1078
|
+
#==============================================================================#
|
1079
|
+
|
1080
|
+
# Take an IPv6 address in short-hand format, and expand it into standard
|
1081
|
+
# notation. The address should not contain a netmask.
|
1082
|
+
#
|
1083
|
+
# - Arguments:
|
1084
|
+
# * String
|
1085
|
+
#
|
1086
|
+
# - Returns:
|
1087
|
+
# * String
|
1088
|
+
#
|
1089
|
+
# Example:
|
1090
|
+
# long = IPAdmin.unshorten('fec0::1') --> 'fec0:0000:0000:0000:0000:0000:0000:0001'
|
1091
|
+
#
|
1092
|
+
def unshorten(addr)
|
1093
|
+
|
1094
|
+
# is this a string?
|
1095
|
+
if (!addr.kind_of? String)
|
1096
|
+
raise ArgumentError, "Expected String, but #{addr.class} provided."
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
validate_ip_addr(:IP => addr, :Version => 6)
|
1100
|
+
ipv4_mapped = true if (addr =~ /\./)
|
1101
|
+
|
1102
|
+
packed = pack_ip_addr(:IP => addr, :Version => 6)
|
1103
|
+
if (!ipv4_mapped)
|
1104
|
+
long = unpack_ip_addr(:Integer => packed, :Version => 6)
|
1105
|
+
else
|
1106
|
+
long = unpack_ip_addr(:Integer => packed, :Version => 6, :IPv4Mapped => true)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
return(long)
|
1110
|
+
end
|
1111
|
+
module_function :unshorten
|
1112
|
+
|
1113
|
+
#======================================#
|
1114
|
+
#
|
1115
|
+
#======================================#
|
1116
|
+
|
1117
|
+
|
1118
|
+
#==============================================================================#
|
1119
|
+
# validate_ip_addr()
|
1120
|
+
#==============================================================================#
|
1121
|
+
|
1122
|
+
# Validate an IP address. The address should not contain a netmask.
|
1123
|
+
#
|
1124
|
+
# - Arguments
|
1125
|
+
# * Hash with the following fields:
|
1126
|
+
# - :IP -- IP address to validate - String or Integer
|
1127
|
+
# - :Version -- IP version - Integer (optional)
|
1128
|
+
#
|
1129
|
+
# - Returns:
|
1130
|
+
# * True
|
1131
|
+
#
|
1132
|
+
# - Notes:
|
1133
|
+
# * IP version will attempt to be auto-detected if not provided
|
1134
|
+
#
|
1135
|
+
# Example:
|
1136
|
+
# validate_ip_addr(IP => '192.168.1.1')
|
1137
|
+
# validate_ip_addr(IP => 'ffff::1')
|
1138
|
+
# validate_ip_addr(IP => '::192.168.1.1')
|
1139
|
+
# validate_ip_addr(IP => 0xFFFFFF)
|
1140
|
+
# validate_ip_addr(IP => 2**128-1)
|
1141
|
+
# validate_ip_addr(IP => 2**32-1, :Version => 4)
|
1142
|
+
#
|
1143
|
+
def validate_ip_addr(options)
|
1144
|
+
|
1145
|
+
if (!options.kind_of?(Hash))
|
1146
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
if (!options.has_key?(:IP))
|
1150
|
+
raise ArgumentError, "Missing argument: IP."
|
1151
|
+
end
|
1152
|
+
ip = options[:IP]
|
1153
|
+
|
1154
|
+
if (options.has_key?(:Version))
|
1155
|
+
version = options[:Version]
|
1156
|
+
if (version != 4 && version != 6)
|
1157
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
if ( ip.kind_of?(String) )
|
1162
|
+
|
1163
|
+
# check validity of charaters
|
1164
|
+
if (ip =~ /[^0-9a-fA-F\.:]/)
|
1165
|
+
raise "#{ip} is invalid (contains invalid characters)."
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
# determine version if not specified
|
1169
|
+
if (!version && (ip =~ /\./ && ip !~ /:/ ) )
|
1170
|
+
version = 4
|
1171
|
+
elsif (!version && ip =~ /:/)
|
1172
|
+
version = 6
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
if (version == 4)
|
1176
|
+
octets = ip.split('.')
|
1177
|
+
raise "#{ip} is invalid (IPv4 requires (4) octets)." if (octets.length != 4)
|
1178
|
+
|
1179
|
+
# are octets in range 0..255?
|
1180
|
+
octets.each do |octet|
|
1181
|
+
raise "#{ip} is invalid (IPv4 dotted-decimal format " +
|
1182
|
+
"should not contain non-numeric characters)." if (octet =~ /[^0-9]/ )
|
1183
|
+
octet = octet.to_i()
|
1184
|
+
if ( (octet < 0) || (octet >= 256) )
|
1185
|
+
raise "#{ip} is invalid (IPv4 octets should be between 0 and 255)."
|
1186
|
+
end
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
elsif (version == 6)
|
1190
|
+
# make sure we only have at most (2) colons in a row, and then only
|
1191
|
+
# (1) instance of that
|
1192
|
+
if ( (ip =~ /:{3,}/) || (ip.split("::").length > 2) )
|
1193
|
+
raise "#{ip} is invalid (IPv6 field separators (:) are bad)."
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
# set flags
|
1197
|
+
shorthand = false
|
1198
|
+
if (ip =~ /\./)
|
1199
|
+
dotted_dec = true
|
1200
|
+
else
|
1201
|
+
dotted_dec = false
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
# split up by ':'
|
1205
|
+
fields = []
|
1206
|
+
if (ip =~ /::/)
|
1207
|
+
shorthand = true
|
1208
|
+
ip.split('::').each do |x|
|
1209
|
+
fields.concat( x.split(':') )
|
1210
|
+
end
|
1211
|
+
else
|
1212
|
+
fields.concat( ip.split(':') )
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
# make sure we have the correct number of fields
|
1216
|
+
if (shorthand)
|
1217
|
+
if ( (dotted_dec && fields.length > 6) || (!dotted_dec && fields.length > 7) )
|
1218
|
+
raise "#{ip} is invalid (IPv6 shorthand notation has " +
|
1219
|
+
"incorrect number of fields)."
|
1220
|
+
end
|
1221
|
+
else
|
1222
|
+
if ( (dotted_dec && fields.length != 7 ) || (!dotted_dec && fields.length != 8) )
|
1223
|
+
raise "#{ip} is invalid (IPv6 address has " +
|
1224
|
+
"incorrect number of fields)."
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
# if dotted_dec then validate the last field
|
1229
|
+
if (dotted_dec)
|
1230
|
+
dotted = fields.pop()
|
1231
|
+
octets = dotted.split('.')
|
1232
|
+
raise "#{ip} is invalid (Legacy IPv4 portion of IPv6 " +
|
1233
|
+
"address should contain (4) octets)." if (octets.length != 4)
|
1234
|
+
octets.each do |x|
|
1235
|
+
raise "#{ip} is invalid (egacy IPv4 portion of IPv6 " +
|
1236
|
+
"address should not contain non-numeric characters)." if (x =~ /[^0-9]/ )
|
1237
|
+
x = x.to_i
|
1238
|
+
if ( (x < 0) || (x >= 256) )
|
1239
|
+
raise "#{ip} is invalid (Octets of a legacy IPv4 portion of IPv6 " +
|
1240
|
+
"address should be between 0 and 255)."
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
# validate hex fields
|
1246
|
+
fields.each do |x|
|
1247
|
+
if (x =~ /[^0-9a-fA-F]/)
|
1248
|
+
raise "#{ip} is invalid (IPv6 address contains invalid hex characters)."
|
1249
|
+
else
|
1250
|
+
x = x.to_i(16)
|
1251
|
+
if ( (x < 0) || (x >= 2**16) )
|
1252
|
+
raise "#{ip} is invalid (Fields of an IPv6 address " +
|
1253
|
+
"should be between 0x0 and 0xFFFF)."
|
1254
|
+
end
|
1255
|
+
end
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
else
|
1259
|
+
raise "#{ip} is invalid (Did you mean to pass an Integer instead of a String?)."
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
elsif ( ip.kind_of?(Integer) )
|
1263
|
+
if (version && version == 4)
|
1264
|
+
raise "#{ip} is invalid (Integer is out of bounds)." if ( (ip < 0) || (ip > 2**32) )
|
1265
|
+
else
|
1266
|
+
raise "#{ip} is invalid (Integer is out of bounds)." if ( (ip < 0) || (ip > 2**128) )
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
else
|
1270
|
+
raise ArgumentError, "Expected String or Integer, but #{ip.class} provided."
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
|
1274
|
+
return(true)
|
1275
|
+
end
|
1276
|
+
module_function :validate_ip_addr
|
1277
|
+
|
1278
|
+
#======================================#
|
1279
|
+
#
|
1280
|
+
#======================================#
|
1281
|
+
|
1282
|
+
|
1283
|
+
#==============================================================================#
|
1284
|
+
# validate_ip_netmask()
|
1285
|
+
#==============================================================================#
|
1286
|
+
|
1287
|
+
# Validate IP Netmask.
|
1288
|
+
#
|
1289
|
+
# - Arguments:
|
1290
|
+
# * Hash with the following fields:
|
1291
|
+
# - :Netmask -- Netmask to validate - String or Integer
|
1292
|
+
# - :Packed -- if true, the provided Netmask is a packed Integer
|
1293
|
+
# - :Version -- IP version - Integer (optional)
|
1294
|
+
#
|
1295
|
+
# - Returns:
|
1296
|
+
# * True or exception.
|
1297
|
+
#
|
1298
|
+
# - Notes:
|
1299
|
+
# * Version defaults to 4 if not specified.
|
1300
|
+
#
|
1301
|
+
# Example:
|
1302
|
+
# IPAdmin.validate_ip_netmask(:Netmask => '/32')
|
1303
|
+
# IPAdmin.validate_ip_netmask(:Netmask => 32)
|
1304
|
+
# IPAdmin.validate_ip_netmask(:Netmask => 0xffffffff, :Packed => true)
|
1305
|
+
#
|
1306
|
+
def validate_ip_netmask(options)
|
1307
|
+
packed = false
|
1308
|
+
|
1309
|
+
if (!options.kind_of?(Hash))
|
1310
|
+
raise ArgumentError, "Expected Hash, but #{options.class} provided."
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
if (!options.has_key?(:Netmask))
|
1314
|
+
raise ArgumentError, "Missing argument: Netmask."
|
1315
|
+
end
|
1316
|
+
netmask = options[:Netmask]
|
1317
|
+
|
1318
|
+
if (options.has_key?(:Packed) && options[:Packed] == true)
|
1319
|
+
packed = true
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
if (options.has_key?(:Version))
|
1323
|
+
version = options[:Version]
|
1324
|
+
if (version != 4 && version != 6)
|
1325
|
+
raise ArgumentError, ":Version should be 4 or 6, but was '#{version}'."
|
1326
|
+
elsif (version == 6)
|
1327
|
+
max_bits = 128
|
1328
|
+
else
|
1329
|
+
max_bits = 32
|
1330
|
+
end
|
1331
|
+
else
|
1332
|
+
version = 4
|
1333
|
+
max_bits = 32
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
|
1337
|
+
if (netmask.kind_of?(String))
|
1338
|
+
if(netmask =~ /\./)
|
1339
|
+
all_f = 2**32-1
|
1340
|
+
packed_netmask = 0
|
1341
|
+
|
1342
|
+
# validate & pack extended mask
|
1343
|
+
begin
|
1344
|
+
validate_ip_addr(:IP => netmask)
|
1345
|
+
packed_netmask = pack_ip_addr(:IP => netmask)
|
1346
|
+
rescue Exception
|
1347
|
+
raise "#{netmask} is an improperly formed IPv4 address."
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
# cycle through the bits of hostmask and compare
|
1351
|
+
# with packed_mask. when we hit the firt '1' within
|
1352
|
+
# packed_mask (our netmask boundary), xor hostmask and
|
1353
|
+
# packed_mask. the result should be all 1's. this whole
|
1354
|
+
# process is in place to make sure that we dont have
|
1355
|
+
# and crazy masks such as 255.254.255.0
|
1356
|
+
hostmask = 1
|
1357
|
+
32.times do
|
1358
|
+
check = packed_netmask & hostmask
|
1359
|
+
if ( check != 0)
|
1360
|
+
hostmask = hostmask >> 1
|
1361
|
+
unless ( (packed_netmask ^ hostmask) == all_f)
|
1362
|
+
raise "#{netmask} contains '1' bits within the host portion of the netmask."
|
1363
|
+
end
|
1364
|
+
break
|
1365
|
+
else
|
1366
|
+
hostmask = hostmask << 1
|
1367
|
+
hostmask = hostmask | 1
|
1368
|
+
end
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
else
|
1372
|
+
# remove '/' if present
|
1373
|
+
if (netmask =~ /^\// )
|
1374
|
+
netmask[0] = " "
|
1375
|
+
netmask.lstrip!
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
# check if we have any non numeric characters
|
1379
|
+
if (netmask =~ /\D/)
|
1380
|
+
raise "#{netmask} contains invalid characters."
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
netmask = netmask.to_i
|
1384
|
+
if (netmask > max_bits || netmask == 0 )
|
1385
|
+
raise "Netmask, #{netmask}, is out of bounds for IPv#{version}."
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
end
|
1389
|
+
|
1390
|
+
elsif (netmask.kind_of?(Integer) )
|
1391
|
+
if (!packed)
|
1392
|
+
if (netmask > max_bits || netmask == 0 )
|
1393
|
+
raise "Netmask, #{netmask}, is out of bounds for IPv#{version}."
|
1394
|
+
end
|
1395
|
+
else
|
1396
|
+
if (netmask >= 2**max_bits || netmask == 0 )
|
1397
|
+
raise "Packed netmask, #{netmask}, is out of bounds for IPv#{version}."
|
1398
|
+
end
|
1399
|
+
end
|
1400
|
+
|
1401
|
+
else
|
1402
|
+
raise ArgumentError, "Expected String or Integer, but #{netmask.class} provided."
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
return(true)
|
1406
|
+
end
|
1407
|
+
module_function :validate_ip_netmask
|
1408
|
+
|
1409
|
+
#======================================#
|
1410
|
+
#
|
1411
|
+
#======================================#
|
1412
|
+
|
1413
|
+
|
1414
|
+
#==============================================================================#
|
1415
|
+
# NetStruct
|
1416
|
+
#==============================================================================#
|
1417
|
+
|
1418
|
+
# Struct object used internally by IPAdmin. It is not likely directly useful
|
1419
|
+
# to anyone.
|
1420
|
+
#
|
1421
|
+
# Description of fields:
|
1422
|
+
# * network - Integer representing an IP address
|
1423
|
+
# * netmask - Integer representing IP netmask
|
1424
|
+
# * version - Integer representing an IP version
|
1425
|
+
# * object - holds an IPAdmin class object
|
1426
|
+
# * subnets - an array of children NetStruct objects
|
1427
|
+
#
|
1428
|
+
NetStruct = Struct.new(:network, :netmask, :version, :object, :subnets)
|
1429
|
+
|
1430
|
+
#======================================#
|
1431
|
+
#
|
1432
|
+
#======================================#
|
1433
|
+
|
1434
|
+
|
1435
|
+
|
1436
|
+
|
1437
|
+
|
1438
|
+
# DEPRICATED
|
1439
|
+
|
1440
|
+
#==============================================================================#
|
1441
|
+
# arpa()
|
1442
|
+
#==============================================================================#
|
1443
|
+
|
1444
|
+
# DEPRICATED - USE CIDR.arpa:
|
1445
|
+
# Using the provided CIDR object,
|
1446
|
+
# return either an in-addr.arpa. or ip6.arpa. string. The netmask will be used
|
1447
|
+
# to determine the length of the returned arpa string.
|
1448
|
+
#
|
1449
|
+
# - Arguments:
|
1450
|
+
# * CIDR object
|
1451
|
+
#
|
1452
|
+
# - Returns:
|
1453
|
+
# * String
|
1454
|
+
#
|
1455
|
+
# - Notes:
|
1456
|
+
# * IPAdmin.arpa will use the original IP address passed during the initialization
|
1457
|
+
# of the CIDR objects. This IP can be found using the CIDR.ip() method.
|
1458
|
+
#
|
1459
|
+
# Example:
|
1460
|
+
# arpa = IPAdmin.arpa(cidr)
|
1461
|
+
#
|
1462
|
+
def arpa(object)
|
1463
|
+
|
1464
|
+
if (!object.kind_of? IPAdmin::CIDR)
|
1465
|
+
raise ArgumentError, "Expected IPAdmin::CIDR object, " +
|
1466
|
+
"but #{object.class} provided."
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
base = object.ip()
|
1470
|
+
netmask = object.bits()
|
1471
|
+
|
1472
|
+
if (object.version == 4)
|
1473
|
+
net = base.split('.')
|
1474
|
+
|
1475
|
+
if (netmask)
|
1476
|
+
while (netmask < 32)
|
1477
|
+
net.pop
|
1478
|
+
netmask = netmask + 8
|
1479
|
+
end
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
arpa = net.reverse.join('.')
|
1483
|
+
arpa << ".in-addr.arpa."
|
1484
|
+
|
1485
|
+
elsif (object.version == 6)
|
1486
|
+
fields = base.split(':')
|
1487
|
+
net = []
|
1488
|
+
fields.each do |field|
|
1489
|
+
(field.split("")).each do |x|
|
1490
|
+
net.push(x)
|
1491
|
+
end
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
if (netmask)
|
1495
|
+
while (netmask < 128)
|
1496
|
+
net.pop
|
1497
|
+
netmask = netmask + 4
|
1498
|
+
end
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
arpa = net.reverse.join('.')
|
1502
|
+
arpa << ".ip6.arpa."
|
1503
|
+
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
return(arpa)
|
1507
|
+
end
|
1508
|
+
module_function :arpa
|
1509
|
+
|
1510
|
+
#======================================#
|
1511
|
+
#
|
1512
|
+
#======================================#
|
1513
|
+
|
1514
|
+
#==============================================================================#
|
1515
|
+
# validate_ipv4_addr()
|
1516
|
+
#==============================================================================#
|
1517
|
+
|
1518
|
+
# DEPRICATED - USE validate_ip_addr INSTEAD
|
1519
|
+
# Validate IPv4 addresses. The address should not contain a netmask.
|
1520
|
+
#
|
1521
|
+
# - Arguments:
|
1522
|
+
# * IPv4 address
|
1523
|
+
#
|
1524
|
+
# - Returns:
|
1525
|
+
# * packed IP on valid, or exception on error.
|
1526
|
+
#
|
1527
|
+
# Example:
|
1528
|
+
# IPAdmin.validate_ipv4_addr('192.168.1.1')
|
1529
|
+
#
|
1530
|
+
def validate_ipv4_addr(ip)
|
1531
|
+
|
1532
|
+
# is this a string?
|
1533
|
+
unless (ip.kind_of? String)
|
1534
|
+
raise ArgumentError, "Expected String, but #{ip.class} provided."
|
1535
|
+
end
|
1536
|
+
|
1537
|
+
# check validity of characters in the addr
|
1538
|
+
if ( (ip =~ /\.{2,}?/ ) || (ip =~ /[^0-9\.]/) )
|
1539
|
+
raise "#{ip} is not a valid IPv4 address."
|
1540
|
+
end
|
1541
|
+
|
1542
|
+
# do we have 4 octets?
|
1543
|
+
octets = ip.split( /\./ ).reverse
|
1544
|
+
if (octets.length != 4)
|
1545
|
+
raise "#{ip} is not a valid IPv4 address."
|
1546
|
+
end
|
1547
|
+
|
1548
|
+
# are octets in range 0..255?
|
1549
|
+
packed_ip = 0
|
1550
|
+
(0..3).each do |x|
|
1551
|
+
octets[x] = octets[x].to_i
|
1552
|
+
unless ( (octets[x] >= 0) && (octets[x] < 256 ) )
|
1553
|
+
raise "#{ip} is not a valid IPv4 address."
|
1554
|
+
end
|
1555
|
+
octets[x] = octets[x] << 8*x
|
1556
|
+
packed_ip = packed_ip | octets[x]
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
# dont allow first octet to be 0
|
1560
|
+
if (octets[3] == 0)
|
1561
|
+
raise "#{ip} is not a valid IPv4 address."
|
1562
|
+
end
|
1563
|
+
|
1564
|
+
return(packed_ip)
|
1565
|
+
|
1566
|
+
end
|
1567
|
+
module_function :validate_ipv4_addr
|
1568
|
+
|
1569
|
+
#======================================#
|
1570
|
+
#
|
1571
|
+
#======================================#
|
1572
|
+
|
1573
|
+
|
1574
|
+
#==============================================================================#
|
1575
|
+
# validate_ipv4_netmask()
|
1576
|
+
#==============================================================================#
|
1577
|
+
|
1578
|
+
# DEPRICATED - USE validate_ip_netmask INSTEAD
|
1579
|
+
# Validate IPv4 Netmask.
|
1580
|
+
#
|
1581
|
+
# - Arguments:
|
1582
|
+
# * IPv4 netmask in cidr or extended notation
|
1583
|
+
#
|
1584
|
+
# - Returns:
|
1585
|
+
# * packed netmask on valid, or exception on error.
|
1586
|
+
#
|
1587
|
+
# Example:
|
1588
|
+
# IPAdmin.validate_ipv4_netmask('255.255.255.0')
|
1589
|
+
# IPAdmin.validate_ipv4_netmask('24')
|
1590
|
+
# IPAdmin.validate_ipv4_netmask('/24')
|
1591
|
+
# IPAdmin.validate_ipv4_netmask(24)
|
1592
|
+
#
|
1593
|
+
def validate_ipv4_netmask(netmask)
|
1594
|
+
all_f = 2**32 - 1
|
1595
|
+
packed_mask = nil
|
1596
|
+
|
1597
|
+
# is this a CIDR or Extended mask?
|
1598
|
+
if(netmask =~ /\./)
|
1599
|
+
# validate & pack extended mask
|
1600
|
+
begin
|
1601
|
+
validate_ipv4_addr(netmask)
|
1602
|
+
packed_netmask = pack_ipv4_addr(netmask)
|
1603
|
+
|
1604
|
+
rescue Exception
|
1605
|
+
raise "#{netmask} is not a valid IPv4 netmask."
|
1606
|
+
end
|
1607
|
+
|
1608
|
+
# cycle through the bits of hostmask and compare
|
1609
|
+
# with packed_mask. when we hit the firt '1' within
|
1610
|
+
# packed_mask (our netmask boundary), xor hostmask and
|
1611
|
+
# packed_mask. the result should be all 1's. this whole
|
1612
|
+
# process is in place to make sure that we dont have
|
1613
|
+
# and crazy masks such as 255.254.255.0
|
1614
|
+
hostmask = 1
|
1615
|
+
32.times do
|
1616
|
+
check = packed_netmask & hostmask
|
1617
|
+
if ( check != 0)
|
1618
|
+
hostmask = hostmask >> 1
|
1619
|
+
unless ( (packed_netmask ^ hostmask) == all_f)
|
1620
|
+
raise "#{netmask} is not a valid IPv4 netmask."
|
1621
|
+
end
|
1622
|
+
break
|
1623
|
+
else
|
1624
|
+
hostmask = hostmask << 1
|
1625
|
+
hostmask = hostmask | 1
|
1626
|
+
end
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
else
|
1630
|
+
# remove '/' if present
|
1631
|
+
if (netmask =~ /^\// )
|
1632
|
+
netmask[0] = " "
|
1633
|
+
netmask.lstrip!
|
1634
|
+
end
|
1635
|
+
|
1636
|
+
# check if we have any non numeric characters
|
1637
|
+
if (netmask =~ /\D/)
|
1638
|
+
raise "#{netmask} is not a valid IPv4 netmask."
|
1639
|
+
end
|
1640
|
+
|
1641
|
+
# are we between 1 and 32 inclusive
|
1642
|
+
if (netmask.kind_of? String)
|
1643
|
+
netmask = netmask.to_i
|
1644
|
+
end
|
1645
|
+
|
1646
|
+
if ( (netmask > 32) || (netmask == 0) )
|
1647
|
+
raise "#{netmask} is not a valid IPv4 netmask."
|
1648
|
+
end
|
1649
|
+
|
1650
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
1651
|
+
end
|
1652
|
+
|
1653
|
+
return(packed_netmask)
|
1654
|
+
end
|
1655
|
+
module_function :validate_ipv4_netmask
|
1656
|
+
|
1657
|
+
#======================================#
|
1658
|
+
#
|
1659
|
+
#======================================#
|
1660
|
+
|
1661
|
+
|
1662
|
+
#==============================================================================#
|
1663
|
+
# validate_ipv6_addr()
|
1664
|
+
#==============================================================================#
|
1665
|
+
|
1666
|
+
# DEPRICATED - USE validate_ip_addr INSTEAD
|
1667
|
+
# Validate IPv6 addresses. The address should not contain a netmask.
|
1668
|
+
#
|
1669
|
+
# - Arguments:
|
1670
|
+
# * IPv6 address
|
1671
|
+
#
|
1672
|
+
# - Returns:
|
1673
|
+
# * packed IP on valid, or exception on error.
|
1674
|
+
#
|
1675
|
+
# Example:
|
1676
|
+
# IPAdmin.validate_ipv6_addr('fec0::')
|
1677
|
+
#
|
1678
|
+
def validate_ipv6_addr(ip)
|
1679
|
+
# is this a string?
|
1680
|
+
unless (ip.kind_of? String)
|
1681
|
+
raise ArgumentError, "Expected String, but #{ip.class} provided."
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
# check validity of characters in the addr
|
1685
|
+
if ( (ip =~ /:{3,}?/ ) || (ip =~ /[^0-9a-fA-F:]/) )
|
1686
|
+
raise "#{ip} is not a valid IPv6 address."
|
1687
|
+
end
|
1688
|
+
|
1689
|
+
# look for a '::' to see if this address is in shorthand
|
1690
|
+
# if found, split on it & make sure that we have at most
|
1691
|
+
# two elements
|
1692
|
+
if (ip =~ /::/)
|
1693
|
+
shrthnd = ip.split( /::/ )
|
1694
|
+
unless ( (shrthnd.length > 0) && (shrthnd.length < 3) )
|
1695
|
+
raise "#{ip} is not a valid IPv6 address."
|
1696
|
+
end
|
1697
|
+
end
|
1698
|
+
|
1699
|
+
if (shrthnd)
|
1700
|
+
# if shorthand, we should have between 1 and 7
|
1701
|
+
# hex fields
|
1702
|
+
hex_fields = []
|
1703
|
+
shrthnd.each do |x|
|
1704
|
+
elements = x.split( /:/ )
|
1705
|
+
elements.each {|x| hex_fields.push(x)}
|
1706
|
+
end
|
1707
|
+
if ( (hex_fields.length < 1) || (hex_fields.length > 7) )
|
1708
|
+
raise "#{ip} is not a valid IPv6 address."
|
1709
|
+
end
|
1710
|
+
|
1711
|
+
else
|
1712
|
+
# since no shorthand notation was detected we should
|
1713
|
+
# have exactly 8 hex fields
|
1714
|
+
hex_fields = ip.split( /:/ )
|
1715
|
+
if (hex_fields.length != 8)
|
1716
|
+
raise "#{ip} is not a valid IPv6 address."
|
1717
|
+
end
|
1718
|
+
|
1719
|
+
end
|
1720
|
+
|
1721
|
+
# check that we have no more than 4 characters in each
|
1722
|
+
# hex field
|
1723
|
+
hex_fields.each do |x|
|
1724
|
+
if (x.length > 4)
|
1725
|
+
raise "#{ip} is not a valid IPv6 address."
|
1726
|
+
end
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
packed_ip = IPAdmin.pack_ipv6_addr(ip)
|
1730
|
+
return(packed_ip)
|
1731
|
+
end
|
1732
|
+
module_function :validate_ipv6_addr
|
1733
|
+
|
1734
|
+
#======================================#
|
1735
|
+
#
|
1736
|
+
#======================================#
|
1737
|
+
|
1738
|
+
|
1739
|
+
#==============================================================================#
|
1740
|
+
# validate_ipv6_netmask()
|
1741
|
+
#==============================================================================#
|
1742
|
+
|
1743
|
+
# DEPRICATED - USE validate_ip_netmask INSTEAD
|
1744
|
+
# Validate IPv6 netmask.
|
1745
|
+
#
|
1746
|
+
# - Arguments:
|
1747
|
+
# * IPv6 netmask in cidr notation
|
1748
|
+
#
|
1749
|
+
# - Returns:
|
1750
|
+
# * packed netmask on valid, or exception on error.
|
1751
|
+
#
|
1752
|
+
# Example:
|
1753
|
+
# IPAdmin.validate_ipv6_netmask('64')
|
1754
|
+
# IPAdmin.validate_ipv6_netmask('/64')
|
1755
|
+
# IPAdmin.validate_ipv6_netmask(64)
|
1756
|
+
#
|
1757
|
+
def validate_ipv6_netmask(netmask)
|
1758
|
+
all_f = 2**128 -1
|
1759
|
+
|
1760
|
+
# remove '/' if present
|
1761
|
+
if (netmask =~ /^\// )
|
1762
|
+
netmask[0] = " "
|
1763
|
+
netmask.lstrip!
|
1764
|
+
end
|
1765
|
+
|
1766
|
+
if (netmask =~ /\D/)
|
1767
|
+
raise "#{netmask} is not a valid IPv6 netmask."
|
1768
|
+
|
1769
|
+
else
|
1770
|
+
# are we between 1 and 128 inclusive
|
1771
|
+
if (netmask.kind_of? String)
|
1772
|
+
netmask = netmask.to_i
|
1773
|
+
end
|
1774
|
+
|
1775
|
+
if ( (netmask > 128) || (netmask == 0) )
|
1776
|
+
raise "#{netmask} is not a valid IPv6 netmask."
|
1777
|
+
end
|
1778
|
+
|
1779
|
+
end
|
1780
|
+
|
1781
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
1782
|
+
return(packed_netmask)
|
1783
|
+
end
|
1784
|
+
module_function :validate_ipv6_netmask
|
1785
|
+
|
1786
|
+
#======================================#
|
1787
|
+
#
|
1788
|
+
#======================================#
|
1789
|
+
|
1790
|
+
|
1791
|
+
#==============================================================================#
|
1792
|
+
# pack_ipv4_addr()
|
1793
|
+
#==============================================================================#
|
1794
|
+
|
1795
|
+
# DEPRICATED - USE pack_ip_addr INSTEAD
|
1796
|
+
# Convert IPv4 addresses into an integer. No attempt at
|
1797
|
+
# validation is performed.
|
1798
|
+
#
|
1799
|
+
# - Arguments:
|
1800
|
+
# * IPv4 address
|
1801
|
+
#
|
1802
|
+
# - Returns:
|
1803
|
+
# * packed IPv4 address or exception on error.
|
1804
|
+
#
|
1805
|
+
# Example:
|
1806
|
+
# packed = IPAdmin.pack_ipv4_addr('192.168.1.1')
|
1807
|
+
#
|
1808
|
+
def pack_ipv4_addr(ip)
|
1809
|
+
|
1810
|
+
# is this a string?
|
1811
|
+
unless (ip.kind_of? String)
|
1812
|
+
raise ArgumentError, "Expected String, but #{ip.class} provided."
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
# pack our ip
|
1816
|
+
octets = ip.split( /\./ ).reverse
|
1817
|
+
packed_ip = 0
|
1818
|
+
|
1819
|
+
(0..3).each do |x|
|
1820
|
+
octets[x] = (octets[x]).to_i
|
1821
|
+
octets[x] = octets[x] << 8*x
|
1822
|
+
packed_ip = packed_ip | octets[x]
|
1823
|
+
end
|
1824
|
+
|
1825
|
+
return(packed_ip)
|
1826
|
+
end
|
1827
|
+
module_function :pack_ipv4_addr
|
1828
|
+
|
1829
|
+
#======================================#
|
1830
|
+
#
|
1831
|
+
#======================================#
|
1832
|
+
|
1833
|
+
|
1834
|
+
#==============================================================================#
|
1835
|
+
# pack_ipv4_netmask()
|
1836
|
+
#==============================================================================#
|
1837
|
+
|
1838
|
+
# DEPRICATED - USE pack_ip_netmask INSTEAD
|
1839
|
+
# Convert IPv4 netmask into an integer. Only very basic
|
1840
|
+
# validation is performed.
|
1841
|
+
#
|
1842
|
+
# - Arguments:
|
1843
|
+
# * IPv4 netmask in cidr or extended notation
|
1844
|
+
#
|
1845
|
+
# - Returns:
|
1846
|
+
# * packed IPv4 netmask or exception on error.
|
1847
|
+
#
|
1848
|
+
# Example:
|
1849
|
+
# packed = IPAdmin.pack_ipv4_netmask('255.255.255.0')
|
1850
|
+
# packed = IPAdmin.pack_ipv4_netmask('24')
|
1851
|
+
# packed = IPAdmin.pack_ipv4_netmask('/24')
|
1852
|
+
# packed = IPAdmin.pack_ipv4_netmask(24)
|
1853
|
+
#
|
1854
|
+
def pack_ipv4_netmask(netmask)
|
1855
|
+
all_f = 2**32-1
|
1856
|
+
|
1857
|
+
# is this a CIDR or Extended mask?
|
1858
|
+
if(netmask =~ /\./)
|
1859
|
+
# pack extended mask
|
1860
|
+
begin
|
1861
|
+
packed_netmask = pack_ipv4_addr(netmask)
|
1862
|
+
rescue Exception
|
1863
|
+
raise "#{netmask} is not a valid IPv4 netmask."
|
1864
|
+
end
|
1865
|
+
|
1866
|
+
else
|
1867
|
+
# remove '/' if present
|
1868
|
+
if (netmask =~ /^\// )
|
1869
|
+
netmask[0] = " "
|
1870
|
+
netmask.lstrip!
|
1871
|
+
end
|
1872
|
+
|
1873
|
+
# check if we have any non numeric characters
|
1874
|
+
if (netmask =~ /\D/)
|
1875
|
+
raise "#{netmask} is not a valid IPv4 netmask."
|
1876
|
+
end
|
1877
|
+
|
1878
|
+
if (netmask.kind_of? String)
|
1879
|
+
netmask = netmask.to_i
|
1880
|
+
end
|
1881
|
+
|
1882
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
1883
|
+
end
|
1884
|
+
|
1885
|
+
return(packed_netmask)
|
1886
|
+
end
|
1887
|
+
module_function :pack_ipv4_netmask
|
1888
|
+
|
1889
|
+
#======================================#
|
1890
|
+
#
|
1891
|
+
#======================================#
|
1892
|
+
|
1893
|
+
|
1894
|
+
#==============================================================================#
|
1895
|
+
# pack_ipv6_addr()
|
1896
|
+
#==============================================================================#
|
1897
|
+
|
1898
|
+
# DEPRICATED - USE pack_ip_addr INSTEAD
|
1899
|
+
# Convert IPv6 addresses into an integer. No attempt at
|
1900
|
+
# validation is performed.
|
1901
|
+
#
|
1902
|
+
# - Arguments:
|
1903
|
+
# * IPv6 address
|
1904
|
+
#
|
1905
|
+
# - Returns:
|
1906
|
+
# * packed IPv6 address or exception on error.
|
1907
|
+
#
|
1908
|
+
# Example:
|
1909
|
+
# packed = IPAdmin.pack_ipv6_addr('fec0::1')
|
1910
|
+
#
|
1911
|
+
def pack_ipv6_addr(ip)
|
1912
|
+
# is this a string?
|
1913
|
+
unless (ip.kind_of? String)
|
1914
|
+
raise ArgumentError, "Expected String, but #{ip.class} provided."
|
1915
|
+
end
|
1916
|
+
|
1917
|
+
# look for a '::' to see if this address is in shorthand
|
1918
|
+
# if found, split on it
|
1919
|
+
hex_fields = []
|
1920
|
+
if (ip =~ /::/)
|
1921
|
+
shrthnd = ip.split( /::/ )
|
1922
|
+
if (ip =~ /^::/)
|
1923
|
+
sec_half = shrthnd[1].split( /:/ )
|
1924
|
+
zero_pads = 8 - sec_half.length
|
1925
|
+
(1..zero_pads).each {hex_fields.push('0')}
|
1926
|
+
sec_half.each {|field| hex_fields.push(field)}
|
1927
|
+
elsif (ip =~ /::$/)
|
1928
|
+
hex_fields = shrthnd[0].split( /:/ )
|
1929
|
+
zero_pads = 8 - hex_fields.length
|
1930
|
+
(1..zero_pads).each {hex_fields.push('0')}
|
1931
|
+
else
|
1932
|
+
first_half = shrthnd[0].split( /:/ )
|
1933
|
+
sec_half = shrthnd[1].split( /:/ )
|
1934
|
+
zero_pads = 8 - (first_half.length + sec_half.length)
|
1935
|
+
first_half.each {|field| hex_fields.push(field)}
|
1936
|
+
(1..zero_pads).each {hex_fields.push('0')}
|
1937
|
+
sec_half.each {|field| hex_fields.push(field)}
|
1938
|
+
end
|
1939
|
+
|
1940
|
+
else
|
1941
|
+
hex_fields = ip.split( /:/ )
|
1942
|
+
end
|
1943
|
+
|
1944
|
+
# pack
|
1945
|
+
hex_fields.reverse!
|
1946
|
+
packed_ip = 0
|
1947
|
+
(0..7).each do |x|
|
1948
|
+
hex = hex_fields[x]
|
1949
|
+
base16 = hex.to_i(16)
|
1950
|
+
|
1951
|
+
base16 = base16 << 16*x
|
1952
|
+
packed_ip = packed_ip | base16
|
1953
|
+
end
|
1954
|
+
|
1955
|
+
|
1956
|
+
return(packed_ip)
|
1957
|
+
end
|
1958
|
+
module_function :pack_ipv6_addr
|
1959
|
+
|
1960
|
+
#======================================#
|
1961
|
+
#
|
1962
|
+
#======================================#
|
1963
|
+
|
1964
|
+
|
1965
|
+
#==============================================================================#
|
1966
|
+
# pack_ipv6_netmask()
|
1967
|
+
#==============================================================================#
|
1968
|
+
|
1969
|
+
# DEPRICATED - USE pack_ip_netmask INSTEAD
|
1970
|
+
# Convert IPv6 netmask into an integer. Only very basic
|
1971
|
+
# validation is performed.
|
1972
|
+
#
|
1973
|
+
# - Arguments:
|
1974
|
+
# * IPv6 netmask in cidr notation
|
1975
|
+
#
|
1976
|
+
# - Returns:
|
1977
|
+
# * packed IPv6 netmask or exception on error.
|
1978
|
+
#
|
1979
|
+
# Example:
|
1980
|
+
# packed = IPAdmin.pack_ipv6_netmask('64')
|
1981
|
+
# packed = IPAdmin.pack_ipv6_netmask('/64')
|
1982
|
+
# packed = IPAdmin.pack_ipv6_netmask(64)
|
1983
|
+
#
|
1984
|
+
def pack_ipv6_netmask(netmask)
|
1985
|
+
all_f = 2**128-1
|
1986
|
+
|
1987
|
+
# remove '/' if present
|
1988
|
+
if (netmask =~ /^\// )
|
1989
|
+
netmask[0] = " "
|
1990
|
+
netmask.lstrip!
|
1991
|
+
end
|
1992
|
+
|
1993
|
+
if (netmask !~ /\D/)
|
1994
|
+
# pack
|
1995
|
+
if (netmask.kind_of? String)
|
1996
|
+
netmask = netmask.to_i
|
1997
|
+
end
|
1998
|
+
|
1999
|
+
packed_netmask = all_f ^ (all_f >> netmask)
|
2000
|
+
|
2001
|
+
else
|
2002
|
+
raise "#{netmask} is not a valid IPv6 netmask."
|
2003
|
+
|
2004
|
+
end
|
2005
|
+
|
2006
|
+
return(packed_netmask)
|
2007
|
+
end
|
2008
|
+
module_function :pack_ipv6_netmask
|
2009
|
+
|
2010
|
+
#======================================#
|
2011
|
+
#
|
2012
|
+
#======================================#
|
2013
|
+
|
2014
|
+
|
2015
|
+
#==============================================================================#
|
2016
|
+
# unpack_ipv4_addr()
|
2017
|
+
#==============================================================================#
|
2018
|
+
|
2019
|
+
# DEPRICATED - USE unpack_ip_addr INSTEAD
|
2020
|
+
# Unack a packed IPv4 address back into a printable string. No attempt at
|
2021
|
+
# validation is performed.
|
2022
|
+
#
|
2023
|
+
# - Arguments:
|
2024
|
+
# * Byte-packed IPv4 address
|
2025
|
+
#
|
2026
|
+
# - Returns:
|
2027
|
+
# * IPv4 address.
|
2028
|
+
#
|
2029
|
+
# Example:
|
2030
|
+
# unpacked = IPAdmin.unpack_ipv4_addr(packed)
|
2031
|
+
#
|
2032
|
+
def unpack_ipv4_addr(packed_ip)
|
2033
|
+
octets = []
|
2034
|
+
(0..3).each do |x|
|
2035
|
+
octets[x] = packed_ip & 0xFF
|
2036
|
+
octets[x] = (octets[x]).to_s
|
2037
|
+
packed_ip = packed_ip >> 8
|
2038
|
+
end
|
2039
|
+
|
2040
|
+
octets.reverse!
|
2041
|
+
ip = octets.join('.')
|
2042
|
+
|
2043
|
+
return(ip)
|
2044
|
+
end
|
2045
|
+
module_function :unpack_ipv4_addr
|
2046
|
+
|
2047
|
+
#======================================#
|
2048
|
+
#
|
2049
|
+
#======================================#
|
2050
|
+
|
2051
|
+
|
2052
|
+
#==============================================================================#
|
2053
|
+
# unpack_ipv4_netmask()
|
2054
|
+
#==============================================================================#
|
2055
|
+
|
2056
|
+
# DEPRICATED - USE unpack_ip_netmask INSTEAD
|
2057
|
+
# Unack a packed IPv4 netmask into a integer representing the number of
|
2058
|
+
# bits in the CIDR mask. No attempt at validation is performed.
|
2059
|
+
#
|
2060
|
+
# - Arguments:
|
2061
|
+
# * Byte-packed IPv4 netmask
|
2062
|
+
#
|
2063
|
+
# - Returns:
|
2064
|
+
# * IPv4 netmask as number of bits (cidr format).
|
2065
|
+
#
|
2066
|
+
# Example:
|
2067
|
+
# unpacked = IPAdmin.unpack_ipv4_netmask(packed)
|
2068
|
+
#
|
2069
|
+
def unpack_ipv4_netmask(packed_mask)
|
2070
|
+
mask = 32
|
2071
|
+
32.times do
|
2072
|
+
if ( (packed_mask & 1) != 0)
|
2073
|
+
break
|
2074
|
+
end
|
2075
|
+
packed_mask = packed_mask >> 1
|
2076
|
+
mask = mask - 1
|
2077
|
+
end
|
2078
|
+
|
2079
|
+
return(mask)
|
2080
|
+
end
|
2081
|
+
module_function :unpack_ipv4_netmask
|
2082
|
+
|
2083
|
+
#======================================#
|
2084
|
+
#
|
2085
|
+
#======================================#
|
2086
|
+
|
2087
|
+
|
2088
|
+
#==============================================================================#
|
2089
|
+
# unpack_ipv6_addr()
|
2090
|
+
#==============================================================================#
|
2091
|
+
|
2092
|
+
# DEPRICATED - USE unpack_ip_addr INSTEAD
|
2093
|
+
# Unack a packed IPv6 address back into a printable string. No attempt at
|
2094
|
+
# validation is performed.
|
2095
|
+
#
|
2096
|
+
# - Arguments:
|
2097
|
+
# * Byte-packed IPv6 address
|
2098
|
+
#
|
2099
|
+
# - Returns:
|
2100
|
+
# * IPv6 address.
|
2101
|
+
#
|
2102
|
+
# Example:
|
2103
|
+
# unpacked = IPAdmin.unpack_ipv6_addr(packed)
|
2104
|
+
#
|
2105
|
+
def unpack_ipv6_addr(packed_ip)
|
2106
|
+
hex_fields = []
|
2107
|
+
(0..7).each do |x|
|
2108
|
+
hex_fields[x] = packed_ip & 0xFFFF
|
2109
|
+
hex_fields[x] = (hex_fields[x]).to_s(16)
|
2110
|
+
packed_ip = packed_ip >> 16
|
2111
|
+
|
2112
|
+
# if hex_fields[x] < 4 characters, then pad with 0's
|
2113
|
+
(4 - hex_fields[x].length).times do
|
2114
|
+
hex_fields[x] = '0' << hex_fields[x]
|
2115
|
+
end
|
2116
|
+
end
|
2117
|
+
|
2118
|
+
hex_fields.reverse!
|
2119
|
+
ip = hex_fields.join(':')
|
2120
|
+
|
2121
|
+
return(ip)
|
2122
|
+
end
|
2123
|
+
module_function :unpack_ipv6_addr
|
2124
|
+
|
2125
|
+
#======================================#
|
2126
|
+
#
|
2127
|
+
#======================================#
|
2128
|
+
|
2129
|
+
|
2130
|
+
#==============================================================================#
|
2131
|
+
# unpack_ipv6_netmask()
|
2132
|
+
#==============================================================================#
|
2133
|
+
|
2134
|
+
# DEPRICATED - USE unpack_ip_netmask INSTEAD
|
2135
|
+
# Unack a packed IPv6 netmask into a integer representing the number of
|
2136
|
+
# bits in the CIDR mask. No attempt at validation is performed.
|
2137
|
+
#
|
2138
|
+
# - Arguments:
|
2139
|
+
# * Byte-packed IPv6 netmask
|
2140
|
+
#
|
2141
|
+
# - Returns:
|
2142
|
+
# * IPv6 netmask as number of bits (cidr format).
|
2143
|
+
#
|
2144
|
+
# Example:
|
2145
|
+
# unpacked = IPAdmin.unpack_ipv6_netmask(packed)
|
2146
|
+
#
|
2147
|
+
def unpack_ipv6_netmask(packed_mask)
|
2148
|
+
|
2149
|
+
mask = 128
|
2150
|
+
128.times do
|
2151
|
+
if ( (packed_mask & 1) == 1)
|
2152
|
+
break
|
2153
|
+
end
|
2154
|
+
mask = mask - 1
|
2155
|
+
packed_mask = packed_mask >> 1
|
2156
|
+
end
|
2157
|
+
|
2158
|
+
return(mask)
|
2159
|
+
end
|
2160
|
+
module_function :unpack_ipv6_netmask
|
2161
|
+
|
2162
|
+
#======================================#
|
2163
|
+
#
|
2164
|
+
#======================================#
|
2165
|
+
|
2166
|
+
end # module IPAdmin
|
2167
|
+
__END__
|
2168
|
+
|