relevance_ipaddress 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,683 @@
1
+ require 'ipaddress/ipbase'
2
+ require 'ipaddress/prefix'
3
+
4
+ module IPAddress;
5
+ #
6
+ # =Name
7
+ #
8
+ # IPAddress::IPv6 - IP version 6 address manipulation library
9
+ #
10
+ # =Synopsis
11
+ #
12
+ # require 'ipaddress'
13
+ #
14
+ # =Description
15
+ #
16
+ # Class IPAddress::IPv6 is used to handle IPv6 type addresses.
17
+ #
18
+ # == IPv6 addresses
19
+ #
20
+ # IPv6 addresses are 128 bits long, in contrast with IPv4 addresses
21
+ # which are only 32 bits long. An IPv6 address is generally written as
22
+ # eight groups of four hexadecimal digits, each group representing 16
23
+ # bits or two octect. For example, the following is a valid IPv6
24
+ # address:
25
+ #
26
+ # 1080:0000:0000:0000:0008:0800:200c:417a
27
+ #
28
+ # Letters in an IPv6 address are usually written downcase, as per
29
+ # RFC. You can create a new IPv6 object using uppercase letters, but
30
+ # they will be converted.
31
+ #
32
+ # === Compression
33
+ #
34
+ # Since IPv6 addresses are very long to write, there are some
35
+ # semplifications and compressions that you can use to shorten them.
36
+ #
37
+ # * Leading zeroes: all the leading zeroes within a group can be
38
+ # omitted: "0008" would become "8"
39
+ #
40
+ # * A string of consecutive zeroes can be replaced by the string
41
+ # "::". This can be only applied once.
42
+ #
43
+ # Using compression, the IPv6 address written above can be shorten into
44
+ # the following, equivalent, address
45
+ #
46
+ # 1080::8:800:200c:417a
47
+ #
48
+ # This short version is often used in human representation.
49
+ #
50
+ # === Network Mask
51
+ #
52
+ # As we used to do with IPv4 addresses, an IPv6 address can be written
53
+ # using the prefix notation to specify the subnet mask:
54
+ #
55
+ # 1080::8:800:200c:417a/64
56
+ #
57
+ # The /64 part means that the first 64 bits of the address are
58
+ # representing the network portion, and the last 64 bits are the host
59
+ # portion.
60
+ #
61
+ #
62
+ class IPv6 < IPBase
63
+
64
+ include IPAddress
65
+ include Enumerable
66
+ include Comparable
67
+
68
+
69
+ #
70
+ # Format string to pretty print IPv6 addresses
71
+ #
72
+ IN6FORMAT = ("%.4x:"*8).chop
73
+
74
+ #
75
+ # Creates a new IPv6 address object.
76
+ #
77
+ # An IPv6 address can be expressed in any of the following forms:
78
+ #
79
+ # * "1080:0000:0000:0000:0008:0800:200C:417A": IPv6 address with no compression
80
+ # * "1080:0:0:0:8:800:200C:417A": IPv6 address with leading zeros compression
81
+ # * "1080::8:800:200C:417A": IPv6 address with full compression
82
+ #
83
+ # In all these 3 cases, a new IPv6 address object will be created, using the default
84
+ # subnet mask /128
85
+ #
86
+ # You can also specify the subnet mask as with IPv4 addresses:
87
+ #
88
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
89
+ #
90
+ def initialize(str)
91
+ ip, netmask = str.split("/")
92
+
93
+ if IPAddress.valid_ipv6?(ip)
94
+ @groups = self.class.groups(ip)
95
+ @address = IN6FORMAT % @groups
96
+ @compressed = compress_address
97
+ else
98
+ raise ArgumentError, "Invalid IP #{ip.inspect}"
99
+ end
100
+
101
+ @prefix = Prefix128.new(netmask ? netmask : 128)
102
+
103
+ end # def initialize
104
+
105
+
106
+ #
107
+ # Returns the IPv6 address in uncompressed form:
108
+ #
109
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
110
+ #
111
+ # ip6.address
112
+ # #=> "2001:0db8:0000:0000:0008:0800:200c:417a"
113
+ #
114
+ def address
115
+ @address
116
+ end
117
+
118
+ #
119
+ # Returns an array with the 16 bits groups in decimal
120
+ # format:
121
+ #
122
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
123
+ #
124
+ # ip6.groups
125
+ # #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762]
126
+ #
127
+ def groups
128
+ @groups
129
+ end
130
+
131
+ #
132
+ # Returns an instance of the prefix object
133
+ #
134
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
135
+ #
136
+ # ip6.prefix
137
+ # #=> 64
138
+ #
139
+ def prefix
140
+ @prefix
141
+ end
142
+
143
+ #
144
+ # Set a new prefix number for the object
145
+ #
146
+ # This is useful if you want to change the prefix
147
+ # to an object created with IPv6::parse_u128 or
148
+ # if the object was created using the default prefix
149
+ # of 128 bits.
150
+ #
151
+ # ip = IPAddress("2001:db8::8:800:200c:417a")
152
+ # puts ip
153
+ # #=> 2001:db8::8:800:200c:417a/128
154
+ #
155
+ # ip.prefix = 64
156
+ # puts ip
157
+ # #=> 2001:db8::8:800:200c:417a/64
158
+ #
159
+ def prefix=(num)
160
+ @prefix = Prefix128.new(num)
161
+ end
162
+
163
+ #
164
+ # Unlike its counterpart IPv6#to_s method, IPv6#to_string
165
+ # returns the whole IPv6 address and prefix in an uncompressed form
166
+ #
167
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
168
+ #
169
+ # ip6.to_string
170
+ # #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64"
171
+ #
172
+ def to_string
173
+ "#@address/#@prefix"
174
+ end
175
+
176
+ #
177
+ # Returns the IPv6 address in a human readable form,
178
+ # using the compressed address.
179
+ #
180
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
181
+ #
182
+ # ip6.to_string
183
+ # #=> "2001:db8::8:800:200c:417a/64"
184
+ #
185
+ def to_s
186
+ "#{compressed}/#@prefix"
187
+ end
188
+
189
+ #
190
+ # Returns a decimal format (unsigned 128 bit) of the
191
+ # IPv6 address
192
+ #
193
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
194
+ #
195
+ # ip6.to_i
196
+ # #=> 42540766411282592856906245548098208122
197
+ #
198
+ def to_i
199
+ to_hex.hex
200
+ end
201
+ alias_method :to_u128, :to_i
202
+
203
+ #
204
+ # True if the IPv6 address is a network
205
+ #
206
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
207
+ # ip6.network?
208
+ # #=> false
209
+ #
210
+ # ip6 = IPAddress "2001:db8:8:800::/64"
211
+ # ip6.network?
212
+ # #=> true
213
+ #
214
+ def network?
215
+ to_u128 | @prefix.to_u128 == @prefix.to_u128
216
+ end
217
+
218
+ #
219
+ # Returns the 16-bits value specified by index
220
+ #
221
+ # ip = IPAddress("2001:db8::8:800:200c:417a/64")
222
+ # ip[0]
223
+ # #=> 8193
224
+ # ip[1]
225
+ # #=> 3512
226
+ # ip[2]
227
+ # #=> 0
228
+ # ip[3]
229
+ # #=> 0
230
+ #
231
+ def [](index)
232
+ @groups[index]
233
+ end
234
+ alias_method :group, :[]
235
+
236
+ #
237
+ # Returns a Base16 number representing the IPv6
238
+ # address
239
+ #
240
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
241
+ #
242
+ # ip6.to_hex
243
+ # #=> "20010db80000000000080800200c417a"
244
+ #
245
+ def to_hex
246
+ hexs.join("")
247
+ end
248
+
249
+ # Returns the address portion of an IPv6 object
250
+ # in a network byte order format.
251
+ #
252
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
253
+ # ip6.data
254
+ # #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
255
+ #
256
+ # It is usually used to include an IP address
257
+ # in a data packet to be sent over a socket
258
+ #
259
+ # a = Socket.open(params) # socket details here
260
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
261
+ # binary_data = ["Address: "].pack("a*") + ip.data
262
+ #
263
+ # # Send binary data
264
+ # a.puts binary_data
265
+ #
266
+ def data
267
+ @groups.pack("n8")
268
+ end
269
+
270
+ #
271
+ # Returns an array of the 16 bits groups in hexdecimal
272
+ # format:
273
+ #
274
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
275
+ #
276
+ # ip6.hexs
277
+ # #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"]
278
+ #
279
+ # Not to be confused with the similar IPv6#to_hex method.
280
+ #
281
+ def hexs
282
+ @address.split(":")
283
+ end
284
+
285
+ #
286
+ # Compressed form of the IPv6 address
287
+ #
288
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
289
+ #
290
+ # ip6.compressed
291
+ # #=> "2001:db8::8:800:200c:417a"
292
+ #
293
+ def compressed
294
+ @compressed
295
+ end
296
+
297
+ #
298
+ # Returns true if the address is an unspecified address
299
+ #
300
+ # See IPAddress::IPv6::Unspecified for more information
301
+ #
302
+ def unspecified?
303
+ @prefix == 128 and @compressed == "::"
304
+ end
305
+
306
+ #
307
+ # Returns true if the address is a loopback address
308
+ #
309
+ # See IPAddress::IPv6::Loopback for more information
310
+ #
311
+ def loopback?
312
+ @prefix == 128 and @compressed == "::1"
313
+ end
314
+
315
+ #
316
+ # Returns true if the address is a mapped address
317
+ #
318
+ # See IPAddress::IPv6::Mapped for more information
319
+ #
320
+ def mapped?
321
+ false
322
+ end
323
+
324
+ #
325
+ # Returns the address portion of an IP in binary format,
326
+ # as a string containing a sequence of 0 and 1
327
+ #
328
+ # ip = IPAddress("2001:db8::8:800:200c:417a")
329
+ #
330
+ # ip.bits
331
+ # #=> "01111111000000000000000000000001"
332
+ #
333
+ def bits
334
+ data.unpack("B*").first
335
+ end
336
+
337
+ #
338
+ # Expands an IPv6 address in the canocical form
339
+ #
340
+ # IPAddress::IPv6.expand "2001:0DB8:0:CD30::"
341
+ # #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
342
+ #
343
+ def self.expand(str)
344
+ self.new(str).address
345
+ end
346
+
347
+ #
348
+ # Compress an IPv6 address in its compressed form
349
+ #
350
+ # IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000"
351
+ # #=> "2001:db8:0:cd30::"
352
+ #
353
+ def self.compress(str)
354
+ self.new(str).compressed
355
+ end
356
+
357
+ #
358
+ # Literal version of the IPv6 address
359
+ #
360
+ # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
361
+ #
362
+ # ip6.literal
363
+ # #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net"
364
+ #
365
+ def literal
366
+ @address.gsub(":","-") + ".ipv6-literal.net"
367
+ end
368
+
369
+ #
370
+ # Extract 16 bits groups from a string
371
+ #
372
+ def self.groups(str)
373
+ l, r = if str =~ /^(.*)::(.*)$/
374
+ [$1,$2].map {|i| i.split ":"}
375
+ else
376
+ [str.split(":"),[]]
377
+ end
378
+ (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
379
+ end
380
+
381
+ #
382
+ # Creates a new IPv6 object from binary data,
383
+ # like the one you get from a network stream.
384
+ #
385
+ # For example, on a network stream the IP
386
+ #
387
+ # "2001:db8::8:800:200c:417a"
388
+ #
389
+ # is represented with the binary data
390
+ #
391
+ # " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
392
+ #
393
+ # With that data you can create a new IPv6 object:
394
+ #
395
+ # ip6 = IPAddress::IPv4::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
396
+ # ip6.prefix = 64
397
+ #
398
+ # ip6.to_s
399
+ # #=> "2001:db8::8:800:200c:417a/64"
400
+ #
401
+ def self.parse_data(str)
402
+ self.new(IN6FORMAT % str.unpack("n8"))
403
+ end
404
+
405
+ #
406
+ # Creates a new IPv6 object from an
407
+ # unsigned 128 bits integer.
408
+ #
409
+ # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066)
410
+ # ip6.prefix = 64
411
+ #
412
+ # ip6.to_s
413
+ # #=> "1080::8:800:200c:417a/64"
414
+ #
415
+ # The +prefix+ parameter is optional:
416
+ #
417
+ # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066, 64)
418
+ #
419
+ # ip6.to_s
420
+ # #=> "1080::8:800:200c:417a/64"
421
+ #
422
+ def self.parse_u128(u128, prefix=128)
423
+ str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff}
424
+ self.new(str + "/#{prefix}")
425
+ end
426
+
427
+ #
428
+ # Creates a new IPv6 object from a number expressed in
429
+ # hexdecimal format:
430
+ #
431
+ # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a")
432
+ # ip6.prefix = 64
433
+ #
434
+ # ip6.to_s
435
+ # #=> "2001:db8::8:800:200c:417a/64"
436
+ #
437
+ # The +prefix+ parameter is optional:
438
+ #
439
+ # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64)
440
+ #
441
+ # ip6.to_s
442
+ # #=> "1080::8:800:200c:417a/64"
443
+ #
444
+ def self.parse_hex(hex, prefix=128)
445
+ self.parse_u128(hex.hex, prefix)
446
+ end
447
+
448
+ private
449
+
450
+ def compress_address
451
+ str = @groups.map{|i| i.to_s 16}.join ":"
452
+ loop do
453
+ break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
454
+ break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
455
+ break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
456
+ break if str.sub!(/\b0:0:0:0:0\b/, ':')
457
+ break if str.sub!(/\b0:0:0:0\b/, ':')
458
+ break if str.sub!(/\b0:0:0\b/, ':')
459
+ break if str.sub!(/\b0:0\b/, ':')
460
+ break
461
+ end
462
+ str.sub(/:{3,}/, '::')
463
+ end
464
+
465
+ end # class IPv6
466
+
467
+ #
468
+ # The address with all zero bits is called the +unspecified+ address
469
+ # (corresponding to 0.0.0.0 in IPv4). It should be something like this:
470
+ #
471
+ # 0000:0000:0000:0000:0000:0000:0000:0000
472
+ #
473
+ # but, with the use of compression, it is usually written as just two
474
+ # colons:
475
+ #
476
+ # ::
477
+ #
478
+ # or, specifying the netmask:
479
+ #
480
+ # ::/128
481
+ #
482
+ # With IPAddress, create a new unspecified IPv6 address using its own
483
+ # subclass:
484
+ #
485
+ # ip = IPAddress::IPv6::Unspecified.new
486
+ #
487
+ # ip.to_s
488
+ # #=> => "::/128"
489
+ #
490
+ # You can easily check if an IPv6 object is an unspecified address by
491
+ # using the IPv6#unspecified? method
492
+ #
493
+ # ip.unspecified?
494
+ # #=> true
495
+ #
496
+ # An unspecified IPv6 address can also be created with the wrapper
497
+ # method, like we've seen before
498
+ #
499
+ # ip = IPAddress "::"
500
+ #
501
+ # ip.unspecified?
502
+ # #=> true
503
+ #
504
+ # This address must never be assigned to an interface and is to be used
505
+ # only in software before the application has learned its host's source
506
+ # address appropriate for a pending connection. Routers must not forward
507
+ # packets with the unspecified address.
508
+ #
509
+ class IPAddress::IPv6::Unspecified < IPAddress::IPv6
510
+ #
511
+ # Creates a new IPv6 unspecified address
512
+ #
513
+ # ip = IPAddress::IPv6::Unspecified.new
514
+ #
515
+ # ip.to_s
516
+ # #=> => "::/128"
517
+ #
518
+ def initialize
519
+ @address = ("0000:"*8).chop
520
+ @groups = Array.new(8,0)
521
+ @prefix = Prefix128.new(128)
522
+ @compressed = compress_address
523
+ end
524
+ end # class IPv6::Unspecified
525
+
526
+ #
527
+ # The loopback address is a unicast localhost address. If an
528
+ # application in a host sends packets to this address, the IPv6 stack
529
+ # will loop these packets back on the same virtual interface.
530
+ #
531
+ # Loopback addresses are expressed in the following form:
532
+ #
533
+ # ::1
534
+ #
535
+ # or, with their appropriate prefix,
536
+ #
537
+ # ::1/128
538
+ #
539
+ # As for the unspecified addresses, IPv6 loopbacks can be created with
540
+ # IPAddress calling their own class:
541
+ #
542
+ # ip = IPAddress::IPv6::Loopback.new
543
+ #
544
+ # ip.to_s
545
+ # #=> "::1/128"
546
+ #
547
+ # or by using the wrapper:
548
+ #
549
+ # ip = IPAddress "::1"
550
+ #
551
+ # ip.to_s
552
+ # #=> "::1/128"
553
+ #
554
+ # Checking if an address is loopback is easy with the IPv6#loopback?
555
+ # method:
556
+ #
557
+ # ip.loopback?
558
+ # #=> true
559
+ #
560
+ # The IPv6 loopback address corresponds to 127.0.0.1 in IPv4.
561
+ #
562
+ class IPAddress::IPv6::Loopback < IPAddress::IPv6
563
+ #
564
+ # Creates a new IPv6 unspecified address
565
+ #
566
+ # ip = IPAddress::IPv6::Loopback.new
567
+ #
568
+ # ip.to_s
569
+ # #=> "::1/128"
570
+ #
571
+ def initialize
572
+ @address = ("0000:"*7)+"0001"
573
+ @groups = Array.new(7,0).push(1)
574
+ @prefix = Prefix128.new(128)
575
+ @compressed = compress_address
576
+ end
577
+ end # class IPv6::Loopback
578
+
579
+ #
580
+ # It is usually identified as a IPv4 mapped IPv6 address, a particular
581
+ # IPv6 address which aids the transition from IPv4 to IPv6. The
582
+ # structure of the address is
583
+ #
584
+ # ::ffff:w.y.x.z
585
+ #
586
+ # where w.x.y.z is a normal IPv4 address. For example, the following is
587
+ # a mapped IPv6 address:
588
+ #
589
+ # ::ffff:192.168.100.1
590
+ #
591
+ # IPAddress is very powerful in handling mapped IPv6 addresses, as the
592
+ # IPv4 portion is stored internally as a normal IPv4 object. Let's have
593
+ # a look at some examples. To create a new mapped address, just use the
594
+ # class builder itself
595
+ #
596
+ # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
597
+ #
598
+ # or just use the wrapper method
599
+ #
600
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
601
+ #
602
+ # Let's check it's really a mapped address:
603
+ #
604
+ # ip6.mapped?
605
+ # #=> true
606
+ #
607
+ # ip6.to_s
608
+ # #=> "::FFFF:172.16.10.1/128"
609
+ #
610
+ # Now with the +ipv4+ attribute, we can easily access the IPv4 portion
611
+ # of the mapped IPv6 address:
612
+ #
613
+ # ip6.ipv4.address
614
+ # #=> "172.16.10.1"
615
+ #
616
+ # Internally, the IPv4 address is stored as two 16 bits
617
+ # groups. Therefore all the usual methods for an IPv6 address are
618
+ # working perfectly fine:
619
+ #
620
+ # ip6.to_hex
621
+ # #=> "00000000000000000000ffffac100a01"
622
+ #
623
+ # ip6.address
624
+ # #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01"
625
+ #
626
+ # A mapped IPv6 can also be created just by specify the address in the
627
+ # following format:
628
+ #
629
+ # ip6 = IPAddress "::172.16.10.1"
630
+ #
631
+ # That is, two colons and the IPv4 address. However, as by RFC, the ffff
632
+ # group will be automatically added at the beginning
633
+ #
634
+ # ip6.to_s
635
+ # => "::ffff:172.16.10.1/128"
636
+ #
637
+ # making it a mapped IPv6 compatible address.
638
+ #
639
+ class IPAddress::IPv6::Mapped < IPAddress::IPv6
640
+
641
+ # Access the internal IPv4 address
642
+ attr_reader :ipv4
643
+
644
+ #
645
+ # Creates a new IPv6 unspecified address
646
+ #
647
+ # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
648
+ #
649
+ def initialize(str)
650
+ string, netmask = str.split("/")
651
+ @ipv4 = IPAddress::IPv4.extract(string)
652
+ super("::ffff:#{@ipv4.to_ipv6}/#{netmask}")
653
+ end
654
+
655
+ #
656
+ # Similar to IPv6#to_s, but prints out the IPv4 address
657
+ # in dotted decimal format
658
+ #
659
+ #
660
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
661
+ #
662
+ # ip6.to_s
663
+ # #=> "::FFFF:172.16.10.1/128"
664
+ #
665
+ def to_s
666
+ "::ffff:#{@ipv4.address}/#@prefix"
667
+ end
668
+
669
+ #
670
+ # Checks if the IPv6 address is IPv4 mapped
671
+ #
672
+ # ip6 = IPAddress "::ffff:172.16.10.1/128"
673
+ #
674
+ # ip6.mapped?
675
+ # #=> true
676
+ #
677
+ def mapped?
678
+ true
679
+ end
680
+ end # class IPv6::Mapped
681
+
682
+ end # module IPAddress
683
+