ipaddress 0.7.5 → 0.8.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.
@@ -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"