blackwinter-ipaddress 0.8.0

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