ipaddress 0.7.5 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,7 +22,7 @@ module IPAddress;
22
22
  # bits or two octect. For example, the following is a valid IPv6
23
23
  # address:
24
24
  #
25
- # 1080:0000:0000:0000:0008:0800:200c:417a
25
+ # 2001:0db8:0000:0000:0008:0800:200c:417a
26
26
  #
27
27
  # Letters in an IPv6 address are usually written downcase, as per
28
28
  # RFC. You can create a new IPv6 object using uppercase letters, but
@@ -42,7 +42,7 @@ module IPAddress;
42
42
  # Using compression, the IPv6 address written above can be shorten into
43
43
  # the following, equivalent, address
44
44
  #
45
- # 1080::8:800:200c:417a
45
+ # 2001:db8::8:800:200c:417a
46
46
  #
47
47
  # This short version is often used in human representation.
48
48
  #
@@ -51,7 +51,7 @@ module IPAddress;
51
51
  # As we used to do with IPv4 addresses, an IPv6 address can be written
52
52
  # using the prefix notation to specify the subnet mask:
53
53
  #
54
- # 1080::8:800:200c:417a/64
54
+ # 2001:db8::8:800:200c:417a/64
55
55
  #
56
56
  # The /64 part means that the first 64 bits of the address are
57
57
  # representing the network portion, and the last 64 bits are the host
@@ -75,9 +75,9 @@ module IPAddress;
75
75
  #
76
76
  # An IPv6 address can be expressed in any of the following forms:
77
77
  #
78
- # * "1080:0000:0000:0000:0008:0800:200C:417A": IPv6 address with no compression
79
- # * "1080:0:0:0:8:800:200C:417A": IPv6 address with leading zeros compression
80
- # * "1080::8:800:200C:417A": IPv6 address with full compression
78
+ # * "2001:0db8:0000:0000:0008:0800:200C:417A": IPv6 address with no compression
79
+ # * "2001:db8:0:0:8:800:200C:417A": IPv6 address with leading zeros compression
80
+ # * "2001:db8::8:800:200C:417A": IPv6 address with full compression
81
81
  #
82
82
  # In all these 3 cases, a new IPv6 address object will be created, using the default
83
83
  # subnet mask /128
@@ -328,6 +328,36 @@ module IPAddress;
328
328
  to_u128 & @prefix.to_u128
329
329
  end
330
330
 
331
+ #
332
+ # Returns the broadcast address in Unsigned 128bits format
333
+ #
334
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
335
+ #
336
+ # ip6.broadcast_u128
337
+ # #=> 42540766411282592875350729025363378175
338
+ #
339
+ # Please note that there is no Broadcast concept in IPv6
340
+ # addresses as in IPv4 addresses, and this method is just
341
+ # an helper to other functions.
342
+ #
343
+ def broadcast_u128
344
+ network_u128 + size - 1
345
+ end
346
+
347
+ #
348
+ # Returns the number of IP addresses included
349
+ # in the network. It also counts the network
350
+ # address and the broadcast address.
351
+ #
352
+ # ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
353
+ #
354
+ # ip6.size
355
+ # #=> 18446744073709551616
356
+ #
357
+ def size
358
+ 2 ** @prefix.host_prefix
359
+ end
360
+
331
361
  #
332
362
  # Checks whether a subnet includes the given IP address.
333
363
  #
@@ -384,7 +414,74 @@ module IPAddress;
384
414
  def mapped?
385
415
  to_u128 >> 32 == 0xffff
386
416
  end
387
-
417
+
418
+ #
419
+ # Iterates over all the IP addresses for the given
420
+ # network (or IP address).
421
+ #
422
+ # The object yielded is a new IPv6 object created
423
+ # from the iteration.
424
+ #
425
+ # ip6 = IPAddress("2001:db8::4/125")
426
+ #
427
+ # ip6.each do |i|
428
+ # p i.compressed
429
+ # end
430
+ # #=> "2001:db8::"
431
+ # #=> "2001:db8::1"
432
+ # #=> "2001:db8::2"
433
+ # #=> "2001:db8::3"
434
+ # #=> "2001:db8::4"
435
+ # #=> "2001:db8::5"
436
+ # #=> "2001:db8::6"
437
+ # #=> "2001:db8::7"
438
+ #
439
+ # WARNING: if the host portion is very large, this method
440
+ # can be very slow and possibly hang your system!
441
+ #
442
+ def each
443
+ (network_u128..broadcast_u128).each do |i|
444
+ yield self.class.parse_u128(i, @prefix)
445
+ end
446
+ end
447
+
448
+ #
449
+ # Spaceship operator to compare IPv6 objects
450
+ #
451
+ # Comparing IPv6 addresses is useful to ordinate
452
+ # them into lists that match our intuitive
453
+ # perception of ordered IP addresses.
454
+ #
455
+ # The first comparison criteria is the u128 value.
456
+ # For example, 2001:db8:1::1 will be considered
457
+ # to be less than 2001:db8:2::1, because, in a ordered list,
458
+ # we expect 2001:db8:1::1 to come before 2001:db8:2::1.
459
+ #
460
+ # The second criteria, in case two IPv6 objects
461
+ # have identical addresses, is the prefix. An higher
462
+ # prefix will be considered greater than a lower
463
+ # prefix. This is because we expect to see
464
+ # 2001:db8:1::1/64 come before 2001:db8:1::1/65
465
+ #
466
+ # Example:
467
+ #
468
+ # ip1 = IPAddress "2001:db8:1::1/64"
469
+ # ip2 = IPAddress "2001:db8:2::1/64"
470
+ # ip3 = IPAddress "2001:db8:1::1/65"
471
+ #
472
+ # ip1 < ip2
473
+ # #=> true
474
+ # ip1 < ip3
475
+ # #=> false
476
+ #
477
+ # [ip1,ip2,ip3].sort.map{|i| i.to_string}
478
+ # #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"]
479
+ #
480
+ def <=>(oth)
481
+ return prefix <=> oth.prefix if to_u128 == oth.to_u128
482
+ to_u128 <=> oth.to_u128
483
+ end
484
+
388
485
  #
389
486
  # Returns the address portion of an IP in binary format,
390
487
  # as a string containing a sequence of 0 and 1
@@ -430,6 +527,19 @@ module IPAddress;
430
527
  @address.gsub(":","-") + ".ipv6-literal.net"
431
528
  end
432
529
 
530
+ #
531
+ # Returns a new IPv6 object with the network number
532
+ # for the given IP.
533
+ #
534
+ # ip = IPAddress "2001:db8:1:1:1:1:1:1/32"
535
+ #
536
+ # ip.network.to_string
537
+ # #=> "2001:db8::/32"
538
+ #
539
+ def network
540
+ self.class.parse_u128(network_u128, @prefix)
541
+ end
542
+
433
543
  #
434
544
  # Extract 16 bits groups from a string
435
545
  #
@@ -470,18 +580,18 @@ module IPAddress;
470
580
  # Creates a new IPv6 object from an
471
581
  # unsigned 128 bits integer.
472
582
  #
473
- # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066)
583
+ # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122)
474
584
  # ip6.prefix = 64
475
585
  #
476
- # ip6.to_s
477
- # #=> "1080::8:800:200c:417a/64"
586
+ # ip6.to_string
587
+ # #=> "2001:db8::8:800:200c:417a/64"
478
588
  #
479
589
  # The +prefix+ parameter is optional:
480
590
  #
481
- # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066, 64)
591
+ # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122, 64)
482
592
  #
483
- # ip6.to_s
484
- # #=> "1080::8:800:200c:417a/64"
593
+ # ip6.to_string
594
+ # #=> "2001:db8::8:800:200c:417a/64"
485
595
  #
486
596
  def self.parse_u128(u128, prefix=128)
487
597
  str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff}
@@ -495,15 +605,15 @@ module IPAddress;
495
605
  # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a")
496
606
  # ip6.prefix = 64
497
607
  #
498
- # ip6.to_s
608
+ # ip6.to_string
499
609
  # #=> "2001:db8::8:800:200c:417a/64"
500
610
  #
501
611
  # The +prefix+ parameter is optional:
502
612
  #
503
613
  # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64)
504
614
  #
505
- # ip6.to_s
506
- # #=> "1080::8:800:200c:417a/64"
615
+ # ip6.to_string
616
+ # #=> "2001:db8::8:800:200c:417a/64"
507
617
  #
508
618
  def self.parse_hex(hex, prefix=128)
509
619
  self.parse_u128(hex.hex, prefix)
@@ -605,14 +715,14 @@ module IPAddress;
605
715
  #
606
716
  # ip = IPAddress::IPv6::Loopback.new
607
717
  #
608
- # ip.to_s
718
+ # ip.to_string
609
719
  # #=> "::1/128"
610
720
  #
611
721
  # or by using the wrapper:
612
722
  #
613
723
  # ip = IPAddress "::1"
614
724
  #
615
- # ip.to_s
725
+ # ip.to_string
616
726
  # #=> "::1/128"
617
727
  #
618
728
  # Checking if an address is loopback is easy with the IPv6#loopback?
@@ -629,7 +739,7 @@ module IPAddress;
629
739
  #
630
740
  # ip = IPAddress::IPv6::Loopback.new
631
741
  #
632
- # ip.to_s
742
+ # ip.to_string
633
743
  # #=> "::1/128"
634
744
  #
635
745
  def initialize
@@ -668,7 +778,7 @@ module IPAddress;
668
778
  # ip6.mapped?
669
779
  # #=> true
670
780
  #
671
- # ip6.to_s
781
+ # ip6.to_string
672
782
  # #=> "::FFFF:172.16.10.1/128"
673
783
  #
674
784
  # Now with the +ipv4+ attribute, we can easily access the IPv4 portion
@@ -695,7 +805,7 @@ module IPAddress;
695
805
  # That is, two colons and the IPv4 address. However, as by RFC, the ffff
696
806
  # group will be automatically added at the beginning
697
807
  #
698
- # ip6.to_s
808
+ # ip6.to_string
699
809
  # => "::ffff:172.16.10.1/128"
700
810
  #
701
811
  # making it a mapped IPv6 compatible address.
@@ -718,7 +828,7 @@ module IPAddress;
718
828
  #
719
829
  # ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403"
720
830
  #
721
- # ip6.to_s
831
+ # ip6.to_string
722
832
  # #=> "::ffff:13.1.68.3"
723
833
  #
724
834
  def initialize(str)
@@ -18,7 +18,7 @@ module IPAddress
18
18
  # IPAddress::Prefix shouldn't be accesses directly, unless
19
19
  # for particular needs.
20
20
  #
21
- class Prefix
21
+ class Prefix
22
22
 
23
23
  include Comparable
24
24
 
@@ -247,6 +247,19 @@ module IPAddress
247
247
  bits.to_i(2)
248
248
  end
249
249
 
250
+ #
251
+ # Returns the length of the host portion
252
+ # of a netmask.
253
+ #
254
+ # prefix = Prefix128.new 96
255
+ #
256
+ # prefix.host_prefix
257
+ # #=> 32
258
+ #
259
+ def host_prefix
260
+ 128 - @prefix
261
+ end
262
+
250
263
  end # class Prefix123 < Prefix
251
264
 
252
265
  end # module IPAddress
@@ -304,20 +304,20 @@ class IPv4Test < Test::Unit::TestCase
304
304
  assert_equal "1.10.16.172.in-addr.arpa", @ip.reverse
305
305
  end
306
306
 
307
- def test_method_comparabble
307
+ def test_method_compare
308
308
  ip1 = @klass.new("10.1.1.1/8")
309
309
  ip2 = @klass.new("10.1.1.1/16")
310
310
  ip3 = @klass.new("172.16.1.1/14")
311
311
  ip4 = @klass.new("10.1.1.1/8")
312
312
 
313
- # ip1 should be major than ip2
314
- assert_equal true, ip1 > ip2
315
- assert_equal false, ip1 < ip2
316
- assert_equal false, ip2 > ip1
317
- # ip2 should be minor than ip3
313
+ # ip2 should be greater than ip1
314
+ assert_equal true, ip1 < ip2
315
+ assert_equal false, ip1 > ip2
316
+ assert_equal false, ip2 < ip1
317
+ # ip2 should be less than ip3
318
318
  assert_equal true, ip2 < ip3
319
319
  assert_equal false, ip2 > ip3
320
- # ip1 should be minor than ip3
320
+ # ip1 should be less than ip3
321
321
  assert_equal true, ip1 < ip3
322
322
  assert_equal false, ip1 > ip3
323
323
  assert_equal false, ip3 < ip1
@@ -326,7 +326,13 @@ class IPv4Test < Test::Unit::TestCase
326
326
  # ip1 should be equal to ip4
327
327
  assert_equal true, ip1 == ip4
328
328
  # test sorting
329
- arr = ["10.1.1.1/16","10.1.1.1/8","172.16.1.1/14"]
329
+ arr = ["10.1.1.1/8","10.1.1.1/16","172.16.1.1/14"]
330
+ assert_equal arr, [ip1,ip2,ip3].sort.map{|s| s.to_string}
331
+ # test same prefix
332
+ ip1 = @klass.new("10.0.0.0/24")
333
+ ip2 = @klass.new("10.0.0.0/16")
334
+ ip3 = @klass.new("10.0.0.0/8")
335
+ arr = ["10.0.0.0/8","10.0.0.0/16","10.0.0.0/24"]
330
336
  assert_equal arr, [ip1,ip2,ip3].sort.map{|s| s.to_string}
331
337
  end
332
338
 
@@ -371,41 +377,57 @@ class IPv4Test < Test::Unit::TestCase
371
377
  assert_equal 24, ip.prefix.to_i
372
378
  end
373
379
 
374
- def test_method_subnet
375
- assert_raise(ArgumentError) {@ip.subnet(0)}
376
- assert_raise(ArgumentError) {@ip.subnet(257)}
377
-
378
- arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
379
- "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
380
- "172.16.10.192/27", "172.16.10.224/27"]
381
- assert_equal arr, @network.subnet(8).map {|s| s.to_string}
382
- arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
383
- "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
384
- "172.16.10.192/26"]
385
- assert_equal arr, @network.subnet(7).map {|s| s.to_string}
386
- arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
387
- "172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"]
388
- assert_equal arr, @network.subnet(6).map {|s| s.to_string}
389
- arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
390
- "172.16.10.96/27", "172.16.10.128/25"]
391
- assert_equal arr, @network.subnet(5).map {|s| s.to_string}
392
- arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
393
- "172.16.10.192/26"]
394
- assert_equal arr, @network.subnet(4).map {|s| s.to_string}
395
- arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"]
396
- assert_equal arr, @network.subnet(3).map {|s| s.to_string}
397
- arr = ["172.16.10.0/25", "172.16.10.128/25"]
398
- assert_equal arr, @network.subnet(2).map {|s| s.to_string}
399
- arr = ["172.16.10.0/24"]
400
- assert_equal arr, @network.subnet(1).map {|s| s.to_string}
401
- end
402
-
403
- def test_method_supernet
404
- assert_raise(ArgumentError) {@ip.supernet(0)}
405
- assert_raise(ArgumentError) {@ip.supernet(24)}
406
- assert_equal "172.16.10.0/23", @ip.supernet(23).to_string
407
- assert_equal "172.16.8.0/22", @ip.supernet(22).to_string
408
- end
380
+ def test_method_split
381
+ assert_raise(ArgumentError) {@ip.split(0)}
382
+ assert_raise(ArgumentError) {@ip.split(257)}
383
+
384
+ assert_equal @ip.network, @ip.split(1).first
385
+
386
+ arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
387
+ "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
388
+ "172.16.10.192/27", "172.16.10.224/27"]
389
+ assert_equal arr, @network.split(8).map {|s| s.to_string}
390
+ arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
391
+ "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
392
+ "172.16.10.192/26"]
393
+ assert_equal arr, @network.split(7).map {|s| s.to_string}
394
+ arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
395
+ "172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"]
396
+ assert_equal arr, @network.split(6).map {|s| s.to_string}
397
+ arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
398
+ "172.16.10.96/27", "172.16.10.128/25"]
399
+ assert_equal arr, @network.split(5).map {|s| s.to_string}
400
+ arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
401
+ "172.16.10.192/26"]
402
+ assert_equal arr, @network.split(4).map {|s| s.to_string}
403
+ arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"]
404
+ assert_equal arr, @network.split(3).map {|s| s.to_string}
405
+ arr = ["172.16.10.0/25", "172.16.10.128/25"]
406
+ assert_equal arr, @network.split(2).map {|s| s.to_string}
407
+ arr = ["172.16.10.0/24"]
408
+ assert_equal arr, @network.split(1).map {|s| s.to_string}
409
+ end
410
+
411
+ def test_method_subnet
412
+ assert_raise(ArgumentError) {@network.subnet(23)}
413
+ assert_raise(ArgumentError) {@network.subnet(33)}
414
+ assert_nothing_raised {@ip.subnet(30)}
415
+ arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
416
+ "172.16.10.192/26"]
417
+ assert_equal arr, @network.subnet(26).map {|s| s.to_string}
418
+ arr = ["172.16.10.0/25", "172.16.10.128/25"]
419
+ assert_equal arr, @network.subnet(25).map {|s| s.to_string}
420
+ arr = ["172.16.10.0/24"]
421
+ assert_equal arr, @network.subnet(24).map {|s| s.to_string}
422
+ end
423
+
424
+ def test_method_supernet
425
+ assert_raise(ArgumentError) {@ip.supernet(24)}
426
+ assert_equal "0.0.0.0/0", @ip.supernet(0).to_string
427
+ assert_equal "0.0.0.0/0", @ip.supernet(-2).to_string
428
+ assert_equal "172.16.10.0/23", @ip.supernet(23).to_string
429
+ assert_equal "172.16.8.0/22", @ip.supernet(22).to_string
430
+ end
409
431
 
410
432
  def test_classmethod_parse_u32
411
433
  @decimal_values.each do |addr,int|
@@ -32,6 +32,11 @@ class IPv6Test < Test::Unit::TestCase
32
32
 
33
33
  @invalid_ipv6 = [":1:2:3:4:5:6:7",
34
34
  ":1:2:3:4:5:6:7"]
35
+
36
+ @networks = {
37
+ "2001:db8:1:1:1:1:1:1/32" => "2001:db8::/32",
38
+ "2001:db8:1:1:1:1:1::/32" => "2001:db8::/32",
39
+ "2001:db8::1/64" => "2001:db8::/64"}
35
40
 
36
41
  @ip = @klass.new "2001:db8::8:800:200c:417a/64"
37
42
  @network = @klass.new "2001:db8:8:800::/64"
@@ -122,7 +127,22 @@ class IPv6Test < Test::Unit::TestCase
122
127
  def test_method_network_u128
123
128
  assert_equal 42540766411282592856903984951653826560, @ip.network_u128
124
129
  end
125
-
130
+
131
+ def test_method_broadcast_u128
132
+ assert_equal 42540766411282592875350729025363378175, @ip.broadcast_u128
133
+ end
134
+
135
+ def test_method_size
136
+ ip = @klass.new("2001:db8::8:800:200c:417a/64")
137
+ assert_equal 2**64, ip.size
138
+ ip = @klass.new("2001:db8::8:800:200c:417a/32")
139
+ assert_equal 2**96, ip.size
140
+ ip = @klass.new("2001:db8::8:800:200c:417a/120")
141
+ assert_equal 2**8, ip.size
142
+ ip = @klass.new("2001:db8::8:800:200c:417a/124")
143
+ assert_equal 2**4, ip.size
144
+ end
145
+
126
146
  def test_method_include?
127
147
  assert_equal true, @ip.include?(@ip)
128
148
  # test prefix on same address
@@ -186,7 +206,53 @@ class IPv6Test < Test::Unit::TestCase
186
206
  assert_equal true, @klass.new("::1").loopback?
187
207
  assert_equal false, @ip.loopback?
188
208
  end
189
-
209
+
210
+ def test_method_network
211
+ @networks.each do |addr,net|
212
+ ip = @klass.new addr
213
+ assert_instance_of @klass, ip.network
214
+ assert_equal net, ip.network.to_string
215
+ end
216
+ end
217
+
218
+ def test_method_each
219
+ ip = @klass.new("2001:db8::4/125")
220
+ arr = []
221
+ ip.each {|i| arr << i.compressed}
222
+ expected = ["2001:db8::","2001:db8::1","2001:db8::2",
223
+ "2001:db8::3","2001:db8::4","2001:db8::5",
224
+ "2001:db8::6","2001:db8::7"]
225
+ assert_equal expected, arr
226
+ end
227
+
228
+ def test_method_compare
229
+ ip1 = @klass.new("2001:db8:1::1/64")
230
+ ip2 = @klass.new("2001:db8:2::1/64")
231
+ ip3 = @klass.new("2001:db8:1::2/64")
232
+ ip4 = @klass.new("2001:db8:1::1/65")
233
+
234
+ # ip2 should be greater than ip1
235
+ assert_equal true, ip2 > ip1
236
+ assert_equal false, ip1 > ip2
237
+ assert_equal false, ip2 < ip1
238
+ # ip3 should be less than ip2
239
+ assert_equal true, ip2 > ip3
240
+ assert_equal false, ip2 < ip3
241
+ # ip1 should be less than ip3
242
+ assert_equal true, ip1 < ip3
243
+ assert_equal false, ip1 > ip3
244
+ assert_equal false, ip3 < ip1
245
+ # ip1 should be equal to itself
246
+ assert_equal true, ip1 == ip1
247
+ # ip4 should be greater than ip1
248
+ assert_equal true, ip1 < ip4
249
+ assert_equal false, ip1 > ip4
250
+ # test sorting
251
+ arr = ["2001:db8:1::1/64","2001:db8:1::1/65",
252
+ "2001:db8:1::2/64","2001:db8:2::1/64"]
253
+ assert_equal arr, [ip1,ip2,ip3,ip4].sort.map{|s| s.to_string}
254
+ end
255
+
190
256
  def test_classmethod_expand
191
257
  compressed = "2001:db8:0:cd30::"
192
258
  expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000"