ipaddress 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rcov/rcovtask'
5
+
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "ipaddress"
11
+ gem.summary = %Q{IPv4/IPv6 addresses manipulation library}
12
+ gem.email = "ceresa@gmail.com"
13
+ gem.homepage = "http://github.com/bluemonk/ipaddress"
14
+ gem.authors = ["Marco Ceresa"]
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/*_test.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/*_test.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ if File.exist?('VERSION.yml')
48
+ config = YAML.load(File.read('VERSION.yml'))
49
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
50
+ else
51
+ version = ""
52
+ end
53
+
54
+ rdoc.rdoc_dir = 'rdoc'
55
+ rdoc.title = "ipaddress #{version}"
56
+ rdoc.rdoc_files.include('README*')
57
+ rdoc.rdoc_files.include('lib/**/*.rb')
58
+ end
59
+
60
+ desc "Open an irb session preloaded with this library"
61
+ task :console do
62
+ sh "irb -rubygems -I lib -r ipaddress.rb"
63
+ end
64
+
65
+ desc "Look for TODO and FIXME tags in the code"
66
+ task :todo do
67
+ def egrep(pattern)
68
+ Dir['**/*.rb'].each do |fn|
69
+ count = 0
70
+ open(fn) do |f|
71
+ while line = f.gets
72
+ count += 1
73
+ if line =~ pattern
74
+ puts "#{fn}:#{count}:#{line}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ egrep /(FIXME|TODO|TBD)/
81
+ end
82
+
83
+ begin
84
+ require 'jeweler'
85
+ Jeweler::Tasks.new do |gemspec|
86
+ # omitted for brevity
87
+ end
88
+ Jeweler::GemcutterTasks.new
89
+ rescue LoadError
90
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
91
+ 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 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
+