construqt-ipaddress 0.8.1

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,1003 @@
1
+ require 'ipaddress/prefix'
2
+
3
+ module IPAddress;
4
+ #
5
+ # =Name
6
+ #
7
+ # IPAddress::IPv6 - IP version 6 address manipulation library
8
+ #
9
+ # =Synopsis
10
+ #
11
+ # require 'ipaddress'
12
+ #
13
+ # =Description
14
+ #
15
+ # Class IPAddress::IPv6 is used to handle IPv6 type addresses.
16
+ #
17
+ # == IPv6 addresses
18
+ #
19
+ # IPv6 addresses are 128 bits long, in contrast with IPv4 addresses
20
+ # which are only 32 bits long. An IPv6 address is generally written as
21
+ # eight groups of four hexadecimal digits, each group representing 16
22
+ # bits or two octect. For example, the following is a valid IPv6
23
+ # address:
24
+ #
25
+ # 2001:0db8:0000:0000:0008:0800:200c:417a
26
+ #
27
+ # Letters in an IPv6 address are usually written downcase, as per
28
+ # RFC. You can create a new IPv6 object using uppercase letters, but
29
+ # they will be converted.
30
+ #
31
+ # === Compression
32
+ #
33
+ # Since IPv6 addresses are very long to write, there are some
34
+ # semplifications and compressions that you can use to shorten them.
35
+ #
36
+ # * Leading zeroes: all the leading zeroes within a group can be
37
+ # omitted: "0008" would become "8"
38
+ #
39
+ # * A string of consecutive zeroes can be replaced by the string
40
+ # "::". This can be only applied once.
41
+ #
42
+ # Using compression, the IPv6 address written above can be shorten into
43
+ # the following, equivalent, address
44
+ #
45
+ # 2001:db8::8:800:200c:417a
46
+ #
47
+ # This short version is often used in human representation.
48
+ #
49
+ # === Network Mask
50
+ #
51
+ # As we used to do with IPv4 addresses, an IPv6 address can be written
52
+ # using the prefix notation to specify the subnet mask:
53
+ #
54
+ # 2001:db8::8:800:200c:417a/64
55
+ #
56
+ # The /64 part means that the first 64 bits of the address are
57
+ # representing the network portion, and the last 64 bits are the host
58
+ # portion.
59
+ #
60
+ #
61
+ class IPv6
62
+
63
+ include IPAddress
64
+ include Enumerable
65
+ include Comparable
66
+
67
+
68
+ #
69
+ # Format string to pretty print IPv6 addresses
70
+ #
71
+ IN6FORMAT = ("%.4x:"*8).chop
72
+
73
+ #
74
+ # Creates a new IPv6 address object.
75
+ #
76
+ # An IPv6 address can be expressed in any of the following forms:
77
+ #
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
+ #
82
+ # In all these 3 cases, a new IPv6 address object will be created, using the default
83
+ # subnet mask /128
84
+ #
85
+ # You can also specify the subnet mask as with IPv4 addresses:
86
+ #
87
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
88
+ #
89
+ def initialize(str)
90
+ ip, netmask = str.split("/")
91
+
92
+ if str =~ /:.+\./
93
+ raise ArgumentError, "Please use #{self.class}::Mapped for IPv4 mapped addresses"
94
+ end
95
+
96
+ if IPAddress.valid_ipv6?(ip)
97
+ @groups = self.class.groups(ip)
98
+ @address = IN6FORMAT % @groups
99
+ @compressed = compress_address
100
+ else
101
+ raise ArgumentError, "Invalid IP #{ip.inspect}"
102
+ end
103
+
104
+ @prefix = Prefix128.new(netmask ? netmask : 128)
105
+
106
+ end # def initialize
107
+
108
+ #
109
+ # Returns the IPv6 address in uncompressed form:
110
+ #
111
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
112
+ #
113
+ # ip6.address
114
+ # #=> "2001:0db8:0000:0000:0008:0800:200c:417a"
115
+ #
116
+ def address
117
+ @address
118
+ end
119
+
120
+ #
121
+ # Returns an array with the 16 bits groups in decimal
122
+ # format:
123
+ #
124
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
125
+ #
126
+ # ip6.groups
127
+ # #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762]
128
+ #
129
+ def groups
130
+ @groups
131
+ end
132
+
133
+ #
134
+ # Returns an instance of the prefix object
135
+ #
136
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
137
+ #
138
+ # ip6.prefix
139
+ # #=> 64
140
+ #
141
+ def prefix
142
+ @prefix
143
+ end
144
+
145
+ #
146
+ # Set a new prefix number for the object
147
+ #
148
+ # This is useful if you want to change the prefix
149
+ # to an object created with IPv6::parse_u128 or
150
+ # if the object was created using the default prefix
151
+ # of 128 bits.
152
+ #
153
+ # ip6 = IPAddress("2001:db8::8:800:200c:417a")
154
+ #
155
+ # puts ip6.to_string
156
+ # #=> "2001:db8::8:800:200c:417a/128"
157
+ #
158
+ # ip6.prefix = 64
159
+ # puts ip6.to_string
160
+ # #=> "2001:db8::8:800:200c:417a/64"
161
+ #
162
+ def prefix=(num)
163
+ @prefix = Prefix128.new(num)
164
+ end
165
+
166
+ #
167
+ # Unlike its counterpart IPv6#to_string method, IPv6#to_string_uncompressed
168
+ # returns the whole IPv6 address and prefix in an uncompressed form
169
+ #
170
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
171
+ #
172
+ # ip6.to_string_uncompressed
173
+ # #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64"
174
+ #
175
+ def to_string_uncompressed
176
+ "#@address/#@prefix"
177
+ end
178
+
179
+ #
180
+ # Returns the IPv6 address in a human readable form,
181
+ # using the compressed address.
182
+ #
183
+ # ip6 = IPAddress "2001:0db8:0000:0000:0008:0800:200c:417a/64"
184
+ #
185
+ # ip6.to_string
186
+ # #=> "2001:db8::8:800:200c:417a/64"
187
+ #
188
+ def to_string
189
+ "#@compressed/#@prefix"
190
+ end
191
+
192
+ #
193
+ # Returns the IPv6 address in a human readable form,
194
+ # using the compressed address.
195
+ #
196
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
197
+ #
198
+ # ip6.to_s
199
+ # #=> "2001:db8::8:800:200c:417a"
200
+ #
201
+ def to_s
202
+ @compressed
203
+ end
204
+
205
+ #
206
+ # Returns a decimal format (unsigned 128 bit) of the
207
+ # IPv6 address
208
+ #
209
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
210
+ #
211
+ # ip6.to_i
212
+ # #=> 42540766411282592856906245548098208122
213
+ #
214
+ def to_i
215
+ to_hex.hex
216
+ end
217
+ alias_method :to_u128, :to_i
218
+
219
+ #
220
+ # True if the IPv6 address is a network
221
+ #
222
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
223
+ #
224
+ # ip6.network?
225
+ # #=> false
226
+ #
227
+ # ip6 = IPAddress "2001:db8:8:800::/64"
228
+ #
229
+ # ip6.network?
230
+ # #=> true
231
+ #
232
+ def network?
233
+ to_u128 | @prefix.to_u128 == @prefix.to_u128
234
+ end
235
+
236
+ #
237
+ # Returns the 16-bits value specified by index
238
+ #
239
+ # ip = IPAddress("2001:db8::8:800:200c:417a/64")
240
+ #
241
+ # ip[0]
242
+ # #=> 8193
243
+ # ip[1]
244
+ # #=> 3512
245
+ # ip[2]
246
+ # #=> 0
247
+ # ip[3]
248
+ # #=> 0
249
+ #
250
+ def [](index)
251
+ @groups[index]
252
+ end
253
+ alias_method :group, :[]
254
+
255
+ #
256
+ # Returns a Base16 number representing the IPv6
257
+ # address
258
+ #
259
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
260
+ #
261
+ # ip6.to_hex
262
+ # #=> "20010db80000000000080800200c417a"
263
+ #
264
+ def to_hex
265
+ hexs.join("")
266
+ end
267
+
268
+ # Returns the address portion of an IPv6 object
269
+ # in a network byte order format.
270
+ #
271
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
272
+ #
273
+ # ip6.data
274
+ # #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
275
+ #
276
+ # It is usually used to include an IP address
277
+ # in a data packet to be sent over a socket
278
+ #
279
+ # a = Socket.open(params) # socket details here
280
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
281
+ # binary_data = ["Address: "].pack("a*") + ip.data
282
+ #
283
+ # # Send binary data
284
+ # a.puts binary_data
285
+ #
286
+ def data
287
+ @groups.pack("n8")
288
+ end
289
+
290
+ #
291
+ # Returns an array of the 16 bits groups in hexdecimal
292
+ # format:
293
+ #
294
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
295
+ #
296
+ # ip6.hexs
297
+ # #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"]
298
+ #
299
+ # Not to be confused with the similar IPv6#to_hex method.
300
+ #
301
+ def hexs
302
+ @address.split(":")
303
+ end
304
+
305
+ #
306
+ # Returns the IPv6 address in a DNS reverse lookup
307
+ # string, as per RFC3172 and RFC2874.
308
+ #
309
+ # ip6 = IPAddress "3ffe:505:2::f"
310
+ #
311
+ # ip6.reverse
312
+ # #=> "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa"
313
+ #
314
+ def reverse
315
+ to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa"
316
+ end
317
+ alias_method :arpa, :reverse
318
+
319
+ #
320
+ # Returns the IPv6 address in a DNS reverse lookup
321
+ # string, as per RFC3172 and RFC2874.
322
+ # for DNS Domain definition entries like SOA Records
323
+ #
324
+ # ipv6 = IPAddress.parse("fd04:fd80:5::/3")
325
+ #
326
+ # ip.reverse
327
+ # #=> ["e.ip6.arpa", "f.ip6.arpa"]
328
+ #
329
+ def rev_domains
330
+ ret = []
331
+ network = IPv6.parse_u128(network_u128, [(@prefix.to_i/4)*4 + 3, 123].min)
332
+ network.each_net do |net|
333
+ ret << net.reverse[(128-(net.prefix.to_i))/4*2..-1]
334
+ end
335
+ ret
336
+ end
337
+
338
+
339
+
340
+ # Returns the network number in Unsigned 128bits format
341
+ #
342
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
343
+ #
344
+ # ip6.network_u128
345
+ # #=> 42540766411282592856903984951653826560
346
+ #
347
+ def network_u128
348
+ to_u128 & @prefix.to_u128
349
+ end
350
+
351
+ #
352
+ # Returns the broadcast address in Unsigned 128bits format
353
+ #
354
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
355
+ #
356
+ # ip6.broadcast_u128
357
+ # #=> 42540766411282592875350729025363378175
358
+ #
359
+ # Please note that there is no Broadcast concept in IPv6
360
+ # addresses as in IPv4 addresses, and this method is just
361
+ # an helper to other functions.
362
+ #
363
+ def broadcast_u128
364
+ network_u128 + size - 1
365
+ end
366
+
367
+ #
368
+ # Returns the number of IP addresses included
369
+ # in the network. It also counts the network
370
+ # address and the broadcast address.
371
+ #
372
+ # ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
373
+ #
374
+ # ip6.size
375
+ # #=> 18446744073709551616
376
+ #
377
+ def size
378
+ 2 ** @prefix.host_prefix
379
+ end
380
+
381
+ #
382
+ # Checks whether a subnet includes the given IP address.
383
+ #
384
+ # Example:
385
+ #
386
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
387
+ # addr = IPAddress "2001:db8::8:800:200c:1/128"
388
+ #
389
+ # ip6.include? addr
390
+ # #=> true
391
+ #
392
+ # ip6.include? IPAddress("2001:db8:1::8:800:200c:417a/76")
393
+ # #=> false
394
+ #
395
+ def include?(oth)
396
+ @prefix <= oth.prefix and network_u128 == self.class.new(oth.address+"/#@prefix").network_u128
397
+ end
398
+
399
+ #
400
+ # Compressed form of the IPv6 address
401
+ #
402
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
403
+ #
404
+ # ip6.compressed
405
+ # #=> "2001:db8::8:800:200c:417a"
406
+ #
407
+ def compressed
408
+ @compressed
409
+ end
410
+
411
+ #
412
+ # Returns true if the address is an unspecified address
413
+ #
414
+ # See IPAddress::IPv6::Unspecified for more information
415
+ #
416
+ def unspecified?
417
+ @prefix == 128 and @compressed == "::"
418
+ end
419
+
420
+ #
421
+ # Returns true if the address is a loopback address
422
+ #
423
+ # See IPAddress::IPv6::Loopback for more information
424
+ #
425
+ def loopback?
426
+ @prefix == 128 and @compressed == "::1"
427
+ end
428
+
429
+ #
430
+ # Returns true if the address is a mapped address
431
+ #
432
+ # See IPAddress::IPv6::Mapped for more information
433
+ #
434
+ def mapped?
435
+ to_u128 >> 32 == 0xffff
436
+ end
437
+
438
+ #
439
+ # Iterates over all the IP addresses for the given
440
+ # network (or IP address).
441
+ #
442
+ # The object yielded is a new IPv6 object created
443
+ # from the iteration.
444
+ #
445
+ # ip6 = IPAddress("2001:db8::4/125")
446
+ #
447
+ # ip6.each do |i|
448
+ # p i.compressed
449
+ # end
450
+ # #=> "2001:db8::"
451
+ # #=> "2001:db8::1"
452
+ # #=> "2001:db8::2"
453
+ # #=> "2001:db8::3"
454
+ # #=> "2001:db8::4"
455
+ # #=> "2001:db8::5"
456
+ # #=> "2001:db8::6"
457
+ # #=> "2001:db8::7"
458
+ #
459
+ # WARNING: if the host portion is very large, this method
460
+ # can be very slow and possibly hang your system!
461
+ #
462
+ def each
463
+ (network_u128..broadcast_u128).each do |i|
464
+ yield self.class.parse_u128(i, @prefix)
465
+ end
466
+ end
467
+
468
+ #
469
+ # Iterates over all the IP Network for the given
470
+ # network (or IP address).
471
+ #
472
+ # The object yielded is a new IPv6 object created
473
+ # from the iteration.
474
+ #
475
+ # ip6 = IPAddress("fd01::4/3")
476
+ #
477
+ # ip6.each_net do |i|
478
+ # p i.compressed
479
+ # end
480
+ # #=> "e000::/3"
481
+ # #=> "f000::/3"
482
+ #
483
+ # WARNING: if the host portion is very large, this method
484
+ # can be very slow and possibly hang your system!
485
+ #
486
+ def each_net
487
+ start = network_u128
488
+ while (@prefix.host_prefix.to_i-1) > 0 && start < broadcast_u128
489
+ yield self.class.parse_u128(start, @prefix)
490
+ start += 1<<(@prefix.host_prefix.to_i-1)
491
+ end
492
+ end
493
+
494
+ #
495
+ # Spaceship operator to compare IPv6 objects
496
+ #
497
+ # Comparing IPv6 addresses is useful to ordinate
498
+ # them into lists that match our intuitive
499
+ # perception of ordered IP addresses.
500
+ #
501
+ # The first comparison criteria is the u128 value.
502
+ # For example, 2001:db8:1::1 will be considered
503
+ # to be less than 2001:db8:2::1, because, in a ordered list,
504
+ # we expect 2001:db8:1::1 to come before 2001:db8:2::1.
505
+ #
506
+ # The second criteria, in case two IPv6 objects
507
+ # have identical addresses, is the prefix. An higher
508
+ # prefix will be considered greater than a lower
509
+ # prefix. This is because we expect to see
510
+ # 2001:db8:1::1/64 come before 2001:db8:1::1/65
511
+ #
512
+ # Example:
513
+ #
514
+ # ip1 = IPAddress "2001:db8:1::1/64"
515
+ # ip2 = IPAddress "2001:db8:2::1/64"
516
+ # ip3 = IPAddress "2001:db8:1::1/65"
517
+ #
518
+ # ip1 < ip2
519
+ # #=> true
520
+ # ip1 < ip3
521
+ # #=> false
522
+ #
523
+ # [ip1,ip2,ip3].sort.map{|i| i.to_string}
524
+ # #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"]
525
+ #
526
+ def <=>(oth)
527
+ return prefix <=> oth.prefix if to_u128 == oth.to_u128
528
+ to_u128 <=> oth.to_u128
529
+ end
530
+
531
+ #
532
+ # Returns the address portion of an IP in binary format,
533
+ # as a string containing a sequence of 0 and 1
534
+ #
535
+ # ip6 = IPAddress("2001:db8::8:800:200c:417a")
536
+ #
537
+ # ip6.bits
538
+ # #=> "0010000000000001000011011011100000 [...] "
539
+ #
540
+ def bits
541
+ data.unpack("B*").first
542
+ end
543
+
544
+ #
545
+ # Expands an IPv6 address in the canocical form
546
+ #
547
+ # IPAddress::IPv6.expand "2001:0DB8:0:CD30::"
548
+ # #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
549
+ #
550
+ def self.expand(str)
551
+ self.new(str).address
552
+ end
553
+
554
+ #
555
+ # Compress an IPv6 address in its compressed form
556
+ #
557
+ # IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000"
558
+ # #=> "2001:db8:0:cd30::"
559
+ #
560
+ def self.compress(str)
561
+ self.new(str).compressed
562
+ end
563
+
564
+ #
565
+ # Literal version of the IPv6 address
566
+ #
567
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
568
+ #
569
+ # ip6.literal
570
+ # #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net"
571
+ #
572
+ def literal
573
+ @address.gsub(":","-") + ".ipv6-literal.net"
574
+ end
575
+
576
+ #
577
+ # Returns a new IPv6 object with the network number
578
+ # for the given IP.
579
+ #
580
+ # ip = IPAddress "2001:db8:1:1:1:1:1:1/32"
581
+ #
582
+ # ip.network.to_string
583
+ # #=> "2001:db8::/32"
584
+ #
585
+ def network
586
+ self.class.parse_u128(network_u128, @prefix)
587
+ end
588
+
589
+ #
590
+ # Extract 16 bits groups from a string
591
+ #
592
+ def self.groups(str)
593
+ l, r = if str =~ /^(.*)::(.*)$/
594
+ [$1,$2].map {|i| i.split ":"}
595
+ else
596
+ [str.split(":"),[]]
597
+ end
598
+ (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
599
+ end
600
+
601
+ #
602
+ # Creates a new IPv6 object from binary data,
603
+ # like the one you get from a network stream.
604
+ #
605
+ # For example, on a network stream the IP
606
+ #
607
+ # "2001:db8::8:800:200c:417a"
608
+ #
609
+ # is represented with the binary data
610
+ #
611
+ # " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
612
+ #
613
+ # With that data you can create a new IPv6 object:
614
+ #
615
+ # ip6 = IPAddress::IPv6::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
616
+ # ip6.prefix = 64
617
+ #
618
+ # ip6.to_s
619
+ # #=> "2001:db8::8:800:200c:417a/64"
620
+ #
621
+ def self.parse_data(str)
622
+ self.new(IN6FORMAT % str.unpack("n8"))
623
+ end
624
+
625
+ #
626
+ # Creates a new IPv6 object from an
627
+ # unsigned 128 bits integer.
628
+ #
629
+ # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122)
630
+ # ip6.prefix = 64
631
+ #
632
+ # ip6.to_string
633
+ # #=> "2001:db8::8:800:200c:417a/64"
634
+ #
635
+ # The +prefix+ parameter is optional:
636
+ #
637
+ # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122, 64)
638
+ #
639
+ # ip6.to_string
640
+ # #=> "2001:db8::8:800:200c:417a/64"
641
+ #
642
+ def self.parse_u128(u128, prefix=128)
643
+ str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff}
644
+ self.new(str + "/#{prefix}")
645
+ end
646
+
647
+ #
648
+ # Creates a new IPv6 object from a number expressed in
649
+ # hexdecimal format:
650
+ #
651
+ # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a")
652
+ # ip6.prefix = 64
653
+ #
654
+ # ip6.to_string
655
+ # #=> "2001:db8::8:800:200c:417a/64"
656
+ #
657
+ # The +prefix+ parameter is optional:
658
+ #
659
+ # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64)
660
+ #
661
+ # ip6.to_string
662
+ # #=> "2001:db8::8:800:200c:417a/64"
663
+ #
664
+ def self.parse_hex(hex, prefix=128)
665
+ self.parse_u128(hex.hex, prefix)
666
+ end
667
+
668
+ private
669
+
670
+ def compress_address
671
+ str = @groups.map{|i| i.to_s 16}.join ":"
672
+ loop do
673
+ break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
674
+ break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
675
+ break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
676
+ break if str.sub!(/\b0:0:0:0:0\b/, ':')
677
+ break if str.sub!(/\b0:0:0:0\b/, ':')
678
+ break if str.sub!(/\b0:0:0\b/, ':')
679
+ break if str.sub!(/\b0:0\b/, ':')
680
+ break if str.sub!(/\b0:\b/, ':')
681
+ break
682
+ end
683
+ str.sub(/:{3,}/, '::')
684
+ end
685
+
686
+ def +(oth)
687
+ summarize([self,oth])
688
+ end
689
+
690
+ #
691
+ # Summarization (or aggregation) is the process when two or more
692
+ # networks are taken together to check if a supernet, including all
693
+ # and only these networks, exists. If it exists then this supernet
694
+ # is called the summarized (or aggregated) network.
695
+ #
696
+ # It is very important to understand that summarization can only
697
+ # occur if there are no holes in the aggregated network, or, in other
698
+ # words, if the given networks fill completely the address space
699
+ # of the supernet. So the two rules are:
700
+ #
701
+ # 1) The aggregate network must contain +all+ the IP addresses of the
702
+ # original networks;
703
+ # 2) The aggregate network must contain +only+ the IP addresses of the
704
+ # original networks;
705
+ #
706
+ # A few examples will help clarify the above. Let's consider for
707
+ # instance the following two networks:
708
+ #
709
+ # ip1 = IPAddress("2000:0::4/32")
710
+ # ip2 = IPAddress("2000:1::6/32")
711
+ #
712
+ # These two networks can be expressed using only one IP address
713
+ # network if we change the prefix. Let Ruby do the work:
714
+ #
715
+ # IPAddress::IPv6::summarize(ip1,ip2).to_s
716
+ # #=> "2000:0::/31"
717
+ #
718
+ # We note how the network "2000:0::/31" includes all the addresses
719
+ # specified in the above networks, and (more important) includes
720
+ # ONLY those addresses.
721
+ #
722
+ # If we summarized +ip1+ and +ip2+ with the following network:
723
+ #
724
+ # "2000::/16"
725
+ #
726
+ # we would have satisfied rule #1 above, but not rule #2. So "2000::/16"
727
+ # is not an aggregate network for +ip1+ and +ip2+.
728
+ #
729
+ # If it's not possible to compute a single aggregated network for all the
730
+ # original networks, the method returns an array with all the aggregate
731
+ # networks found. For example, the following four networks can be
732
+ # aggregated in a single /22:
733
+ #
734
+ # ip1 = IPAddress("2000:0::/32")
735
+ # ip2 = IPAddress("2000:1::/32")
736
+ # ip3 = IPAddress("2000:2::/32")
737
+ # ip4 = IPAddress("2000:3::/32")
738
+ #
739
+ # IPAddress::IPv6::summarize(ip1,ip2,ip3,ip4).to_string
740
+ # #=> ""2000:3::/30",
741
+ #
742
+ # But the following networks can't be summarized in a single network:
743
+ #
744
+ # ip1 = IPAddress("2000:1::/32")
745
+ # ip2 = IPAddress("2000:2::/32")
746
+ # ip3 = IPAddress("2000:3::/32")
747
+ # ip4 = IPAddress("2000:4::/32")
748
+ #
749
+ # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string}
750
+ # #=> ["2000:1::/32","2000:2::/31","2000:4::/32"]
751
+ #
752
+ def self.summarize(*args)
753
+ IPAddress.summarize(*args)
754
+ end
755
+
756
+ end # class IPv6
757
+
758
+ #
759
+ # The address with all zero bits is called the +unspecified+ address
760
+ # (corresponding to 0.0.0.0 in IPv4). It should be something like this:
761
+ #
762
+ # 0000:0000:0000:0000:0000:0000:0000:0000
763
+ #
764
+ # but, with the use of compression, it is usually written as just two
765
+ # colons:
766
+ #
767
+ # ::
768
+ #
769
+ # or, specifying the netmask:
770
+ #
771
+ # ::/128
772
+ #
773
+ # With IPAddress, create a new unspecified IPv6 address using its own
774
+ # subclass:
775
+ #
776
+ # ip = IPAddress::IPv6::Unspecified.new
777
+ #
778
+ # ip.to_s
779
+ # #=> => "::/128"
780
+ #
781
+ # You can easily check if an IPv6 object is an unspecified address by
782
+ # using the IPv6#unspecified? method
783
+ #
784
+ # ip.unspecified?
785
+ # #=> true
786
+ #
787
+ # An unspecified IPv6 address can also be created with the wrapper
788
+ # method, like we've seen before
789
+ #
790
+ # ip = IPAddress "::"
791
+ #
792
+ # ip.unspecified?
793
+ # #=> true
794
+ #
795
+ # This address must never be assigned to an interface and is to be used
796
+ # only in software before the application has learned its host's source
797
+ # address appropriate for a pending connection. Routers must not forward
798
+ # packets with the unspecified address.
799
+ #
800
+ class IPAddress::IPv6::Unspecified < IPAddress::IPv6
801
+ #
802
+ # Creates a new IPv6 unspecified address
803
+ #
804
+ # ip = IPAddress::IPv6::Unspecified.new
805
+ #
806
+ # ip.to_s
807
+ # #=> => "::/128"
808
+ #
809
+ def initialize
810
+ @address = ("0000:"*8).chop
811
+ @groups = Array.new(8,0)
812
+ @prefix = Prefix128.new(128)
813
+ @compressed = compress_address
814
+ end
815
+ end # class IPv6::Unspecified
816
+
817
+ #
818
+ # The loopback address is a unicast localhost address. If an
819
+ # application in a host sends packets to this address, the IPv6 stack
820
+ # will loop these packets back on the same virtual interface.
821
+ #
822
+ # Loopback addresses are expressed in the following form:
823
+ #
824
+ # ::1
825
+ #
826
+ # or, with their appropriate prefix,
827
+ #
828
+ # ::1/128
829
+ #
830
+ # As for the unspecified addresses, IPv6 loopbacks can be created with
831
+ # IPAddress calling their own class:
832
+ #
833
+ # ip = IPAddress::IPv6::Loopback.new
834
+ #
835
+ # ip.to_string
836
+ # #=> "::1/128"
837
+ #
838
+ # or by using the wrapper:
839
+ #
840
+ # ip = IPAddress "::1"
841
+ #
842
+ # ip.to_string
843
+ # #=> "::1/128"
844
+ #
845
+ # Checking if an address is loopback is easy with the IPv6#loopback?
846
+ # method:
847
+ #
848
+ # ip.loopback?
849
+ # #=> true
850
+ #
851
+ # The IPv6 loopback address corresponds to 127.0.0.1 in IPv4.
852
+ #
853
+ class IPAddress::IPv6::Loopback < IPAddress::IPv6
854
+ #
855
+ # Creates a new IPv6 unspecified address
856
+ #
857
+ # ip = IPAddress::IPv6::Loopback.new
858
+ #
859
+ # ip.to_string
860
+ # #=> "::1/128"
861
+ #
862
+ def initialize
863
+ @address = ("0000:"*7)+"0001"
864
+ @groups = Array.new(7,0).push(1)
865
+ @prefix = Prefix128.new(128)
866
+ @compressed = compress_address
867
+ end
868
+ end # class IPv6::Loopback
869
+
870
+ #
871
+ # It is usually identified as a IPv4 mapped IPv6 address, a particular
872
+ # IPv6 address which aids the transition from IPv4 to IPv6. The
873
+ # structure of the address is
874
+ #
875
+ # ::ffff:w.y.x.z
876
+ #
877
+ # where w.x.y.z is a normal IPv4 address. For example, the following is
878
+ # a mapped IPv6 address:
879
+ #
880
+ # ::ffff:192.168.100.1
881
+ #
882
+ # IPAddress is very powerful in handling mapped IPv6 addresses, as the
883
+ # IPv4 portion is stored internally as a normal IPv4 object. Let's have
884
+ # a look at some examples. To create a new mapped address, just use the
885
+ # class builder itself
886
+ #
887
+ # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
888
+ #
889
+ # or just use the wrapper method
890
+ #
891
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
892
+ #
893
+ # Let's check it's really a mapped address:
894
+ #
895
+ # ip6.mapped?
896
+ # #=> true
897
+ #
898
+ # ip6.to_string
899
+ # #=> "::FFFF:172.16.10.1/128"
900
+ #
901
+ # Now with the +ipv4+ attribute, we can easily access the IPv4 portion
902
+ # of the mapped IPv6 address:
903
+ #
904
+ # ip6.ipv4.address
905
+ # #=> "172.16.10.1"
906
+ #
907
+ # Internally, the IPv4 address is stored as two 16 bits
908
+ # groups. Therefore all the usual methods for an IPv6 address are
909
+ # working perfectly fine:
910
+ #
911
+ # ip6.to_hex
912
+ # #=> "00000000000000000000ffffac100a01"
913
+ #
914
+ # ip6.address
915
+ # #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01"
916
+ #
917
+ # A mapped IPv6 can also be created just by specify the address in the
918
+ # following format:
919
+ #
920
+ # ip6 = IPAddress "::172.16.10.1"
921
+ #
922
+ # That is, two colons and the IPv4 address. However, as by RFC, the ffff
923
+ # group will be automatically added at the beginning
924
+ #
925
+ # ip6.to_string
926
+ # => "::ffff:172.16.10.1/128"
927
+ #
928
+ # making it a mapped IPv6 compatible address.
929
+ #
930
+ class IPAddress::IPv6::Mapped < IPAddress::IPv6
931
+
932
+ # Access the internal IPv4 address
933
+ attr_reader :ipv4
934
+
935
+ #
936
+ # Creates a new IPv6 IPv4-mapped address
937
+ #
938
+ # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
939
+ #
940
+ # ipv6.ipv4.class
941
+ # #=> IPAddress::IPv4
942
+ #
943
+ # An IPv6 IPv4-mapped address can also be created using the
944
+ # IPv6 only format of the address:
945
+ #
946
+ # ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403"
947
+ #
948
+ # ip6.to_string
949
+ # #=> "::ffff:13.1.68.3"
950
+ #
951
+ def initialize(str)
952
+ string, netmask = str.split("/")
953
+ if string =~ /\./ # IPv4 in dotted decimal form
954
+ @ipv4 = IPAddress::IPv4.extract(string)
955
+ else # IPv4 in hex form
956
+ groups = IPAddress::IPv6.groups(string)
957
+ @ipv4 = IPAddress::IPv4.parse_u32((groups[-2]<< 16)+groups[-1])
958
+ end
959
+ super("::ffff:#{@ipv4.to_ipv6}/#{netmask}")
960
+ end
961
+
962
+ #
963
+ # Similar to IPv6#to_s, but prints out the IPv4 address
964
+ # in dotted decimal format
965
+ #
966
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
967
+ #
968
+ # ip6.to_s
969
+ # #=> "::ffff:172.16.10.1"
970
+ #
971
+ def to_s
972
+ "::ffff:#{@ipv4.address}"
973
+ end
974
+
975
+ #
976
+ # Similar to IPv6#to_string, but prints out the IPv4 address
977
+ # in dotted decimal format
978
+ #
979
+ #
980
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
981
+ #
982
+ # ip6.to_string
983
+ # #=> "::ffff:172.16.10.1/128"
984
+ #
985
+ def to_string
986
+ "::ffff:#{@ipv4.address}/#@prefix"
987
+ end
988
+
989
+ #
990
+ # Checks if the IPv6 address is IPv4 mapped
991
+ #
992
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
993
+ #
994
+ # ip6.mapped?
995
+ # #=> true
996
+ #
997
+ def mapped?
998
+ true
999
+ end
1000
+ end # class IPv6::Mapped
1001
+
1002
+ end # module IPAddress
1003
+