relevance_ipaddress 0.5.0

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.
@@ -0,0 +1,89 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "relevance_ipaddress"
9
+ gem.summary = %Q{Fix Branch of IPAddress gem. IPv4/IPv6 addresses manipulation library}
10
+ gem.email = "ceresa@gmail.com"
11
+ gem.homepage = "http://github.com/bluemonk/ipaddress"
12
+ gem.authors = ["Marco Ceresa"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION.yml')
46
+ config = YAML.load(File.read('VERSION.yml'))
47
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "ipaddress #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
57
+
58
+ desc "Open an irb session preloaded with this library"
59
+ task :console do
60
+ sh "irb -rubygems -I lib -r ipaddress.rb"
61
+ end
62
+
63
+ desc "Look for TODO and FIXME tags in the code"
64
+ task :todo do
65
+ def egrep(pattern)
66
+ Dir['**/*.rb'].each do |fn|
67
+ count = 0
68
+ open(fn) do |f|
69
+ while line = f.gets
70
+ count += 1
71
+ if line =~ pattern
72
+ puts "#{fn}:#{count}:#{line}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ egrep /(FIXME|TODO|TBD)/
79
+ end
80
+
81
+ begin
82
+ require 'jeweler'
83
+ Jeweler::Tasks.new do |gemspec|
84
+ # omitted for brevity
85
+ end
86
+ Jeweler::GemcutterTasks.new
87
+ rescue LoadError
88
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
89
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,52 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'ipaddress/ipbase'
5
+ require 'ipaddress/ipv4'
6
+ require 'ipaddress/ipv6'
7
+
8
+ #
9
+ # IPAddress is a wrapper method built around
10
+ # IPAddress's library classes. Its purpouse is to
11
+ # make you indipendent from the type of IP address
12
+ # you're going to use.
13
+ #
14
+ # For example, instead of creating the three types
15
+ # of IP addresses using their own contructors
16
+ #
17
+ # ip = IPAddress::IPv4.new "172.16.10.1/24"
18
+ # ip6 = IPAddress::IPv6.new "2001:db8::8:800:200c:417a/64"
19
+ # ip_mapped = IPAddress::IPv6::Mapped "::ffff:172.16.10.1/128"
20
+ #
21
+ # you can just use the IPAddress wrapper:
22
+ #
23
+ # ip = IPAddress "172.16.10.1/24"
24
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
25
+ # ip_mapped = IPAddress "::ffff:172.16.10.1/128"
26
+ #
27
+ # All the object created will be instances of the
28
+ # correct class:
29
+ #
30
+ # ip.class
31
+ # #=> IPAddress::IPv4
32
+ # ip6.class
33
+ # #=> IPAddress::IPv6
34
+ # ip_mapped.class
35
+ # #=> IPAddress::IPv6::Mapped
36
+ #
37
+ def IPAddress(str)
38
+ case str
39
+ when /:.+\./
40
+ IPAddress::IPv6::Mapped.new(str)
41
+ else
42
+ begin
43
+ IPAddress::IPv4.new(str)
44
+ rescue ArgumentError
45
+ IPAddress::IPv6.new(str)
46
+ end
47
+ end
48
+ end
49
+
50
+
51
+
52
+
@@ -0,0 +1,16 @@
1
+ class << Math
2
+ def log2(n); log(n) / log(2); end
3
+ end
4
+
5
+ class Integer
6
+ def power_of_2?
7
+ Math::log2(self).to_i == Math::log2(self)
8
+ end
9
+
10
+ def closest_power_of_2
11
+ self.upto(32) do |i|
12
+ return i if i.power_of_2?
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,83 @@
1
+ require 'ipaddress/extensions/extensions'
2
+
3
+ module IPAddress
4
+
5
+ #
6
+ # Checks if the given string is a valid IP address,
7
+ # either IPv4 or IPv6
8
+ #
9
+ # Example:
10
+ #
11
+ # IPAddress::valid? "2002::1"
12
+ # #=> true
13
+ #
14
+ # IPAddress::valid? "10.0.0.256"
15
+ # #=> false
16
+ #
17
+ def self.valid?(addr)
18
+ valid_ipv4?(addr) || valid_ipv6?(addr)
19
+ end
20
+
21
+ #
22
+ # Checks if the given string is a valid IPv4 address
23
+ #
24
+ # Example:
25
+ #
26
+ # IPAddress::valid_ipv4? "2002::1"
27
+ # #=> false
28
+ #
29
+ # IPAddress::valid_ipv4? "172.16.10.1"
30
+ # #=> true
31
+ #
32
+ def self.valid_ipv4?(addr)
33
+ if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr
34
+ return $~.captures.all? {|i| i.to_i < 256}
35
+ end
36
+ false
37
+ end
38
+
39
+ #
40
+ # Checks if the argument is a valid IPv4 netmark
41
+ # expressed in dotted decimal format.
42
+ #
43
+ # IPAddress.valid_ipv4_netmask? "255.255.0.0"
44
+ # #=> true
45
+ #
46
+ def self.valid_ipv4_netmask?(addr)
47
+ arr = addr.split(".").map{|i| i.to_i}.pack("CCCC").unpack("B*").first.scan(/01/)
48
+ arr.empty? && valid_ipv4?(addr)
49
+ rescue
50
+ return false
51
+ end
52
+
53
+ #
54
+ # Checks if the given string is a valid IPv6 address
55
+ #
56
+ # Example:
57
+ #
58
+ # IPAddress::valid_ipv6? "2002::1"
59
+ # #=> true
60
+ #
61
+ # IPAddress::valid_ipv6? "2002::DEAD::BEEF"
62
+ # #=> false
63
+ #
64
+ def self.valid_ipv6?(addr)
65
+ # IPv6 (normal)
66
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr
67
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
68
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
69
+ # IPv6 (IPv4 compat)
70
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_ipv4?($')
71
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
72
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
73
+ false
74
+ end
75
+
76
+ class IPBase; end
77
+
78
+ end # module IPAddress
79
+
80
+
81
+
82
+
83
+
@@ -0,0 +1,849 @@
1
+ require 'ipaddress/ipbase'
2
+ require 'ipaddress/prefix'
3
+
4
+ module IPAddress;
5
+ #
6
+ # =Name
7
+ #
8
+ # IPAddress::IPv4 - IP version 4 address manipulation library
9
+ #
10
+ # =Synopsis
11
+ #
12
+ # require 'ipaddress'
13
+ #
14
+ # =Description
15
+ #
16
+ # Class IPAddress::IPv4 is used to handle IPv4 type addresses.
17
+ #
18
+ class IPv4 < IPBase
19
+
20
+ include IPAddress
21
+ include Enumerable
22
+ include Comparable
23
+
24
+ #
25
+ # This Hash contains the prefix values for Classful networks
26
+ #
27
+ # Note that classes C, D and E will all have a default
28
+ # prefix of /24 or 255.255.255.0
29
+ #
30
+ CLASSFUL = {
31
+ /^0../ => 8, # Class A, from 0.0.0.0 to 127.255.255.255
32
+ /^10./ => 16, # Class B, from 128.0.0.0 to 191.255.255.255
33
+ /^110/ => 24 # Class C, D and E, from 192.0.0.0 to 255.255.255.254
34
+ }
35
+
36
+ #
37
+ # Regular expression to match an IPv4 address
38
+ #
39
+ REGEXP = Regexp.new(/((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/)
40
+
41
+ #
42
+ # Creates a new IPv4 address object.
43
+ #
44
+ # An IPv4 address can be expressed in any of the following forms:
45
+ #
46
+ # * "10.1.1.1/24": ip address and prefix. This is the common and
47
+ # suggested way to create an object .
48
+ # * "10.1.1.1/255.255.255.0": ip address and netmask. Although
49
+ # convenient sometimes, this format is less clear than the previous
50
+ # one.
51
+ # * "10.1.1.1": if the address alone is specified, the prefix will be
52
+ # assigned using the classful boundaries. In this case, the
53
+ # prefix would be /8, a 255.0.0.0 netmask.
54
+ #
55
+ # It is advisable to use the syntactic shortcut provided with the
56
+ # IPAddress() method, as in all the examples below.
57
+ #
58
+ # Examples:
59
+ #
60
+ # # These two methods return the same object
61
+ # ip = IPAddress::IPv4.new("10.0.0.1/24")
62
+ # ip = IPAddress("10.0.0.1/24")
63
+ #
64
+ # # These three are the same
65
+ # IPAddress("10.0.0.1/8")
66
+ # IPAddress("10.0.0.1/255.0.0.0")
67
+ # IPAddress("10.0.0.1")
68
+ # #=> #<IPAddress::IPv4:0xb7b1a438
69
+ # @octets=[10, 0, 0, 1], @address="10.0.0.1", @prefix=8>
70
+ #
71
+ def initialize(str)
72
+ ip, netmask = str.split("/")
73
+
74
+ # Check the ip and remove white space
75
+ if IPAddress.valid_ipv4?(ip)
76
+ @address = ip.strip
77
+ else
78
+ raise ArgumentError, "Invalid IP #{ip.inspect}"
79
+ end
80
+
81
+ # Check the netmask
82
+ if netmask # netmask is defined
83
+ netmask.strip!
84
+ if netmask =~ /^\d{1,2}$/ # netmask in cidr format
85
+ @prefix = Prefix32.new(netmask.to_i)
86
+ elsif IPAddress.valid_ipv4_netmask?(netmask) # netmask in IP format
87
+ @prefix = Prefix32.parse_netmask(netmask)
88
+ else # invalid netmask
89
+ raise ArgumentError, "Invalid netmask #{netmask}"
90
+ end
91
+ else # netmask is nil, reverting to defaul classful mask
92
+ @prefix = prefix_from_ip(@address)
93
+ end
94
+
95
+ # Array formed with the IP octets
96
+ @octets = @address.split(".").map{|i| i.to_i}
97
+
98
+ end # def initialize
99
+
100
+ #
101
+ # Returns the address portion of the IPv4 object
102
+ # as a string.
103
+ #
104
+ # ip = IPAddress("172.16.100.4/22")
105
+ # ip.address
106
+ # #=> "172.16.100.4"
107
+ #
108
+ def address
109
+ @address
110
+ end
111
+
112
+ #
113
+ # Returns the prefix portion of the IPv4 object
114
+ # as a IPAddress::Prefix32 object
115
+ #
116
+ # ip = IPAddress("172.16.100.4/22")
117
+ # ip.prefix
118
+ # #=> 22
119
+ # ip.prefix.class
120
+ # #=> IPAddress::Prefix32
121
+ #
122
+ def prefix
123
+ @prefix
124
+ end
125
+
126
+ #
127
+ # Set a new prefix number for the object
128
+ #
129
+ # This is useful if you want to change the prefix
130
+ # to an object created with IPv4::parse_u32 or
131
+ # if the object was created using the classful
132
+ # mask.
133
+ #
134
+ # ip = IPAddress("172.16.100.4")
135
+ # puts ip
136
+ # #=> 172.16.100.4/16
137
+ #
138
+ # ip.prefix = 22
139
+ # puts ip
140
+ # #=> 172.16.100.4/22
141
+ #
142
+ def prefix=(num)
143
+ @prefix = Prefix32.new(num)
144
+ end
145
+
146
+ #
147
+ # Returns the address as an array of decimal values
148
+ #
149
+ # ip = IPAddress("172.16.100.4")
150
+ # ip.octets
151
+ # #=> [172, 16, 100, 4]
152
+ #
153
+ def octets
154
+ @octets
155
+ end
156
+
157
+ #
158
+ # Returns a string with the IP address in canonical
159
+ # form.
160
+ #
161
+ # ip = IPAddress("172.16.100.4/22")
162
+ # ip.to_s
163
+ # #=> "172.16.100.4/22"
164
+ #
165
+ def to_s
166
+ "#@address/#@prefix"
167
+ end
168
+
169
+ #
170
+ # Returns the prefix as a string in IP format
171
+ #
172
+ # ip = IPAddress("172.16.100.4/22")
173
+ # ip.netmask
174
+ # #=> "255.255.252.0"
175
+ #
176
+ def netmask
177
+ @prefix.to_ip
178
+ end
179
+
180
+ #
181
+ # Like IPv4#prefix=, this method allow you to
182
+ # change the prefix / netmask of an IP address
183
+ # object.
184
+ #
185
+ # ip = IPAddress("172.16.100.4")
186
+ # puts ip
187
+ # #=> 172.16.100.4/16
188
+ #
189
+ # ip.netmask = "255.255.252.0"
190
+ # puts ip
191
+ # #=> 172.16.100.4/22
192
+ #
193
+ def netmask=(addr)
194
+ @prefix = Prefix32.parse_netmask(addr)
195
+ end
196
+
197
+ #
198
+ # Returns the address portion in unsigned
199
+ # 32 bits integer format.
200
+ #
201
+ # This method is identical to the C function
202
+ # inet_pton to create a 32 bits address family
203
+ # structure.
204
+ #
205
+ # ip = IPAddress("10.0.0.0/8")
206
+ # ip.to_u32
207
+ # #=> 167772160
208
+ #
209
+ def to_u32
210
+ data.unpack("N").first
211
+ end
212
+ alias_method :to_i, :to_u32
213
+
214
+ #
215
+ # Returns the address portion of an IPv4 object
216
+ # in a network byte order format.
217
+ #
218
+ # ip = IPAddress("172.16.10.1/24")
219
+ # ip.data
220
+ # #=> "\254\020\n\001"
221
+ #
222
+ # It is usually used to include an IP address
223
+ # in a data packet to be sent over a socket
224
+ #
225
+ # a = Socket.open(params) # socket details here
226
+ # ip = IPAddress("10.1.1.0/24")
227
+ # binary_data = ["Address: "].pack("a*") + ip.data
228
+ #
229
+ # # Send binary data
230
+ # a.puts binary_data
231
+ #
232
+ def data
233
+ @octets.pack("C4")
234
+ end
235
+
236
+ #
237
+ # Returns the octet specified by index
238
+ #
239
+ # ip = IPAddress("172.16.100.50/24")
240
+ # ip[0]
241
+ # #=> 172
242
+ # ip[1]
243
+ # #=> 16
244
+ # ip[2]
245
+ # #=> 100
246
+ # ip[3]
247
+ # #=> 50
248
+ #
249
+ def [](index)
250
+ @octets[index]
251
+ end
252
+ alias_method :octet, :[]
253
+
254
+ #
255
+ # Returns the address portion of an IP in binary format,
256
+ # as a string containing a sequence of 0 and 1
257
+ #
258
+ # ip = IPAddress("127.0.0.1")
259
+ # ip.bits
260
+ # #=> "01111111000000000000000000000001"
261
+ #
262
+ def bits
263
+ data.unpack("B*").first
264
+ end
265
+
266
+ #
267
+ # Returns the broadcast address for the given IP.
268
+ #
269
+ # ip = IPAddress("172.16.10.64/24")
270
+ # ip.broadcast.to_s
271
+ # #=> "172.16.10.255/24"
272
+ #
273
+ def broadcast
274
+ self.class.parse_u32(broadcast_u32, @prefix)
275
+ end
276
+
277
+ #
278
+ # Checks if the IP address is actually a network
279
+ #
280
+ # ip = IPAddress("172.16.10.64/24")
281
+ # ip.network?
282
+ # #=> false
283
+ #
284
+ # ip = IPAddress("172.16.10.64/26")
285
+ # ip.network?
286
+ # #=> true
287
+ #
288
+ def network?
289
+ to_u32 | @prefix.to_u32 == @prefix.to_u32
290
+ end
291
+
292
+ #
293
+ # Returns a new IPv4 object with the network number
294
+ # for the given IP.
295
+ #
296
+ # ip = IPAddress("172.16.10.64/24")
297
+ # ip.network.to_s
298
+ # #=> "172.16.10.0/24"
299
+ #
300
+ def network
301
+ self.class.parse_u32(network_u32, @prefix)
302
+ end
303
+
304
+ #
305
+ # Returns a new IPv4 object with the
306
+ # first host IP address in the range.
307
+ #
308
+ # Example: given the 192.168.100.0/24 network, the first
309
+ # host IP address is 192.168.100.1.
310
+ #
311
+ # ip = IPAddress("192.168.100.0/24")
312
+ # ip.first.to_s
313
+ # #=> "192.168.100.1/24"
314
+ #
315
+ # The object IP doesn't need to be a network: the method
316
+ # automatically gets the network number from it
317
+ #
318
+ # ip = IPAddress("192.168.100.50/24")
319
+ # ip.first.to_s
320
+ # #=> "192.168.100.1/24"
321
+ #
322
+ def first
323
+ self.class.parse_u32(network_u32+1, @prefix)
324
+ end
325
+
326
+ #
327
+ # Like its sibling method IPv4#first, this method
328
+ # returns a new IPv4 object with the
329
+ # last host IP address in the range.
330
+ #
331
+ # Example: given the 192.168.100.0/24 network, the last
332
+ # host IP address is 192.168.100.1.
333
+ #
334
+ # ip = IPAddress("192.168.100.0/24")
335
+ # ip.last.to_s
336
+ # #=> "192.168.100.254/24"
337
+ #
338
+ # The object IP doesn't need to be a network: the method
339
+ # automatically gets the network number from it
340
+ #
341
+ # ip = IPAddress("192.168.100.50/24")
342
+ # ip.last.to_s
343
+ # #=> "192.168.100.254/24"
344
+ #
345
+ def last
346
+ self.class.parse_u32(broadcast_u32-1, @prefix)
347
+ end
348
+
349
+ #
350
+ # Iterates over all the hosts IP addresses for the given
351
+ # network (or IP address).
352
+ #
353
+ # ip = IPaddress("10.0.0.1/29")
354
+ # ip.each do |i|
355
+ # p i
356
+ # end
357
+ # #=> "10.0.0.1"
358
+ # #=> "10.0.0.2"
359
+ # #=> "10.0.0.3"
360
+ # #=> "10.0.0.4"
361
+ # #=> "10.0.0.5"
362
+ # #=> "10.0.0.6"
363
+ #
364
+ def each_host
365
+ hosts.each do |i|
366
+ yield i
367
+ end
368
+ end
369
+
370
+ #
371
+ # Iterates over all the IP addresses for the given
372
+ # network (or IP address).
373
+ #
374
+ # The object yielded is a new IPv4 object created
375
+ # from the iteration.
376
+ #
377
+ # ip = IPaddress("10.0.0.1/29")
378
+ # ip.each do |i|
379
+ # p i.address
380
+ # end
381
+ # #=> "10.0.0.0"
382
+ # #=> "10.0.0.1"
383
+ # #=> "10.0.0.2"
384
+ # #=> "10.0.0.3"
385
+ # #=> "10.0.0.4"
386
+ # #=> "10.0.0.5"
387
+ # #=> "10.0.0.6"
388
+ # #=> "10.0.0.7"
389
+ #
390
+ def each
391
+ (network_u32..broadcast_u32).each do |i|
392
+ yield self.class.parse_u32(i, @prefix)
393
+ end
394
+ end
395
+
396
+ #
397
+ # Spaceship operator to compare IP addresses
398
+ #
399
+ # An IP address is considered to be minor if it
400
+ # has a greater prefix (thus smaller hosts
401
+ # portion) and a smaller u32 value.
402
+ #
403
+ # For example, "10.100.100.1/8" is smaller than
404
+ # "172.16.0.1/16", but it's bigger than "10.100.100.1/16".
405
+ #
406
+ # Example:
407
+ #
408
+ # ip1 = IPAddress "10.100.100.1/8"
409
+ # ip2 = IPAddress ""172.16.0.1/16"
410
+ # ip3 = IPAddress ""10.100.100.1/16"
411
+ #
412
+ # ip1 < ip2
413
+ # #=> true
414
+ # ip1 < ip3
415
+ # #=> false
416
+ #
417
+ def <=>(oth)
418
+ if to_u32 > oth.to_u32
419
+ return 1
420
+ elsif to_u32 < oth.to_u32
421
+ return -1
422
+ else
423
+ if prefix < oth.prefix
424
+ return 1
425
+ elsif prefix > oth.prefix
426
+ return -1
427
+ end
428
+ end
429
+ return 0
430
+ end
431
+
432
+ #
433
+ # Returns the number of IP addresses included
434
+ # in the network. It also counts the network
435
+ # address and the broadcast address.
436
+ #
437
+ # ip = IPaddress("10.0.0.1/29")
438
+ # ip.size
439
+ # #=> 8
440
+ #
441
+ def size
442
+ broadcast_u32 - network_u32 + 1
443
+ end
444
+
445
+ #
446
+ # Returns an array with the IP addresses of
447
+ # all the hosts in the network.
448
+ #
449
+ # ip = IPaddress("10.0.0.1/29")
450
+ # ip.hosts.map {|i| i.address}
451
+ # #=> ["10.0.0.1",
452
+ # #=> "10.0.0.2",
453
+ # #=> "10.0.0.3",
454
+ # #=> "10.0.0.4",
455
+ # #=> "10.0.0.5",
456
+ # #=> "10.0.0.6"]
457
+ #
458
+ def hosts
459
+ to_a[1..-2]
460
+ end
461
+
462
+ #
463
+ # Returns the network number in Unsigned 32bits format
464
+ #
465
+ # ip = IPaddress("10.0.0.1/29")
466
+ # ip.network_u32
467
+ # #=> 167772160
468
+ #
469
+ def network_u32
470
+ to_u32 & @prefix.to_u32
471
+ end
472
+
473
+ #
474
+ # Returns the broadcast address in Unsigned 32bits format
475
+ #
476
+ # ip = IPaddress("10.0.0.1/29")
477
+ # ip.broadcast_u32
478
+ # #=> 167772167
479
+ #
480
+ def broadcast_u32
481
+ [to_u32 | ~@prefix.to_u32].pack("N").unpack("N").first
482
+ end
483
+
484
+ #
485
+ # Checks whether a subnet includes the given IP address.
486
+ #
487
+ # Accepts either string with the IP or and IPAddress::IPv4
488
+ # object.
489
+ #
490
+ # ip = IPAddress("192.168.10.100/24")
491
+ #
492
+ # addr = IPAddress("192.168.10.102/24")
493
+ # ip.include? addr
494
+ # #=> true
495
+ #
496
+ # ip.include? IPAddress("172.16.0.48/16")
497
+ # #=> false
498
+ #
499
+ def include?(oth)
500
+ @prefix <= oth.prefix and network_u32 == self.class.new(oth.address+"/#@prefix").network_u32
501
+ end
502
+
503
+ #
504
+ # Returns the IP address in in-addr.arpa format
505
+ # for DNS lookups
506
+ #
507
+ # ip = IPAddress("172.16.100.50/24")
508
+ # ip.reverse
509
+ # #=> "50.100.16.172.in-addr.arpa"
510
+ #
511
+ def reverse
512
+ @octets.reverse.join(".") + ".in-addr.arpa"
513
+ end
514
+
515
+ #
516
+ # Subnetting a network
517
+ #
518
+ # If the IP Address is a network, it can be divided into
519
+ # multiple networks. If +self+ is not a network, the
520
+ # method will calculate the network from the IP and then
521
+ # subnet it.
522
+ #
523
+ # If +subnets+ is an power of two number, the resulting
524
+ # networks will be divided evenly from the supernet.
525
+ #
526
+ # network = IPAddress("172.16.10.0/24")
527
+ # network / 4 # implies map{|i| i.to_s}
528
+ # #=> ["172.16.10.0/26",
529
+ # "172.16.10.64/26",
530
+ # "172.16.10.128/26",
531
+ # "172.16.10.192/26"]
532
+ #
533
+ # If +num+ is any other number, the supernet will be
534
+ # divided into some networks with a even number of hosts and
535
+ # other networks with the remaining addresses.
536
+ #
537
+ # network = IPAddress("172.16.10.0/24")
538
+ # network / 3 # implies map{|i| i.to_s}
539
+ # #=> ["172.16.10.0/26",
540
+ # "172.16.10.64/26",
541
+ # "172.16.10.128/25"]
542
+ #
543
+ # Returns an array of IPAddress objects
544
+ #
545
+ def subnet(subnets=2)
546
+ unless (1..(2**(32-prefix.to_i))).include? subnets
547
+ raise ArgumentError, "Value #{subnets} out of range"
548
+ end
549
+
550
+ calculate_subnets(subnets)
551
+ end
552
+ alias_method :/, :subnet
553
+
554
+ #
555
+ # Returns a new IPv4 object from the supernetting
556
+ # of the instance network.
557
+ #
558
+ # Supernetting is similar to subnetting, except
559
+ # that you getting as a result a network with a
560
+ # smaller prefix (bigger host space). For example,
561
+ # given the network
562
+ #
563
+ # ip = IPAddress("172.16.10.0/24")
564
+ #
565
+ # you can supernet it with a new /23 prefix
566
+ #
567
+ # ip.supernet(23).to_s
568
+ # #=> "172.16.10.0/23"
569
+ #
570
+ # However if you supernet it with a /22 prefix, the
571
+ # network address will change:
572
+ #
573
+ # ip.supernet(22).to_s
574
+ # #=> "172.16.8.0/22"
575
+ #
576
+ def supernet(new_prefix)
577
+ raise ArgumentError, "Can't supernet a /1 network" if new_prefix < 1
578
+ raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i
579
+ self.class.new(@address+"/#{new_prefix}").network
580
+ end
581
+
582
+ #
583
+ # Returns the difference between two IP addresses
584
+ # in unsigned int 32 bits format
585
+ #
586
+ def -(oth)
587
+ return (to_u32 - oth.to_u32).abs
588
+ end
589
+
590
+ #
591
+ # Returns a new IPv4 object which is the result
592
+ # of the summarization, if possible, of the two
593
+ # objects
594
+ #
595
+ # Example:
596
+ #
597
+ # ip1 = IPAddress("172.16.10.1/24")
598
+ # ip2 = IPAddress("172.16.11.2/24")
599
+ # puts ip1 + ip2
600
+ # #=>"172.16.10.0/23"
601
+ #
602
+ # If the networks are not contiguous, returns
603
+ # the two network numbers from the objects
604
+ #
605
+ def +(oth)
606
+ self.class.summarize(self,oth)
607
+ end
608
+
609
+ #
610
+ # Checks whether the ip address belongs to a
611
+ # RFC 791 CLASS A network, no matter
612
+ # what the subnet mask is.
613
+ #
614
+ # Example:
615
+ #
616
+ # ip = IPAddress("10.0.0.1/24")
617
+ # ip.a?
618
+ # #=> true
619
+ #
620
+ def a?
621
+ CLASSFUL.index(8) === bits
622
+ end
623
+
624
+ #
625
+ # Checks whether the ip address belongs to a
626
+ # RFC 791 CLASS B network, no matter
627
+ # what the subnet mask is.
628
+ #
629
+ # Example:
630
+ #
631
+ # ip = IPAddress("172.16.10.1/24")
632
+ # ip.b?
633
+ # #=> true
634
+ #
635
+ def b?
636
+ CLASSFUL.index(16) === bits
637
+ end
638
+
639
+ #
640
+ # Checks whether the ip address belongs to a
641
+ # RFC 791 CLASS C network, no matter
642
+ # what the subnet mask is.
643
+ #
644
+ # Example:
645
+ #
646
+ # ip = IPAddress("192.168.1.1/30")
647
+ # ip.c?
648
+ # #=> true
649
+ #
650
+ def c?
651
+ CLASSFUL.index(24) === bits
652
+ end
653
+
654
+ #
655
+ # Return the ip address in a format compatible
656
+ # with the IPv6 Mapped IPv4 addresses
657
+ #
658
+ # Example:
659
+ #
660
+ # ip = IPAddress("172.16.10.1/24")
661
+ # ip.to_ipv6
662
+ # #=> "ac10:0a01"
663
+ #
664
+ def to_ipv6
665
+ "%.4x:%.4x" % [to_u32].pack("N").unpack("nn")
666
+ end
667
+
668
+ #
669
+ # Creates a new IPv4 object from an
670
+ # unsigned 32bits integer.
671
+ #
672
+ # ip = IPAddress::IPv4::parse_u32(167772160)
673
+ # ip.prefix = 8
674
+ # ip.to_s
675
+ # #=> "10.0.0.0/8"
676
+ #
677
+ # The +prefix+ parameter is optional:
678
+ #
679
+ # ip = IPAddress::IPv4::parse_u32(167772160, 8)
680
+ # ip.to_s
681
+ # #=> "10.0.0.0/8"
682
+ #
683
+ def self.parse_u32(u32, prefix=nil)
684
+ ip = [u32].pack("N").unpack("C4").join(".")
685
+ if prefix
686
+ self.new(ip+"/#{prefix}")
687
+ else
688
+ self.new(ip)
689
+ end
690
+ end
691
+
692
+ #
693
+ # Creates a new IPv4 object from binary data,
694
+ # like the one you get from a network stream.
695
+ #
696
+ # For example, on a network stream the IP 172.16.0.1
697
+ # is represented with the binary "\254\020\n\001".
698
+ #
699
+ # ip = IPAddress::IPv4::parse_data "\254\020\n\001"
700
+ # ip.prefix = 24
701
+ #
702
+ # ip.to_s
703
+ # #=> "172.16.10.1/24"
704
+ #
705
+ def self.parse_data(str)
706
+ self.new str.unpack("C4").join(".")
707
+ end
708
+
709
+ #
710
+ # Exctract an IPv4 address from a string and
711
+ # returns a new object
712
+ #
713
+ # Example:
714
+ #
715
+ # str = "foobar172.16.10.1barbaz"
716
+ # ip = self.extract str
717
+ #
718
+ # ip.to_s
719
+ # #=> "172.16.10.1/16"
720
+ #
721
+ def self.extract(str)
722
+ self.new REGEXP.match(str).to_s
723
+ end
724
+
725
+ #
726
+ # Summarization (or aggregation) is the process when two or more
727
+ # networks are taken together to check if a supernet, including all
728
+ # and only these networks, exists. If it exists then this supernet
729
+ # is called the summarized (or aggregated) network.
730
+ #
731
+ # It is very important to understand that summarization can only
732
+ # occur if there are no holes in the aggregated network, or, in other
733
+ # words, if the given networks fill completely the address space
734
+ # of the supernet. So the two rules are:
735
+ #
736
+ # 1) The aggregate network must contain +all+ the IP addresses of the
737
+ # original networks;
738
+ # 2) The aggregate network must contain +only+ the IP addresses of the
739
+ # original networks;
740
+ #
741
+ # A few examples will help clarify the above. Let's consider for
742
+ # instance the following two networks:
743
+ #
744
+ # ip1 = IPAddress("172.16.10.0/24")
745
+ # ip2 = IPAddress("172.16.11.0/24")
746
+ #
747
+ # These two networks can be expressed using only one IP address
748
+ # network if we change the prefix. Let Ruby do the work:
749
+ #
750
+ # IPAddress::IPv4::summarize(ip1,ip2).to_s
751
+ # #=> "172.16.10.0/23"
752
+ #
753
+ # We note how the network "172.16.10.0/23" includes all the addresses
754
+ # specified in the above networks, and (more important) includes
755
+ # ONLY those addresses.
756
+ #
757
+ # If we summarized +ip1+ and +ip2+ with the following network:
758
+ #
759
+ # "172.16.0.0/16"
760
+ #
761
+ # we would have satisfied rule #1 above, but not rule #2. So "172.16.0.0/16"
762
+ # is not an aggregate network for +ip1+ and +ip2+.
763
+ #
764
+ # If it's not possible to compute a single aggregated network for all the
765
+ # original networks, the method returns an array with all the aggregate
766
+ # networks found. For example, the following four networks can be
767
+ # aggregated in a single /22:
768
+ #
769
+ # ip1 = IPAddress("10.0.0.1/24")
770
+ # ip2 = IPAddress("10.0.1.1/24")
771
+ # ip3 = IPAddress("10.0.2.1/24")
772
+ # ip4 = IPAddress("10.0.3.1/24")
773
+ # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_s
774
+ # #=> "10.0.0.0/22",
775
+ #
776
+ # But the following networks can't be summarized in a single network:
777
+ #
778
+ # ip1 = IPAddress("10.0.1.1/24")
779
+ # ip2 = IPAddress("10.0.2.1/24")
780
+ # ip3 = IPAddress("10.0.3.1/24")
781
+ # ip4 = IPAddress("10.0.4.1/24")
782
+ # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_s}
783
+ # #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"]
784
+ #
785
+ def self.summarize(*args)
786
+ # one network? no need to summarize
787
+ return args.flatten.first if args.size == 1
788
+
789
+ result, arr, last = [], args.sort, args.sort.last.network
790
+ arr.each_cons(2) do |x,y|
791
+ snet = x.supernet(x.prefix.to_i-1)
792
+ if snet.include? y
793
+ result << snet
794
+ else
795
+ result << x.network unless result.any?{|i| i.include? x}
796
+ end
797
+ end
798
+ result << last unless result.any?{|i| i.include? last}
799
+
800
+ if result.size == args.size
801
+ return result
802
+ else
803
+ return self.summarize(*result)
804
+ end
805
+ end
806
+
807
+ #
808
+ # private methods
809
+ #
810
+ private
811
+
812
+ def bits_from_address(ip)
813
+ ip.split(".").map{|i| i.to_i}.pack("C4").unpack("B*").first
814
+ end
815
+
816
+ def prefix_from_ip(ip)
817
+ bits = bits_from_address(ip)
818
+ CLASSFUL.each {|reg,prefix| return Prefix32.new(prefix) if bits =~ reg}
819
+ end
820
+
821
+ def calculate_subnets(subnets)
822
+ po2 = subnets.closest_power_of_2
823
+ new_prefix = @prefix.to_i + Math::log2(po2).to_i
824
+ networks = Array.new
825
+ (0..po2-1).each do |i|
826
+ mul = i * (2**(32-new_prefix))
827
+ networks << IPAddress::IPv4.parse_u32(network_u32+mul, new_prefix)
828
+ end
829
+ until networks.size == subnets
830
+ networks = sum_first_found(networks)
831
+ end
832
+ return networks
833
+ end
834
+
835
+ def sum_first_found(arr)
836
+ dup = arr.dup.reverse
837
+ dup.each_with_index do |obj,i|
838
+ a = [IPAddress::IPv4.summarize(obj,dup[i+1])].flatten
839
+ if a.size == 1
840
+ dup[i..i+1] = a
841
+ return dup.reverse
842
+ end
843
+ end
844
+ return dup.reverse
845
+ end
846
+
847
+ end # class IPv4
848
+ end # module IPAddress
849
+