ipaddress_2 0.11.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.
- checksums.yaml +7 -0
- data/.byebug_history +25 -0
- data/.document +5 -0
- data/.gitignore +20 -0
- data/.rock.yml +5 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +31 -0
- data/CHANGELOG.rdoc +115 -0
- data/CONTRIBUTING.md +39 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +964 -0
- data/Rakefile +69 -0
- data/VERSION +1 -0
- data/ipaddress_2.gemspec +30 -0
- data/lib/ipaddress_2/ipv4.rb +1204 -0
- data/lib/ipaddress_2/ipv6.rb +1274 -0
- data/lib/ipaddress_2/mongoid.rb +75 -0
- data/lib/ipaddress_2/prefix.rb +285 -0
- data/lib/ipaddress_2/version.rb +3 -0
- data/lib/ipaddress_2.rb +293 -0
- data/test/ipaddress_2/ipv4_test.rb +701 -0
- data/test/ipaddress_2/ipv6_test.rb +707 -0
- data/test/ipaddress_2/mongoid_test.rb +70 -0
- data/test/ipaddress_2/prefix_test.rb +171 -0
- data/test/ipaddress_2_test.rb +109 -0
- data/test/test_helper.rb +37 -0
- metadata +195 -0
@@ -0,0 +1,1274 @@
|
|
1
|
+
require 'ipaddress_2/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
|
+
raise ArgumentError, "Nil IP" unless str
|
91
|
+
ip, netmask = str.split("/")
|
92
|
+
|
93
|
+
if str =~ /:.+\./
|
94
|
+
raise ArgumentError, "Please use #{self.class}::Mapped for IPv4 mapped addresses"
|
95
|
+
end
|
96
|
+
|
97
|
+
if IPAddress.valid_ipv6?(ip)
|
98
|
+
@groups = self.class.groups(ip)
|
99
|
+
@address = IN6FORMAT % @groups
|
100
|
+
@compressed = compress_address
|
101
|
+
else
|
102
|
+
raise ArgumentError, "Invalid IP #{ip.inspect}"
|
103
|
+
end
|
104
|
+
|
105
|
+
@prefix = Prefix128.new(netmask ? netmask : 128)
|
106
|
+
@allocator = 0
|
107
|
+
|
108
|
+
end # def initialize
|
109
|
+
|
110
|
+
#
|
111
|
+
# Returns the IPv6 address in uncompressed form:
|
112
|
+
#
|
113
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
114
|
+
#
|
115
|
+
# ip6.address
|
116
|
+
# #=> "2001:0db8:0000:0000:0008:0800:200c:417a"
|
117
|
+
#
|
118
|
+
def address
|
119
|
+
@address
|
120
|
+
end
|
121
|
+
|
122
|
+
# When serializing to JSON format, just use the string representation
|
123
|
+
#
|
124
|
+
# ip = IPAddress "2001:db8::8:800:200c:417a/64"
|
125
|
+
#
|
126
|
+
# ip.as_json
|
127
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
128
|
+
#
|
129
|
+
def as_json
|
130
|
+
to_string
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Returns an array with the 16 bits groups in decimal
|
135
|
+
# format:
|
136
|
+
#
|
137
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
138
|
+
#
|
139
|
+
# ip6.groups
|
140
|
+
# #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762]
|
141
|
+
#
|
142
|
+
def groups
|
143
|
+
@groups
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Returns an instance of the prefix object
|
148
|
+
#
|
149
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
150
|
+
#
|
151
|
+
# ip6.prefix
|
152
|
+
# #=> 64
|
153
|
+
#
|
154
|
+
def prefix
|
155
|
+
@prefix
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Set a new prefix number for the object
|
160
|
+
#
|
161
|
+
# This is useful if you want to change the prefix
|
162
|
+
# to an object created with IPv6::parse_u128 or
|
163
|
+
# if the object was created using the default prefix
|
164
|
+
# of 128 bits.
|
165
|
+
#
|
166
|
+
# ip6 = IPAddress("2001:db8::8:800:200c:417a")
|
167
|
+
#
|
168
|
+
# puts ip6.to_string
|
169
|
+
# #=> "2001:db8::8:800:200c:417a/128"
|
170
|
+
#
|
171
|
+
# ip6.prefix = 64
|
172
|
+
# puts ip6.to_string
|
173
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
174
|
+
#
|
175
|
+
def prefix=(num)
|
176
|
+
@prefix = Prefix128.new(num)
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Unlike its counterpart IPv6#to_string method, IPv6#to_string_uncompressed
|
181
|
+
# returns the whole IPv6 address and prefix in an uncompressed form
|
182
|
+
#
|
183
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
184
|
+
#
|
185
|
+
# ip6.to_string_uncompressed
|
186
|
+
# #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64"
|
187
|
+
#
|
188
|
+
def to_string_uncompressed
|
189
|
+
"#@address/#@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:0db8:0000:0000:0008:0800:200c:417a/64"
|
197
|
+
#
|
198
|
+
# ip6.to_string
|
199
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
200
|
+
#
|
201
|
+
def to_string
|
202
|
+
"#@compressed/#@prefix"
|
203
|
+
end
|
204
|
+
|
205
|
+
#
|
206
|
+
# Returns the IPv6 address in a human readable form,
|
207
|
+
# using the compressed address.
|
208
|
+
#
|
209
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
210
|
+
#
|
211
|
+
# ip6.to_s
|
212
|
+
# #=> "2001:db8::8:800:200c:417a"
|
213
|
+
#
|
214
|
+
def to_s
|
215
|
+
@compressed
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Returns a decimal format (unsigned 128 bit) of the
|
220
|
+
# IPv6 address
|
221
|
+
#
|
222
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
223
|
+
#
|
224
|
+
# ip6.to_i
|
225
|
+
# #=> 42540766411282592856906245548098208122
|
226
|
+
#
|
227
|
+
def to_i
|
228
|
+
to_hex.hex
|
229
|
+
end
|
230
|
+
alias_method :to_u128, :to_i
|
231
|
+
|
232
|
+
#
|
233
|
+
# True if the IPv6 address is a network
|
234
|
+
#
|
235
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
236
|
+
#
|
237
|
+
# ip6.network?
|
238
|
+
# #=> false
|
239
|
+
#
|
240
|
+
# ip6 = IPAddress "2001:db8:8:800::/64"
|
241
|
+
#
|
242
|
+
# ip6.network?
|
243
|
+
# #=> true
|
244
|
+
#
|
245
|
+
def network?
|
246
|
+
to_u128 | @prefix.to_u128 == @prefix.to_u128
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Returns the 16-bits value specified by index
|
251
|
+
#
|
252
|
+
# ip = IPAddress("2001:db8::8:800:200c:417a/64")
|
253
|
+
#
|
254
|
+
# ip[0]
|
255
|
+
# #=> 8193
|
256
|
+
# ip[1]
|
257
|
+
# #=> 3512
|
258
|
+
# ip[2]
|
259
|
+
# #=> 0
|
260
|
+
# ip[3]
|
261
|
+
# #=> 0
|
262
|
+
#
|
263
|
+
def [](index)
|
264
|
+
@groups[index]
|
265
|
+
end
|
266
|
+
alias_method :group, :[]
|
267
|
+
|
268
|
+
#
|
269
|
+
# Updated the octet specified at index
|
270
|
+
#
|
271
|
+
def []=(index, value)
|
272
|
+
@groups[index] = value
|
273
|
+
initialize("#{IN6FORMAT % @groups}/#{prefix}")
|
274
|
+
end
|
275
|
+
alias_method :group=, :[]=
|
276
|
+
|
277
|
+
#
|
278
|
+
# Returns a Base16 number representing the IPv6
|
279
|
+
# address
|
280
|
+
#
|
281
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
282
|
+
#
|
283
|
+
# ip6.to_hex
|
284
|
+
# #=> "20010db80000000000080800200c417a"
|
285
|
+
#
|
286
|
+
def to_hex
|
287
|
+
hexs.join("")
|
288
|
+
end
|
289
|
+
|
290
|
+
# Returns the address portion of an IPv6 object
|
291
|
+
# in a network byte order format.
|
292
|
+
#
|
293
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
294
|
+
#
|
295
|
+
# ip6.data
|
296
|
+
# #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
|
297
|
+
#
|
298
|
+
# It is usually used to include an IP address
|
299
|
+
# in a data packet to be sent over a socket
|
300
|
+
#
|
301
|
+
# a = Socket.open(params) # socket details here
|
302
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
303
|
+
# binary_data = ["Address: "].pack("a*") + ip.data
|
304
|
+
#
|
305
|
+
# # Send binary data
|
306
|
+
# a.puts binary_data
|
307
|
+
#
|
308
|
+
def data
|
309
|
+
@groups.pack("n8")
|
310
|
+
end
|
311
|
+
|
312
|
+
#
|
313
|
+
# Returns an array of the 16 bits groups in hexdecimal
|
314
|
+
# format:
|
315
|
+
#
|
316
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
317
|
+
#
|
318
|
+
# ip6.hexs
|
319
|
+
# #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"]
|
320
|
+
#
|
321
|
+
# Not to be confused with the similar IPv6#to_hex method.
|
322
|
+
#
|
323
|
+
def hexs
|
324
|
+
@address.split(":")
|
325
|
+
end
|
326
|
+
|
327
|
+
#
|
328
|
+
# Returns the IPv6 address in a DNS reverse lookup
|
329
|
+
# string, as per RFC3172 and RFC2874.
|
330
|
+
#
|
331
|
+
# ip6 = IPAddress "3ffe:505:2::f"
|
332
|
+
#
|
333
|
+
# ip6.reverse
|
334
|
+
# #=> "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"
|
335
|
+
#
|
336
|
+
def reverse
|
337
|
+
to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa"
|
338
|
+
end
|
339
|
+
alias_method :arpa, :reverse
|
340
|
+
|
341
|
+
#
|
342
|
+
# Splits a network into different subnets
|
343
|
+
#
|
344
|
+
# NOTE: Will allow you to split past /64 against RFC 5375
|
345
|
+
#
|
346
|
+
# If the IP Address is a network, it can be divided into
|
347
|
+
# multiple networks. If +self+ is not a network, this
|
348
|
+
# method will calculate the network from the IP and then
|
349
|
+
# subnet it.
|
350
|
+
#
|
351
|
+
# If +subnets+ is an power of two number, the resulting
|
352
|
+
# networks will be divided evenly from the supernet.
|
353
|
+
#
|
354
|
+
# network = IPAddress("2001:db8:8::/48")
|
355
|
+
#
|
356
|
+
# network / 4 # implies map{|i| i.to_string}
|
357
|
+
# #=> ["2001:db8:8::/50",
|
358
|
+
# #=> "2001:db8:8:4000::/50",
|
359
|
+
# #=> "2001:db8:8:8000::/50",
|
360
|
+
# #=> "2001:db8:8:c000::/50"]
|
361
|
+
#
|
362
|
+
# If +num+ is any other number, the supernet will be
|
363
|
+
# divided into some networks with a even number of hosts and
|
364
|
+
# other networks with the remaining addresses.
|
365
|
+
#
|
366
|
+
# network = IPAddress("2001:db8:8::/48")
|
367
|
+
#
|
368
|
+
# network / 3 # implies map{|i| i.to_string}
|
369
|
+
#
|
370
|
+
# #=> ["2001:db8:8::/50",
|
371
|
+
# #=> "2001:db8:8:4000::/50",
|
372
|
+
# #=> "2001:db8:8:8000::/49"]
|
373
|
+
#
|
374
|
+
# Returns an array of IPv6 objects
|
375
|
+
#
|
376
|
+
def split(subnets=2)
|
377
|
+
unless (1..(2**@prefix.host_prefix)).include? subnets
|
378
|
+
raise ArgumentError, "Value #{subnets} out of range"
|
379
|
+
end
|
380
|
+
networks = subnet(newprefix(subnets))
|
381
|
+
until networks.size == subnets
|
382
|
+
networks = sum_first_found(networks)
|
383
|
+
end
|
384
|
+
return networks
|
385
|
+
end
|
386
|
+
alias_method :/, :split
|
387
|
+
|
388
|
+
#
|
389
|
+
# Returns the network number in Unsigned 128bits format
|
390
|
+
#
|
391
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
392
|
+
#
|
393
|
+
# ip6.network_u128
|
394
|
+
# #=> 42540766411282592856903984951653826560
|
395
|
+
#
|
396
|
+
def network_u128
|
397
|
+
to_u128 & @prefix.to_u128
|
398
|
+
end
|
399
|
+
|
400
|
+
#
|
401
|
+
# Returns the broadcast address in Unsigned 128bits format
|
402
|
+
#
|
403
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
404
|
+
#
|
405
|
+
# ip6.broadcast_u128
|
406
|
+
# #=> 42540766411282592875350729025363378175
|
407
|
+
#
|
408
|
+
# Please note that there is no Broadcast concept in IPv6
|
409
|
+
# addresses as in IPv4 addresses, and this method is just
|
410
|
+
# an helper to other functions.
|
411
|
+
#
|
412
|
+
def broadcast_u128
|
413
|
+
network_u128 + size - 1
|
414
|
+
end
|
415
|
+
|
416
|
+
#
|
417
|
+
# Returns the number of IP addresses included
|
418
|
+
# in the network. It also counts the network
|
419
|
+
# address and the broadcast address.
|
420
|
+
#
|
421
|
+
# ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
|
422
|
+
#
|
423
|
+
# ip6.size
|
424
|
+
# #=> 18446744073709551616
|
425
|
+
#
|
426
|
+
def size
|
427
|
+
2 ** @prefix.host_prefix
|
428
|
+
end
|
429
|
+
|
430
|
+
#
|
431
|
+
# Checks whether a subnet includes the given IP address.
|
432
|
+
#
|
433
|
+
# Example:
|
434
|
+
#
|
435
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
436
|
+
# addr = IPAddress "2001:db8::8:800:200c:1/128"
|
437
|
+
#
|
438
|
+
# ip6.include? addr
|
439
|
+
# #=> true
|
440
|
+
#
|
441
|
+
# ip6.include? IPAddress("2001:db8:1::8:800:200c:417a/76")
|
442
|
+
# #=> false
|
443
|
+
#
|
444
|
+
def include?(oth)
|
445
|
+
@prefix <= oth.prefix and network_u128 == self.class.new(oth.address+"/#@prefix").network_u128
|
446
|
+
end
|
447
|
+
|
448
|
+
#
|
449
|
+
# Checks whether a subnet includes all the
|
450
|
+
# given IPv4 objects.
|
451
|
+
#
|
452
|
+
# ip = IPAddress("2001:db8:8:800::1/64")
|
453
|
+
#
|
454
|
+
# addr1 = IPAddress("2001:db8:8:800::2/64")
|
455
|
+
# addr2 = IPAddress("2001:db8:8:800::8/64")
|
456
|
+
#
|
457
|
+
# ip.include_all?(addr1,addr2)
|
458
|
+
# #=> true
|
459
|
+
#
|
460
|
+
def include_all?(*others)
|
461
|
+
others.all? {|oth| include?(oth)}
|
462
|
+
end
|
463
|
+
|
464
|
+
#
|
465
|
+
# Compressed form of the IPv6 address
|
466
|
+
#
|
467
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
468
|
+
#
|
469
|
+
# ip6.compressed
|
470
|
+
# #=> "2001:db8::8:800:200c:417a"
|
471
|
+
#
|
472
|
+
def compressed
|
473
|
+
@compressed
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
# Returns true if the address is an unspecified address
|
478
|
+
#
|
479
|
+
# See IPAddress::IPv6::Unspecified for more information
|
480
|
+
#
|
481
|
+
def unspecified?
|
482
|
+
@prefix == 128 and @compressed == "::"
|
483
|
+
end
|
484
|
+
|
485
|
+
#
|
486
|
+
# Returns true if the address is a loopback address
|
487
|
+
#
|
488
|
+
# See IPAddress::IPv6::Loopback for more information
|
489
|
+
#
|
490
|
+
def loopback?
|
491
|
+
@prefix == 128 and @compressed == "::1"
|
492
|
+
end
|
493
|
+
|
494
|
+
#
|
495
|
+
# Checks if an IPv6 address objects belongs
|
496
|
+
# to a link-local network RFC4291
|
497
|
+
#
|
498
|
+
# Example:
|
499
|
+
#
|
500
|
+
# ip = IPAddress "fe80::1"
|
501
|
+
# ip.link_local?
|
502
|
+
# #=> true
|
503
|
+
#
|
504
|
+
def link_local?
|
505
|
+
[self.class.new("fe80::/10")].any? {|i| i.include? self}
|
506
|
+
end
|
507
|
+
|
508
|
+
#
|
509
|
+
# Checks if an IPv6 address objects belongs
|
510
|
+
# to a unique-local network RFC4193
|
511
|
+
#
|
512
|
+
# Example:
|
513
|
+
#
|
514
|
+
# ip = IPAddress "fc00::1"
|
515
|
+
# ip.unique_local?
|
516
|
+
# #=> true
|
517
|
+
#
|
518
|
+
def unique_local?
|
519
|
+
[self.class.new("fc00::/7")].any? {|i| i.include? self}
|
520
|
+
end
|
521
|
+
|
522
|
+
#
|
523
|
+
# Returns true if the address is a mapped address
|
524
|
+
#
|
525
|
+
# See IPAddress::IPv6::Mapped for more information
|
526
|
+
#
|
527
|
+
def mapped?
|
528
|
+
to_u128 >> 32 == 0xffff
|
529
|
+
end
|
530
|
+
|
531
|
+
#
|
532
|
+
# Returns a new IPv6 object which is the result
|
533
|
+
# of the summarization, if possible, of the two
|
534
|
+
# objects
|
535
|
+
#
|
536
|
+
# Example:
|
537
|
+
#
|
538
|
+
# ip1 = IPAddress("172.16.10.1/24")
|
539
|
+
# ip2 = IPAddress("172.16.11.2/24")
|
540
|
+
#
|
541
|
+
# p (ip1 + ip2).map {|i| i.to_string}
|
542
|
+
# #=> ["172.16.10.0/23"]
|
543
|
+
#
|
544
|
+
# If the networks are not contiguous, returns
|
545
|
+
# the two network numbers from the objects
|
546
|
+
#
|
547
|
+
# ip1 = IPAddress("10.0.0.1/24")
|
548
|
+
# ip2 = IPAddress("10.0.2.1/24")
|
549
|
+
#
|
550
|
+
# p (ip1 + ip2).map {|i| i.to_string}
|
551
|
+
# #=> ["10.0.0.0/24","10.0.2.0/24"]
|
552
|
+
#
|
553
|
+
def +(oth)
|
554
|
+
aggregate(*[self,oth].sort.map{|i| i.network})
|
555
|
+
end
|
556
|
+
|
557
|
+
#
|
558
|
+
# Returns a new IPv4 object from the supernetting
|
559
|
+
# of the instance network.
|
560
|
+
#
|
561
|
+
# Supernetting is similar to subnetting, except
|
562
|
+
# that you getting as a result a network with a
|
563
|
+
# smaller prefix (bigger host space). For example,
|
564
|
+
# given the network
|
565
|
+
#
|
566
|
+
# ip = IPAddress("2001:db8:8:800::1/64")
|
567
|
+
#
|
568
|
+
# you can supernet it with a new /32 prefix
|
569
|
+
#
|
570
|
+
# ip.supernet(32).to_string
|
571
|
+
# #=> "2001:db8::/32"
|
572
|
+
#
|
573
|
+
# However if you supernet it with a /22 prefix, the
|
574
|
+
# network address will change:
|
575
|
+
#
|
576
|
+
# ip.supernet(22).to_string
|
577
|
+
# #=> "2001:c00::/22"
|
578
|
+
#
|
579
|
+
# If +new_prefix+ is less than 1, returns 0000:0000:0000:0000:0000:0000:0000:0000/0
|
580
|
+
#
|
581
|
+
def supernet(new_prefix)
|
582
|
+
raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i
|
583
|
+
return self.class.new("0000:0000:0000:0000:0000:0000:0000:0000/0") if new_prefix < 1
|
584
|
+
return self.class.new(@address+"/#{new_prefix}").network
|
585
|
+
end
|
586
|
+
|
587
|
+
#
|
588
|
+
# This method implements the subnetting function
|
589
|
+
# similar to the one described in RFC3531.
|
590
|
+
#
|
591
|
+
# By specifying a new prefix, the method calculates
|
592
|
+
# the network number for the given IPv4 object
|
593
|
+
# and calculates the subnets associated to the new
|
594
|
+
# prefix.
|
595
|
+
#
|
596
|
+
# For example, given the following network:
|
597
|
+
#
|
598
|
+
# ip = IPAddress "172.16.10.0/24"
|
599
|
+
#
|
600
|
+
# we can calculate the subnets with a /26 prefix
|
601
|
+
#
|
602
|
+
# ip.subnet(26).map{&:to_string)
|
603
|
+
# #=> ["172.16.10.0/26", "172.16.10.64/26",
|
604
|
+
# "172.16.10.128/26", "172.16.10.192/26"]
|
605
|
+
#
|
606
|
+
# The resulting number of subnets will of course always be
|
607
|
+
# a power of two.
|
608
|
+
#
|
609
|
+
def subnet(subprefix)
|
610
|
+
unless ((@prefix.to_i)..128).include? subprefix
|
611
|
+
raise ArgumentError, "New prefix must be between #@prefix and 128"
|
612
|
+
end
|
613
|
+
Array.new(2**(subprefix-@prefix.to_i)) do |i|
|
614
|
+
self.class.parse_u128(network_u128+(i*(2**(128-subprefix))), subprefix)
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
|
619
|
+
#
|
620
|
+
# Iterates over all the IP addresses for the given
|
621
|
+
# network (or IP address).
|
622
|
+
#
|
623
|
+
# The object yielded is a new IPv6 object created
|
624
|
+
# from the iteration.
|
625
|
+
#
|
626
|
+
# ip6 = IPAddress("2001:db8::4/125")
|
627
|
+
#
|
628
|
+
# ip6.each do |i|
|
629
|
+
# p i.compressed
|
630
|
+
# end
|
631
|
+
# #=> "2001:db8::"
|
632
|
+
# #=> "2001:db8::1"
|
633
|
+
# #=> "2001:db8::2"
|
634
|
+
# #=> "2001:db8::3"
|
635
|
+
# #=> "2001:db8::4"
|
636
|
+
# #=> "2001:db8::5"
|
637
|
+
# #=> "2001:db8::6"
|
638
|
+
# #=> "2001:db8::7"
|
639
|
+
#
|
640
|
+
# WARNING: if the host portion is very large, this method
|
641
|
+
# can be very slow and possibly hang your system!
|
642
|
+
#
|
643
|
+
def each
|
644
|
+
(network_u128..broadcast_u128).each do |i|
|
645
|
+
yield self.class.parse_u128(i, @prefix)
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
#
|
650
|
+
# Returns the successor to the IP address
|
651
|
+
#
|
652
|
+
# Example:
|
653
|
+
#
|
654
|
+
# ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
|
655
|
+
#
|
656
|
+
# ip6.succ.to_string
|
657
|
+
# => "2001:db8::8:800:200c:417b/64"
|
658
|
+
#
|
659
|
+
def succ
|
660
|
+
IPAddress::IPv6.parse_u128(to_u128.succ, prefix)
|
661
|
+
end
|
662
|
+
alias_method :next, :succ
|
663
|
+
|
664
|
+
#
|
665
|
+
# Returns the predecessor to the IP address
|
666
|
+
#
|
667
|
+
# Example:
|
668
|
+
#
|
669
|
+
# ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
|
670
|
+
#
|
671
|
+
# ip6.pred.to_string
|
672
|
+
# => "2001:db8::8:800:200c:4179/64"
|
673
|
+
#
|
674
|
+
def pred
|
675
|
+
IPAddress::IPv6.parse_u128(to_u128.pred, prefix)
|
676
|
+
end
|
677
|
+
|
678
|
+
#
|
679
|
+
# Spaceship operator to compare IPv6 objects
|
680
|
+
#
|
681
|
+
# Comparing IPv6 addresses is useful to ordinate
|
682
|
+
# them into lists that match our intuitive
|
683
|
+
# perception of ordered IP addresses.
|
684
|
+
#
|
685
|
+
# The first comparison criteria is the u128 value.
|
686
|
+
# For example, 2001:db8:1::1 will be considered
|
687
|
+
# to be less than 2001:db8:2::1, because, in a ordered list,
|
688
|
+
# we expect 2001:db8:1::1 to come before 2001:db8:2::1.
|
689
|
+
#
|
690
|
+
# The second criteria, in case two IPv6 objects
|
691
|
+
# have identical addresses, is the prefix. An higher
|
692
|
+
# prefix will be considered greater than a lower
|
693
|
+
# prefix. This is because we expect to see
|
694
|
+
# 2001:db8:1::1/64 come before 2001:db8:1::1/65
|
695
|
+
#
|
696
|
+
# Example:
|
697
|
+
#
|
698
|
+
# ip1 = IPAddress "2001:db8:1::1/64"
|
699
|
+
# ip2 = IPAddress "2001:db8:2::1/64"
|
700
|
+
# ip3 = IPAddress "2001:db8:1::1/65"
|
701
|
+
#
|
702
|
+
# ip1 < ip2
|
703
|
+
# #=> true
|
704
|
+
# ip1 < ip3
|
705
|
+
# #=> false
|
706
|
+
#
|
707
|
+
# [ip1,ip2,ip3].sort.map{|i| i.to_string}
|
708
|
+
# #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"]
|
709
|
+
#
|
710
|
+
def <=>(oth)
|
711
|
+
return nil unless oth.is_a?(self.class)
|
712
|
+
return prefix <=> oth.prefix if to_u128 == oth.to_u128
|
713
|
+
to_u128 <=> oth.to_u128
|
714
|
+
end
|
715
|
+
|
716
|
+
#
|
717
|
+
# Returns the address portion of an IP in binary format,
|
718
|
+
# as a string containing a sequence of 0 and 1
|
719
|
+
#
|
720
|
+
# ip6 = IPAddress("2001:db8::8:800:200c:417a")
|
721
|
+
#
|
722
|
+
# ip6.bits
|
723
|
+
# #=> "0010000000000001000011011011100000 [...] "
|
724
|
+
#
|
725
|
+
def bits
|
726
|
+
data.unpack("B*").first
|
727
|
+
end
|
728
|
+
|
729
|
+
#
|
730
|
+
# Expands an IPv6 address in the canocical form
|
731
|
+
#
|
732
|
+
# IPAddress::IPv6.expand "2001:0DB8:0:CD30::"
|
733
|
+
# #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
|
734
|
+
#
|
735
|
+
def self.expand(str)
|
736
|
+
self.new(str).address
|
737
|
+
end
|
738
|
+
|
739
|
+
#
|
740
|
+
# Compress an IPv6 address in its compressed form
|
741
|
+
#
|
742
|
+
# IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000"
|
743
|
+
# #=> "2001:db8:0:cd30::"
|
744
|
+
#
|
745
|
+
def self.compress(str)
|
746
|
+
self.new(str).compressed
|
747
|
+
end
|
748
|
+
|
749
|
+
#
|
750
|
+
# Literal version of the IPv6 address
|
751
|
+
#
|
752
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
753
|
+
#
|
754
|
+
# ip6.literal
|
755
|
+
# #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net"
|
756
|
+
#
|
757
|
+
def literal
|
758
|
+
@address.gsub(":","-") + ".ipv6-literal.net"
|
759
|
+
end
|
760
|
+
|
761
|
+
#
|
762
|
+
# Returns a new IPv6 object with the network number
|
763
|
+
# for the given IP.
|
764
|
+
#
|
765
|
+
# ip = IPAddress "2001:db8:1:1:1:1:1:1/32"
|
766
|
+
#
|
767
|
+
# ip.network.to_string
|
768
|
+
# #=> "2001:db8::/32"
|
769
|
+
#
|
770
|
+
def network
|
771
|
+
self.class.parse_u128(network_u128, @prefix)
|
772
|
+
end
|
773
|
+
|
774
|
+
#
|
775
|
+
# Extract 16 bits groups from a string
|
776
|
+
#
|
777
|
+
def self.groups(str)
|
778
|
+
l, r = if str =~ /^(.*)::(.*)$/
|
779
|
+
[$1,$2].map {|i| i.split ":"}
|
780
|
+
else
|
781
|
+
[str.split(":"),[]]
|
782
|
+
end
|
783
|
+
(l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
|
784
|
+
end
|
785
|
+
|
786
|
+
#
|
787
|
+
# Creates a new IPv6 object from binary data,
|
788
|
+
# like the one you get from a network stream.
|
789
|
+
#
|
790
|
+
# For example, on a network stream the IP
|
791
|
+
#
|
792
|
+
# "2001:db8::8:800:200c:417a"
|
793
|
+
#
|
794
|
+
# is represented with the binary data
|
795
|
+
#
|
796
|
+
# " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
|
797
|
+
#
|
798
|
+
# With that data you can create a new IPv6 object:
|
799
|
+
#
|
800
|
+
# ip6 = IPAddress::IPv6::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
|
801
|
+
# ip6.prefix = 64
|
802
|
+
#
|
803
|
+
# ip6.to_s
|
804
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
805
|
+
#
|
806
|
+
def self.parse_data(str)
|
807
|
+
self.new(IN6FORMAT % str.unpack("n8"))
|
808
|
+
end
|
809
|
+
|
810
|
+
#
|
811
|
+
# Creates a new IPv6 object from an
|
812
|
+
# unsigned 128 bits integer.
|
813
|
+
#
|
814
|
+
# ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122)
|
815
|
+
# ip6.prefix = 64
|
816
|
+
#
|
817
|
+
# ip6.to_string
|
818
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
819
|
+
#
|
820
|
+
# The +prefix+ parameter is optional:
|
821
|
+
#
|
822
|
+
# ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122, 64)
|
823
|
+
#
|
824
|
+
# ip6.to_string
|
825
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
826
|
+
#
|
827
|
+
def self.parse_u128(u128, prefix=128)
|
828
|
+
str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff}
|
829
|
+
self.new(str + "/#{prefix}")
|
830
|
+
end
|
831
|
+
|
832
|
+
#
|
833
|
+
# Creates a new IPv6 object from a number expressed in
|
834
|
+
# hexdecimal format:
|
835
|
+
#
|
836
|
+
# ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a")
|
837
|
+
# ip6.prefix = 64
|
838
|
+
#
|
839
|
+
# ip6.to_string
|
840
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
841
|
+
#
|
842
|
+
# The +prefix+ parameter is optional:
|
843
|
+
#
|
844
|
+
# ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64)
|
845
|
+
#
|
846
|
+
# ip6.to_string
|
847
|
+
# #=> "2001:db8::8:800:200c:417a/64"
|
848
|
+
#
|
849
|
+
def self.parse_hex(hex, prefix=128)
|
850
|
+
self.parse_u128(hex.hex, prefix)
|
851
|
+
end
|
852
|
+
|
853
|
+
#
|
854
|
+
# Summarization (or aggregation) is the process when two or more
|
855
|
+
# networks are taken together to check if a supernet, including all
|
856
|
+
# and only these networks, exists. If it exists then this supernet
|
857
|
+
# is called the summarized (or aggregated) network.
|
858
|
+
#
|
859
|
+
# It is very important to understand that summarization can only
|
860
|
+
# occur if there are no holes in the aggregated network, or, in other
|
861
|
+
# words, if the given networks fill completely the address space
|
862
|
+
# of the supernet. So the two rules are:
|
863
|
+
#
|
864
|
+
# 1) The aggregate network must contain +all+ the IP addresses of the
|
865
|
+
# original networks;
|
866
|
+
# 2) The aggregate network must contain +only+ the IP addresses of the
|
867
|
+
# original networks;
|
868
|
+
#
|
869
|
+
# A few examples will help clarify the above. Let's consider for
|
870
|
+
# instance the following two networks:
|
871
|
+
#
|
872
|
+
# ip1 = IPAddress("2001:db8:8:800::1/64")
|
873
|
+
# ip2 = IPAddress("2001:0db8:8:801::2/64")
|
874
|
+
#
|
875
|
+
# These two networks can be expressed using only one IP address
|
876
|
+
# network if we change the prefix. Let Ruby do the work:
|
877
|
+
#
|
878
|
+
# IPAddress::IPv6::summarize(ip1,ip2).to_s
|
879
|
+
# #=> "2001:db8:8:800::/63"
|
880
|
+
#
|
881
|
+
# We note how the network "2001:db8:8:800::/63" includes all the addresses
|
882
|
+
# specified in the above networks, and (more important) includes
|
883
|
+
# ONLY those addresses.
|
884
|
+
#
|
885
|
+
# If we summarized +ip1+ and +ip2+ with the following network:
|
886
|
+
#
|
887
|
+
# "2001:db8::/32"
|
888
|
+
#
|
889
|
+
# we would have satisfied rule #1 above, but not rule #2. So "2001:db8::/32"
|
890
|
+
# is not an aggregate network for +ip1+ and +ip2+.
|
891
|
+
#
|
892
|
+
# If it's not possible to compute a single aggregated network for all the
|
893
|
+
# original networks, the method returns an array with all the aggregate
|
894
|
+
# networks found. For example, the following four networks can be
|
895
|
+
# aggregated in a single /22:
|
896
|
+
#
|
897
|
+
# ip1 = IPAddress("2001:db8:8:800::1/64")
|
898
|
+
# ip2 = IPAddress("2001:db8:8:801::1/64")
|
899
|
+
# ip3 = IPAddress("2001:db8:8:802::1/64")
|
900
|
+
# ip4 = IPAddress("2001:db8:8:803::1/64")
|
901
|
+
#
|
902
|
+
# IPAddress::IPv6::summarize(ip1,ip2,ip3,ip4).to_string
|
903
|
+
# #=> "2001:db8:8:800::/62",
|
904
|
+
#
|
905
|
+
# But the following networks can't be summarized in a single network:
|
906
|
+
#
|
907
|
+
# ip1 = IPAddress("2001:db8:8:801::1/64")
|
908
|
+
# ip2 = IPAddress("2001:db8:8:802::1/64")
|
909
|
+
# ip3 = IPAddress("2001:db8:8:803::1/64")
|
910
|
+
# ip4 = IPAddress("2001:db8:8:804::1/64")
|
911
|
+
#
|
912
|
+
# IPAddress::IPv6::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string}
|
913
|
+
# #=> ["2001:db8:8:801::/64","2001:db8:8:802::/63","2001:db8:8:804::/64"]
|
914
|
+
#
|
915
|
+
def self.summarize(*args)
|
916
|
+
# one network? no need to summarize
|
917
|
+
return [args.first.network] if args.size == 1
|
918
|
+
|
919
|
+
i = 0
|
920
|
+
result = args.dup.sort.map{|ip| ip.network}
|
921
|
+
while i < result.size-1
|
922
|
+
sum = result[i] + result[i+1]
|
923
|
+
result[i..i+1] = sum.first if sum.size == 1
|
924
|
+
i += 1
|
925
|
+
end
|
926
|
+
|
927
|
+
result.flatten!
|
928
|
+
if result.size == args.size
|
929
|
+
# nothing more to summarize
|
930
|
+
return result
|
931
|
+
else
|
932
|
+
# keep on summarizing
|
933
|
+
return self.summarize(*result)
|
934
|
+
end
|
935
|
+
end
|
936
|
+
|
937
|
+
#
|
938
|
+
# Allocates a new ip from the current subnet. Optional skip parameter
|
939
|
+
# can be used to skip addresses.
|
940
|
+
#
|
941
|
+
# Will raise StopIteration exception when all addresses have been allocated
|
942
|
+
#
|
943
|
+
# Example:
|
944
|
+
#
|
945
|
+
# ip = IPAddress("10.0.0.0/24")
|
946
|
+
# ip.allocate
|
947
|
+
# #=> "10.0.0.1/24"
|
948
|
+
# ip.allocate
|
949
|
+
# #=> "10.0.0.2/24"
|
950
|
+
# ip.allocate(2)
|
951
|
+
# #=> "10.0.0.5/24"
|
952
|
+
#
|
953
|
+
#
|
954
|
+
# Uses an internal @allocator which tracks the state of allocated
|
955
|
+
# addresses.
|
956
|
+
#
|
957
|
+
def allocate(skip=0)
|
958
|
+
@allocator += 1 + skip
|
959
|
+
|
960
|
+
next_ip = network_u128+@allocator
|
961
|
+
if next_ip > broadcast_u128
|
962
|
+
raise StopIteration
|
963
|
+
end
|
964
|
+
self.class.parse_u128(next_ip, @prefix)
|
965
|
+
end
|
966
|
+
|
967
|
+
#
|
968
|
+
# Finds the adjacent block to a subnet.
|
969
|
+
#
|
970
|
+
# Example:
|
971
|
+
#
|
972
|
+
# ip = IPAddress("2001:db8::/32")
|
973
|
+
# ip.find_adjacent_subnet
|
974
|
+
# #=> "2001:db9::/32"
|
975
|
+
#
|
976
|
+
def find_adjacent_subnet
|
977
|
+
return false if prefix == 0
|
978
|
+
current_subnet = to_string
|
979
|
+
self.prefix = @prefix - 1
|
980
|
+
(split.map{|i| i.to_string} - [current_subnet])[0]
|
981
|
+
end
|
982
|
+
|
983
|
+
private
|
984
|
+
|
985
|
+
def newprefix(num)
|
986
|
+
return @prefix + (Math::log2(num).ceil )
|
987
|
+
end
|
988
|
+
|
989
|
+
def sum_first_found(arr)
|
990
|
+
dup = arr.dup.reverse
|
991
|
+
dup.each_with_index do |obj,i|
|
992
|
+
a = [self.class.summarize(obj,dup[i+1])].flatten
|
993
|
+
if a.size == 1
|
994
|
+
dup[i..i+1] = a
|
995
|
+
return dup.reverse
|
996
|
+
end
|
997
|
+
end
|
998
|
+
return dup.reverse
|
999
|
+
end
|
1000
|
+
|
1001
|
+
def compress_address
|
1002
|
+
str = @groups.map{|i| i.to_s 16}.join ":"
|
1003
|
+
loop do
|
1004
|
+
break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
|
1005
|
+
break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
|
1006
|
+
break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
|
1007
|
+
break if str.sub!(/\b0:0:0:0:0\b/, ':')
|
1008
|
+
break if str.sub!(/\b0:0:0:0\b/, ':')
|
1009
|
+
break if str.sub!(/\b0:0:0\b/, ':')
|
1010
|
+
break if str.sub!(/\b0:0\b/, ':')
|
1011
|
+
break
|
1012
|
+
end
|
1013
|
+
str.sub(/:{3,}/, '::')
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
def aggregate(ip1,ip2)
|
1017
|
+
return [ip1] if ip1.include? ip2
|
1018
|
+
|
1019
|
+
snet = ip1.supernet(ip1.prefix-1)
|
1020
|
+
if snet.include_all?(ip1, ip2) && ((ip1.size + ip2.size) == snet.size)
|
1021
|
+
return [snet]
|
1022
|
+
else
|
1023
|
+
return [ip1, ip2]
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
end # class IPv6
|
1028
|
+
|
1029
|
+
#
|
1030
|
+
# The address with all zero bits is called the +unspecified+ address
|
1031
|
+
# (corresponding to 0.0.0.0 in IPv4). It should be something like this:
|
1032
|
+
#
|
1033
|
+
# 0000:0000:0000:0000:0000:0000:0000:0000
|
1034
|
+
#
|
1035
|
+
# but, with the use of compression, it is usually written as just two
|
1036
|
+
# colons:
|
1037
|
+
#
|
1038
|
+
# ::
|
1039
|
+
#
|
1040
|
+
# or, specifying the netmask:
|
1041
|
+
#
|
1042
|
+
# ::/128
|
1043
|
+
#
|
1044
|
+
# With IPAddress, create a new unspecified IPv6 address using its own
|
1045
|
+
# subclass:
|
1046
|
+
#
|
1047
|
+
# ip = IPAddress::IPv6::Unspecified.new
|
1048
|
+
#
|
1049
|
+
# ip.to_s
|
1050
|
+
# #=> => "::/128"
|
1051
|
+
#
|
1052
|
+
# You can easily check if an IPv6 object is an unspecified address by
|
1053
|
+
# using the IPv6#unspecified? method
|
1054
|
+
#
|
1055
|
+
# ip.unspecified?
|
1056
|
+
# #=> true
|
1057
|
+
#
|
1058
|
+
# An unspecified IPv6 address can also be created with the wrapper
|
1059
|
+
# method, like we've seen before
|
1060
|
+
#
|
1061
|
+
# ip = IPAddress "::"
|
1062
|
+
#
|
1063
|
+
# ip.unspecified?
|
1064
|
+
# #=> true
|
1065
|
+
#
|
1066
|
+
# This address must never be assigned to an interface and is to be used
|
1067
|
+
# only in software before the application has learned its host's source
|
1068
|
+
# address appropriate for a pending connection. Routers must not forward
|
1069
|
+
# packets with the unspecified address.
|
1070
|
+
#
|
1071
|
+
class IPAddress::IPv6::Unspecified < IPAddress::IPv6
|
1072
|
+
#
|
1073
|
+
# Creates a new IPv6 unspecified address
|
1074
|
+
#
|
1075
|
+
# ip = IPAddress::IPv6::Unspecified.new
|
1076
|
+
#
|
1077
|
+
# ip.to_s
|
1078
|
+
# #=> => "::/128"
|
1079
|
+
#
|
1080
|
+
def initialize
|
1081
|
+
@address = ("0000:"*8).chop
|
1082
|
+
@groups = Array.new(8,0)
|
1083
|
+
@prefix = Prefix128.new(128)
|
1084
|
+
@compressed = compress_address
|
1085
|
+
end
|
1086
|
+
end # class IPv6::Unspecified
|
1087
|
+
|
1088
|
+
#
|
1089
|
+
# The loopback address is a unicast localhost address. If an
|
1090
|
+
# application in a host sends packets to this address, the IPv6 stack
|
1091
|
+
# will loop these packets back on the same virtual interface.
|
1092
|
+
#
|
1093
|
+
# Loopback addresses are expressed in the following form:
|
1094
|
+
#
|
1095
|
+
# ::1
|
1096
|
+
#
|
1097
|
+
# or, with their appropriate prefix,
|
1098
|
+
#
|
1099
|
+
# ::1/128
|
1100
|
+
#
|
1101
|
+
# As for the unspecified addresses, IPv6 loopbacks can be created with
|
1102
|
+
# IPAddress calling their own class:
|
1103
|
+
#
|
1104
|
+
# ip = IPAddress::IPv6::Loopback.new
|
1105
|
+
#
|
1106
|
+
# ip.to_string
|
1107
|
+
# #=> "::1/128"
|
1108
|
+
#
|
1109
|
+
# or by using the wrapper:
|
1110
|
+
#
|
1111
|
+
# ip = IPAddress "::1"
|
1112
|
+
#
|
1113
|
+
# ip.to_string
|
1114
|
+
# #=> "::1/128"
|
1115
|
+
#
|
1116
|
+
# Checking if an address is loopback is easy with the IPv6#loopback?
|
1117
|
+
# method:
|
1118
|
+
#
|
1119
|
+
# ip.loopback?
|
1120
|
+
# #=> true
|
1121
|
+
#
|
1122
|
+
# The IPv6 loopback address corresponds to 127.0.0.1 in IPv4.
|
1123
|
+
#
|
1124
|
+
class IPAddress::IPv6::Loopback < IPAddress::IPv6
|
1125
|
+
#
|
1126
|
+
# Creates a new IPv6 unspecified address
|
1127
|
+
#
|
1128
|
+
# ip = IPAddress::IPv6::Loopback.new
|
1129
|
+
#
|
1130
|
+
# ip.to_string
|
1131
|
+
# #=> "::1/128"
|
1132
|
+
#
|
1133
|
+
def initialize
|
1134
|
+
@address = ("0000:"*7)+"0001"
|
1135
|
+
@groups = Array.new(7,0).push(1)
|
1136
|
+
@prefix = Prefix128.new(128)
|
1137
|
+
@compressed = compress_address
|
1138
|
+
end
|
1139
|
+
end # class IPv6::Loopback
|
1140
|
+
|
1141
|
+
#
|
1142
|
+
# It is usually identified as a IPv4 mapped IPv6 address, a particular
|
1143
|
+
# IPv6 address which aids the transition from IPv4 to IPv6. The
|
1144
|
+
# structure of the address is
|
1145
|
+
#
|
1146
|
+
# ::ffff:w.y.x.z
|
1147
|
+
#
|
1148
|
+
# where w.x.y.z is a normal IPv4 address. For example, the following is
|
1149
|
+
# a mapped IPv6 address:
|
1150
|
+
#
|
1151
|
+
# ::ffff:192.168.100.1
|
1152
|
+
#
|
1153
|
+
# IPAddress is very powerful in handling mapped IPv6 addresses, as the
|
1154
|
+
# IPv4 portion is stored internally as a normal IPv4 object. Let's have
|
1155
|
+
# a look at some examples. To create a new mapped address, just use the
|
1156
|
+
# class builder itself
|
1157
|
+
#
|
1158
|
+
# ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
|
1159
|
+
#
|
1160
|
+
# or just use the wrapper method
|
1161
|
+
#
|
1162
|
+
# ip6 = IPAddress "::ffff:172.16.10.1/128"
|
1163
|
+
#
|
1164
|
+
# Let's check it's really a mapped address:
|
1165
|
+
#
|
1166
|
+
# ip6.mapped?
|
1167
|
+
# #=> true
|
1168
|
+
#
|
1169
|
+
# ip6.to_string
|
1170
|
+
# #=> "::FFFF:172.16.10.1/128"
|
1171
|
+
#
|
1172
|
+
# Now with the +ipv4+ attribute, we can easily access the IPv4 portion
|
1173
|
+
# of the mapped IPv6 address:
|
1174
|
+
#
|
1175
|
+
# ip6.ipv4.address
|
1176
|
+
# #=> "172.16.10.1"
|
1177
|
+
#
|
1178
|
+
# Internally, the IPv4 address is stored as two 16 bits
|
1179
|
+
# groups. Therefore all the usual methods for an IPv6 address are
|
1180
|
+
# working perfectly fine:
|
1181
|
+
#
|
1182
|
+
# ip6.to_hex
|
1183
|
+
# #=> "00000000000000000000ffffac100a01"
|
1184
|
+
#
|
1185
|
+
# ip6.address
|
1186
|
+
# #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01"
|
1187
|
+
#
|
1188
|
+
# A mapped IPv6 can also be created just by specify the address in the
|
1189
|
+
# following format:
|
1190
|
+
#
|
1191
|
+
# ip6 = IPAddress "::172.16.10.1"
|
1192
|
+
#
|
1193
|
+
# That is, two colons and the IPv4 address. However, as by RFC, the ffff
|
1194
|
+
# group will be automatically added at the beginning
|
1195
|
+
#
|
1196
|
+
# ip6.to_string
|
1197
|
+
# => "::ffff:172.16.10.1/128"
|
1198
|
+
#
|
1199
|
+
# making it a mapped IPv6 compatible address.
|
1200
|
+
#
|
1201
|
+
class IPAddress::IPv6::Mapped < IPAddress::IPv6
|
1202
|
+
|
1203
|
+
# Access the internal IPv4 address
|
1204
|
+
attr_reader :ipv4
|
1205
|
+
|
1206
|
+
#
|
1207
|
+
# Creates a new IPv6 IPv4-mapped address
|
1208
|
+
#
|
1209
|
+
# ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
|
1210
|
+
#
|
1211
|
+
# ipv6.ipv4.class
|
1212
|
+
# #=> IPAddress::IPv4
|
1213
|
+
#
|
1214
|
+
# An IPv6 IPv4-mapped address can also be created using the
|
1215
|
+
# IPv6 only format of the address:
|
1216
|
+
#
|
1217
|
+
# ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403"
|
1218
|
+
#
|
1219
|
+
# ip6.to_string
|
1220
|
+
# #=> "::ffff:13.1.68.3"
|
1221
|
+
#
|
1222
|
+
def initialize(str)
|
1223
|
+
string, netmask = str.split("/")
|
1224
|
+
if string =~ /\./ # IPv4 in dotted decimal form
|
1225
|
+
@ipv4 = IPAddress::IPv4.extract(string)
|
1226
|
+
else # IPv4 in hex form
|
1227
|
+
groups = IPAddress::IPv6.groups(string)
|
1228
|
+
@ipv4 = IPAddress::IPv4.parse_u32((groups[-2]<< 16)+groups[-1])
|
1229
|
+
end
|
1230
|
+
super("::ffff:#{@ipv4.to_ipv6}/#{netmask}")
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
#
|
1234
|
+
# Similar to IPv6#to_s, but prints out the IPv4 address
|
1235
|
+
# in dotted decimal format
|
1236
|
+
#
|
1237
|
+
# ip6 = IPAddress "::ffff:172.16.10.1/128"
|
1238
|
+
#
|
1239
|
+
# ip6.to_s
|
1240
|
+
# #=> "::ffff:172.16.10.1"
|
1241
|
+
#
|
1242
|
+
def to_s
|
1243
|
+
"::ffff:#{@ipv4.address}"
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
#
|
1247
|
+
# Similar to IPv6#to_string, but prints out the IPv4 address
|
1248
|
+
# in dotted decimal format
|
1249
|
+
#
|
1250
|
+
#
|
1251
|
+
# ip6 = IPAddress "::ffff:172.16.10.1/128"
|
1252
|
+
#
|
1253
|
+
# ip6.to_string
|
1254
|
+
# #=> "::ffff:172.16.10.1/128"
|
1255
|
+
#
|
1256
|
+
def to_string
|
1257
|
+
"::ffff:#{@ipv4.address}/#@prefix"
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
#
|
1261
|
+
# Checks if the IPv6 address is IPv4 mapped
|
1262
|
+
#
|
1263
|
+
# ip6 = IPAddress "::ffff:172.16.10.1/128"
|
1264
|
+
#
|
1265
|
+
# ip6.mapped?
|
1266
|
+
# #=> true
|
1267
|
+
#
|
1268
|
+
def mapped?
|
1269
|
+
true
|
1270
|
+
end
|
1271
|
+
end # class IPv6::Mapped
|
1272
|
+
|
1273
|
+
end # module IPAddress
|
1274
|
+
|