ipaddress 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+