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.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +875 -0
- data/Rakefile +89 -0
- data/VERSION +1 -0
- data/lib/ipaddress.rb +52 -0
- data/lib/ipaddress/extensions/extensions.rb +16 -0
- data/lib/ipaddress/ipbase.rb +83 -0
- data/lib/ipaddress/ipv4.rb +849 -0
- data/lib/ipaddress/ipv6.rb +683 -0
- data/lib/ipaddress/prefix.rb +200 -0
- data/test/ipaddress/extensions/extensions_test.rb +18 -0
- data/test/ipaddress/ipbase_test.rb +28 -0
- data/test/ipaddress/ipv4_test.rb +400 -0
- data/test/ipaddress/ipv6_test.rb +290 -0
- data/test/ipaddress/prefix_test.rb +139 -0
- data/test/ipaddress_test.rb +38 -0
- data/test/test_helper.rb +33 -0
- metadata +92 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Marco Ceresa
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,875 @@
|
|
1
|
+
= IPAddress
|
2
|
+
|
3
|
+
IPAddress is a Ruby library designed to make the use of IPv4 and IPv6
|
4
|
+
addresses easy, powerful and enjoyable. It provides a complete set of
|
5
|
+
methods to handle IP addresses for any need, from simple scripting to
|
6
|
+
full network design.
|
7
|
+
|
8
|
+
IPAddress is written with a full OO interface, and its code is easy to
|
9
|
+
read, maintain and extend. The documentation is full of examples, to
|
10
|
+
let you start being productive immediately.
|
11
|
+
|
12
|
+
This document provides a brief introduction to the library and
|
13
|
+
examples of typical usage.
|
14
|
+
|
15
|
+
=== Why not using IPAddr?
|
16
|
+
|
17
|
+
IPAddr is the IP addresses library that comes with Ruby standard
|
18
|
+
lib. We found this library, although very well written, not very
|
19
|
+
suitable for all our needs, and not very flexible.
|
20
|
+
|
21
|
+
Some quick examples of things you can't do with IPAddr:
|
22
|
+
|
23
|
+
* store both the address and the prefix information
|
24
|
+
* quickly find the broadcast address of a network
|
25
|
+
* iterate over hosts
|
26
|
+
* perform subnetting or network aggregation
|
27
|
+
|
28
|
+
We hope that IPAddress will address all these issues and meet all your
|
29
|
+
needs in network programming.
|
30
|
+
|
31
|
+
= Installation
|
32
|
+
|
33
|
+
Install the library using rubygems
|
34
|
+
|
35
|
+
$ gem install ipaddress
|
36
|
+
|
37
|
+
You can then use it in your programs:
|
38
|
+
|
39
|
+
require 'rubygems' # optional
|
40
|
+
require 'ipaddress'
|
41
|
+
|
42
|
+
Another way would be to clone the git repository
|
43
|
+
|
44
|
+
$ git clone git://github.com/bluemonk/ipaddress.git
|
45
|
+
|
46
|
+
And then install the library
|
47
|
+
|
48
|
+
$ cd ipaddress
|
49
|
+
ipaddress$ rake install
|
50
|
+
|
51
|
+
= Documentation
|
52
|
+
|
53
|
+
The code is fully documented with RDoc. You can generate the
|
54
|
+
documentation with Rake:
|
55
|
+
|
56
|
+
ipaddress$ rake rdoc
|
57
|
+
|
58
|
+
The latest documentation can be found online at the following address
|
59
|
+
|
60
|
+
http://marcoceresa.com/ipaddress
|
61
|
+
|
62
|
+
= Usage
|
63
|
+
|
64
|
+
In this section I will illustrate how to use the IPAddress library
|
65
|
+
through some examples of common tasks.
|
66
|
+
|
67
|
+
== IPv4
|
68
|
+
|
69
|
+
Class IPAddress::IPv4 is used to handle IPv4 type addresses. IPAddress
|
70
|
+
is similar to other IP Addresses libraries, like Ruby's own
|
71
|
+
IPAddr. However it works slightly different, as we will see.
|
72
|
+
|
73
|
+
=== Create a new IPv4 address
|
74
|
+
|
75
|
+
The usual way to express an IP Address is using its dotted decimal
|
76
|
+
form, such as 172.16.10.1, and a prefix, such as 24, separated by a
|
77
|
+
slash.
|
78
|
+
|
79
|
+
172.16.10.1/24
|
80
|
+
|
81
|
+
To create a new IPv4 object, you can use IPv4 own class
|
82
|
+
|
83
|
+
ip = IPAddress::IPv4.new "172.16.10.1/24"
|
84
|
+
|
85
|
+
or, in a easier way, using the IPAddress wrapper method
|
86
|
+
|
87
|
+
ip = IPAddress "172.16.10.1/24"
|
88
|
+
|
89
|
+
You can specify an IPv4 address in any of two ways:
|
90
|
+
|
91
|
+
IPAddress "172.16.10.1/24"
|
92
|
+
IPAddress "172.16.10.1/255.255.255.0"
|
93
|
+
|
94
|
+
In this example, prefix /24 and netmask 255.255.255.0 are the same and
|
95
|
+
you have the flexibility to use either one of them.
|
96
|
+
|
97
|
+
=== Classful networks
|
98
|
+
|
99
|
+
If you don't specify a prefix (or a subnet mask), then the library
|
100
|
+
will create an object base on the CLASSFUL network from the given
|
101
|
+
IP. Remember the CLASSFUL network are the following (RFC 791)
|
102
|
+
|
103
|
+
* Class A, from 0.0.0.0 to 127.255.255.255
|
104
|
+
* Class B, from 128.0.0.0 to 191.255.255.255
|
105
|
+
* Class C, from 192.0.0.0 to 255.255.255.255
|
106
|
+
|
107
|
+
Since classful networks here are only considered to calculate the default
|
108
|
+
prefix number, classes D and E are not considered.
|
109
|
+
|
110
|
+
You can easily check which CLASSFUL network the IP belongs:
|
111
|
+
|
112
|
+
ip = IPAddress("10.0.0.1/24")
|
113
|
+
ip.a?
|
114
|
+
#=> true
|
115
|
+
|
116
|
+
ip = IPAddress("172.16.10.1/24")
|
117
|
+
ip.b?
|
118
|
+
#=> true
|
119
|
+
|
120
|
+
ip = IPAddress("192.168.1.1/30")
|
121
|
+
ip.c?
|
122
|
+
#=> true
|
123
|
+
|
124
|
+
These methods are only checking the address portion of an IP, and are
|
125
|
+
indipendent from its prefix.
|
126
|
+
|
127
|
+
For more information on CLASSFUL networks visit the following
|
128
|
+
Wikipedia page: http://en.wikipedia.org/wiki/Classful_network
|
129
|
+
|
130
|
+
=== Handling the IPv4 address
|
131
|
+
|
132
|
+
Once created, you can obtain the attributes for an IPv4 object:
|
133
|
+
|
134
|
+
ip = IPAddress("172.16.10.1/24")
|
135
|
+
|
136
|
+
ip.address
|
137
|
+
#=> "172.16.10.1"
|
138
|
+
ip.prefix
|
139
|
+
#=> 24
|
140
|
+
|
141
|
+
In case you need to retrieve the netmask in IPv4 format, you can use
|
142
|
+
the IPv4#netmask method:
|
143
|
+
|
144
|
+
ip.netmask
|
145
|
+
#=> "255.255.255.0"
|
146
|
+
|
147
|
+
A special attribute, IPv4#octets, is available to get the four
|
148
|
+
decimal octets from the IP address:
|
149
|
+
|
150
|
+
ip.octets
|
151
|
+
#=> [172,16,10,1]
|
152
|
+
|
153
|
+
Shortcut method IPv4#[], provides access to a given octet whithin the
|
154
|
+
range:
|
155
|
+
|
156
|
+
ip[1]
|
157
|
+
#=> 16
|
158
|
+
|
159
|
+
If you need to print out the IPv4 address in a canonical form, you can
|
160
|
+
use IPv4#to_s
|
161
|
+
|
162
|
+
ip.to_s
|
163
|
+
#=> "172.16.10.l/24"
|
164
|
+
|
165
|
+
=== Changing netmask
|
166
|
+
|
167
|
+
You can set a new prefix (netmask) after creating an IPv4
|
168
|
+
object. For example:
|
169
|
+
|
170
|
+
ip.prefix = 25
|
171
|
+
|
172
|
+
ip.to_s
|
173
|
+
#=> "172.16.10.l/25"
|
174
|
+
|
175
|
+
If you need to use a netmask in IPv4 format, you can achive so by
|
176
|
+
using the IPv4#netmask= method
|
177
|
+
|
178
|
+
ip.netmask = "255.255.255.252"
|
179
|
+
|
180
|
+
ip.to_s
|
181
|
+
#=> "172.16.10.1/30"
|
182
|
+
|
183
|
+
=== Working with networks, broadcasts and addresses
|
184
|
+
|
185
|
+
Some very important topics in dealing with IP addresses are the
|
186
|
+
concepts of +network+ and +broadcast+, as well as the addresses
|
187
|
+
included in a range.
|
188
|
+
|
189
|
+
When you specify an IPv4 address such as "172.16.10.1/24", you are
|
190
|
+
actually handling two different information:
|
191
|
+
|
192
|
+
* The IP address itself, "172.16.10.1"
|
193
|
+
* The subnet mask which indicates the network
|
194
|
+
|
195
|
+
The network number is the IP which has all zeroes in the host
|
196
|
+
portion. In our example, because the prefix is 24, we identify our
|
197
|
+
network number to have the last 8 (32-24) bits all zeroes. Thus, IP
|
198
|
+
address "172.16.10.1/24" belongs to network "172.16.10.0/24".
|
199
|
+
|
200
|
+
This is very important because, for instance, IP "172.16.10.1/16" is
|
201
|
+
very different to the previous one, belonging to the very different
|
202
|
+
network "172.16.0.0/16".
|
203
|
+
|
204
|
+
With IPAddress it's very easy to calculate the network for an IP
|
205
|
+
address:
|
206
|
+
|
207
|
+
ip = IPAddress "172.16.10.1/24"
|
208
|
+
|
209
|
+
net = ip.network
|
210
|
+
#=> #<IPAddress::IPv4:0xb7a5ab24 @octets=[172, 16, 10, 0],
|
211
|
+
@prefix=24,
|
212
|
+
@address="172.16.10.0">
|
213
|
+
net.to_s
|
214
|
+
#=> "172.16.10.0/24"
|
215
|
+
|
216
|
+
The IPv4#network method creates a new IPv4 object from the network
|
217
|
+
number, calculated after the original object. We want to outline here
|
218
|
+
that the network address is a perfect legitimate IPv4 address, which
|
219
|
+
just happen to have all zeroes in the host portion.
|
220
|
+
|
221
|
+
You can use method IPv4#network? to check whether an IP address is a
|
222
|
+
network or not:
|
223
|
+
|
224
|
+
ip1 = IPAddress "172.16.10.1/24"
|
225
|
+
ip2 = IPAddress "172.16.10.4/30"
|
226
|
+
|
227
|
+
ip1.network?
|
228
|
+
#=> false
|
229
|
+
ip2.network?
|
230
|
+
#=> true
|
231
|
+
|
232
|
+
The broadcast address is the contrary than the network number: where
|
233
|
+
the network number has all zeroes in the host portion, the broadcast
|
234
|
+
address has all one's. For example, ip "172.16.10.1/24" has broadcast
|
235
|
+
"172.16.10.255/24", where ip "172.16.10.1/16" has broadcast
|
236
|
+
"172.16.255.255/16".
|
237
|
+
|
238
|
+
Method IPv4#broadcast has the same behaviour as is #network
|
239
|
+
counterpart: it creates a new IPv4 object to handle the broadcast
|
240
|
+
address:
|
241
|
+
|
242
|
+
ip = IPAddress "172.16.10.1/24"
|
243
|
+
|
244
|
+
bcast = ip.broadcast
|
245
|
+
#=> #<IPAddress::IPv4:0xb7a406fc @octets=[172, 16, 10, 255],
|
246
|
+
@prefix=24,
|
247
|
+
@address="172.16.10.255">
|
248
|
+
bcast.to_s
|
249
|
+
#=> "172.16.10.255/24"
|
250
|
+
|
251
|
+
So we see that the netmask essentially specifies a range for IP
|
252
|
+
addresses that are included in a network: all the addresses between
|
253
|
+
the network number and the broadcast. IPAddress has many methods to
|
254
|
+
iterate between those addresses. Let's start with IPv4#each, which
|
255
|
+
iterates over all addresses in a range
|
256
|
+
|
257
|
+
ip = IPAddress "172.16.10.1/24"
|
258
|
+
|
259
|
+
ip.each do |addr|
|
260
|
+
puts addr
|
261
|
+
end
|
262
|
+
|
263
|
+
It is important to note that it doesn't matter if the original IP is a
|
264
|
+
host IP or a network number (or a broadcast address): the #each method
|
265
|
+
only considers the range that the original IP specifies.
|
266
|
+
|
267
|
+
If you only want to iterate over hosts IP, use the Ipv4#each_host
|
268
|
+
method:
|
269
|
+
|
270
|
+
ip = IPAddress "172.16.10.1/24"
|
271
|
+
|
272
|
+
ip.each_host do |host|
|
273
|
+
puts host
|
274
|
+
end
|
275
|
+
|
276
|
+
Methods IPv4#first and IPv4#last return a new object containing
|
277
|
+
respectively the first and the last host address in the range
|
278
|
+
|
279
|
+
ip = IPAddress "172.16.10.100/24"
|
280
|
+
|
281
|
+
ip.first.to_s
|
282
|
+
#=> "172.16.10.1/24"
|
283
|
+
|
284
|
+
ip.last.to_s
|
285
|
+
#=> "172.16.10.254/24"
|
286
|
+
|
287
|
+
=== IP special formats
|
288
|
+
|
289
|
+
The IPAddress library provides a complete set of methods to access an
|
290
|
+
IPv4 address in special formats, such as binary, 32 bits unsigned int,
|
291
|
+
data and hexadecimal.
|
292
|
+
|
293
|
+
Let's take the following IPv4 as an example:
|
294
|
+
|
295
|
+
ip = IPAddress "172.16.10.1/24"
|
296
|
+
|
297
|
+
ip.address
|
298
|
+
#=> "172.16.10.1"
|
299
|
+
|
300
|
+
The first thing to highlight here is that all these conversion methods
|
301
|
+
only take into consideration the address portion of an IPv4 object and
|
302
|
+
not the prefix (netmask).
|
303
|
+
|
304
|
+
So, to express the address in binary format, use the IPv4#bits method:
|
305
|
+
|
306
|
+
ip.bits
|
307
|
+
#=> "10101100000100000000101000000001"
|
308
|
+
|
309
|
+
To calculate the 32 bits unsigned int format of the ip address, use
|
310
|
+
the IPv4#to_u32 method
|
311
|
+
|
312
|
+
ip.to_u32
|
313
|
+
#=> 2886732289
|
314
|
+
|
315
|
+
This method is the equivalent of the Unix call pton(), expressing an
|
316
|
+
IP address in the so called +network byte order+ notation. However, if
|
317
|
+
you want to trasmit your IP over a network socket, you might need to
|
318
|
+
transform it in data format using the IPv4#data method:
|
319
|
+
|
320
|
+
ip.data
|
321
|
+
#=> "\254\020\n\001"
|
322
|
+
|
323
|
+
Finally, you can transform an IPv4 address into a format which is
|
324
|
+
suitable to use in IPv4-IPv6 mapped addresses:
|
325
|
+
|
326
|
+
ip.to_ipv6
|
327
|
+
#=> "ac10:0a01"
|
328
|
+
|
329
|
+
|
330
|
+
== Network design with IPAddress
|
331
|
+
|
332
|
+
IPAddress includes a lot of useful methods to manipulate IPv4 and IPv6
|
333
|
+
networks and do some basic network design.
|
334
|
+
|
335
|
+
=== Subnetting
|
336
|
+
|
337
|
+
The process of subnetting is the division of a network into smaller
|
338
|
+
(in terms of hosts capacity) networks, called subnets, so that they
|
339
|
+
all share a common root, which is the starting network.
|
340
|
+
|
341
|
+
For example, if you have network "172.16.10.0/24", we can subnet it
|
342
|
+
into 4 smaller subnets. The new prefix will be /26, because 4 is 2^2
|
343
|
+
and therefore we add 2 bits to the network prefix (24+2=26).
|
344
|
+
|
345
|
+
Subnetting is easy with IPAddress. Let's work out the last example:
|
346
|
+
|
347
|
+
network = IPAddress("172.16.10.0/24")
|
348
|
+
|
349
|
+
subnets = network / 4
|
350
|
+
#=> [#<IPAddress::IPv4:0xb7b10e10 @octets=[172,16,10,0] [...]
|
351
|
+
#<IPAddress::IPv4:0xb7b0f1b4 @octets=[172,16,10,64] [...]
|
352
|
+
#<IPAddress::IPv4:0xb7b0e5ac @octets=[172,16,10,128] [...]
|
353
|
+
#<IPAddress::IPv4:0xb7b0e0c0 @octets=[172,16,10,192] [...]]
|
354
|
+
|
355
|
+
subnets.map{|i| i.to_s}
|
356
|
+
#=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
|
357
|
+
"172.16.10.192/26"]
|
358
|
+
|
359
|
+
You can also use method IPv4#subnets, which is an alias for
|
360
|
+
IPv4#/. Please note that you don't have to specify a network to
|
361
|
+
calculate a subnet: if the IPv4 object is a host IPv4, the method will
|
362
|
+
calculate the network number for that network and then subnet it. For
|
363
|
+
example:
|
364
|
+
|
365
|
+
ip = IPAddress("172.16.10.58/24")
|
366
|
+
|
367
|
+
ip.subnet(4).map{|i| i.to_s}
|
368
|
+
#=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
|
369
|
+
"172.16.10.192/26"]
|
370
|
+
|
371
|
+
Usually, subnetting implies dividing a network to a number of subnets
|
372
|
+
which is a power of two: in this way, you can be sure that the network
|
373
|
+
will be divived evenly, and all the subnets will have the same number
|
374
|
+
of hosts.
|
375
|
+
|
376
|
+
=== Uneven subnetting
|
377
|
+
|
378
|
+
IPAddress also handles un-even subnetting: if you specify any number
|
379
|
+
(up to the prefix limit), the network will be divided so that the
|
380
|
+
first power-of-two networks will be even, and all the rest will just
|
381
|
+
fill out the space.
|
382
|
+
|
383
|
+
As an example, let's divide network 172.16.10.0/24 into 3 different subnets:
|
384
|
+
|
385
|
+
network = IPAddress("172.16.10.0/24")
|
386
|
+
|
387
|
+
network.subnet(3).map{|i| i.to_s}
|
388
|
+
#=> ["172.16.10.0/26",
|
389
|
+
"172.16.10.64/26",
|
390
|
+
"172.16.10.128/25"]
|
391
|
+
|
392
|
+
We can go even further and divide into 11 subnets:
|
393
|
+
|
394
|
+
network = IPAddress("172.16.10.0/24")
|
395
|
+
|
396
|
+
network.subnet(11).map{|i| i.to_s}
|
397
|
+
#=> ["172.16.10.0/28", "172.16.10.16/28", "172.16.10.32/28",
|
398
|
+
"172.16.10.48/28", "172.16.10.64/28", "172.16.10.80/28",
|
399
|
+
"172.16.10.96/28", "172.16.10.112/28", "172.16.10.128/27",
|
400
|
+
"172.16.10.160/27", "172.16.10.192/26"]
|
401
|
+
|
402
|
+
As you can see, most of the networks are /28, with a few /27 and one
|
403
|
+
/26 to fill up the remaning space.
|
404
|
+
|
405
|
+
=== Summarization
|
406
|
+
|
407
|
+
Summarization (or aggregation) is the process when two or more
|
408
|
+
networks are taken together to check if a supernet, including
|
409
|
+
all and only these networks, exists. If it exists then this supernet
|
410
|
+
is called the summarized (or aggregated) network.
|
411
|
+
It is very important to understand that summarization can only
|
412
|
+
occur if there are no holes in the aggregated network, or, in
|
413
|
+
other words, if the given networks fill completely the address space
|
414
|
+
of the supernet. So the two rules are:
|
415
|
+
|
416
|
+
1) The aggregate network must contain +all+ the IP addresses of the
|
417
|
+
original networks;
|
418
|
+
|
419
|
+
2) The aggregate network must contain +only+ the IP addresses of the
|
420
|
+
original networks;
|
421
|
+
|
422
|
+
A few examples will help clarify the above. Let's consider for
|
423
|
+
instance the following two networks:
|
424
|
+
|
425
|
+
ip1 = IPAddress("172.16.10.0/24")
|
426
|
+
ip2 = IPAddress("172.16.11.0/24")
|
427
|
+
|
428
|
+
These two networks can be expressed using only one IP address
|
429
|
+
network if we change the prefix. Let Ruby do the work:
|
430
|
+
|
431
|
+
IPAddress::IPv4::summarize(ip1,ip2).to_s
|
432
|
+
#=> "172.16.10.0/23"
|
433
|
+
|
434
|
+
We note how the network "172.16.10.0/23" includes all the
|
435
|
+
addresses specified in the above networks, and (more important) includes
|
436
|
+
ONLY those addresses.
|
437
|
+
|
438
|
+
If we summarized +ip1+ and +ip2+ with the following network:
|
439
|
+
|
440
|
+
"172.16.0.0/16"
|
441
|
+
|
442
|
+
we would have satisfied rule #1 above, but not rule #2. So
|
443
|
+
|
444
|
+
"172.16.0.0/16"
|
445
|
+
|
446
|
+
is not an aggregate network for +ip1+ and +ip2+.
|
447
|
+
|
448
|
+
If it's not possible to compute a single aggregated network for
|
449
|
+
all the original networks, the method returns an array with all the
|
450
|
+
aggregate networks found. For example, the following four networks can be
|
451
|
+
aggregated in a single /22:
|
452
|
+
|
453
|
+
ip1 = IPAddress("10.0.0.1/24")
|
454
|
+
ip2 = IPAddress("10.0.1.1/24")
|
455
|
+
ip3 = IPAddress("10.0.2.1/24")
|
456
|
+
ip4 = IPAddress("10.0.3.1/24")
|
457
|
+
|
458
|
+
IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_s
|
459
|
+
#=> "10.0.0.0/22",
|
460
|
+
|
461
|
+
But the following networks can't be summarized in a single
|
462
|
+
network:
|
463
|
+
|
464
|
+
ip1 = IPAddress("10.0.1.1/24")
|
465
|
+
ip2 = IPAddress("10.0.2.1/24")
|
466
|
+
ip3 = IPAddress("10.0.3.1/24")
|
467
|
+
ip4 = IPAddress("10.0.4.1/24")
|
468
|
+
|
469
|
+
IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_s}
|
470
|
+
#=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"]
|
471
|
+
|
472
|
+
In this case, the two summarizables networks have been aggregated into
|
473
|
+
a single /23, while the other two networks have been left untouched.
|
474
|
+
|
475
|
+
=== Supernetting
|
476
|
+
|
477
|
+
Supernetting is a different operation than aggregation, as it only
|
478
|
+
works on a single network and returns a new single IPv4 object,
|
479
|
+
representing the supernet.
|
480
|
+
|
481
|
+
Supernetting is similar to subnetting, except that you getting as a
|
482
|
+
result a network with a smaller prefix (bigger host space). For
|
483
|
+
example, given the network
|
484
|
+
|
485
|
+
ip = IPAddress("172.16.10.0/24")
|
486
|
+
|
487
|
+
you can supernet it with a new /23 prefix
|
488
|
+
|
489
|
+
ip.supernet(23).to_s
|
490
|
+
#=> "172.16.10.0/23"
|
491
|
+
|
492
|
+
However if you supernet it with a /22 prefix, the network address will
|
493
|
+
change:
|
494
|
+
|
495
|
+
ip.supernet(22).to_s
|
496
|
+
#=> "172.16.8.0/22"
|
497
|
+
|
498
|
+
This is because "172.16.10.0/22" is not a network anymore, but an host
|
499
|
+
address.
|
500
|
+
|
501
|
+
|
502
|
+
=IPv6
|
503
|
+
|
504
|
+
IPAddress is not only fantastic for IPv4 addresses, it's also great to
|
505
|
+
handle IPv6 addresses family! Let's discover together how to use it in
|
506
|
+
our projects.
|
507
|
+
|
508
|
+
== IPv6 addresses
|
509
|
+
|
510
|
+
IPv6 addresses are 128 bits long, in contrast with IPv4 addresses
|
511
|
+
which are only 32 bits long. An IPv6 address is generally written as
|
512
|
+
eight groups of four hexadecimal digits, each group representing 16
|
513
|
+
bits or two octect. For example, the following is a valid IPv6
|
514
|
+
address:
|
515
|
+
|
516
|
+
1080:0000:0000:0000:0008:0800:200c:417a
|
517
|
+
|
518
|
+
Letters in an IPv6 address are usually written downcase, as per
|
519
|
+
RFC. You can create a new IPv6 object using uppercase letters, but
|
520
|
+
they will be converted.
|
521
|
+
|
522
|
+
=== Compression
|
523
|
+
|
524
|
+
Since IPv6 addresses are very long to write, there are some
|
525
|
+
semplifications and compressions that you can use to shorten them.
|
526
|
+
|
527
|
+
* Leading zeroes: all the leading zeroes within a group can be
|
528
|
+
omitted: "0008" would become "8"
|
529
|
+
|
530
|
+
* A string of consecutive zeroes can be replaced by the string
|
531
|
+
"::". This can be only applied once.
|
532
|
+
|
533
|
+
Using compression, the IPv6 address written above can be shorten into
|
534
|
+
the following, equivalent, address
|
535
|
+
|
536
|
+
1080::8:800:200c:417a
|
537
|
+
|
538
|
+
This short version is often used in human representation.
|
539
|
+
|
540
|
+
=== Network Mask
|
541
|
+
|
542
|
+
As we used to do with IPv4 addresses, an IPv6 address can be written
|
543
|
+
using the prefix notation to specify the subnet mask:
|
544
|
+
|
545
|
+
1080::8:800:200c:417a/64
|
546
|
+
|
547
|
+
The /64 part means that the first 64 bits of the address are
|
548
|
+
representing the network portion, and the last 64 bits are the host
|
549
|
+
portion.
|
550
|
+
|
551
|
+
== Using IPAddress with IPv6 addresses
|
552
|
+
|
553
|
+
All the IPv6 representations we've just seen are perfectly fine when
|
554
|
+
you want to create a new IPv6 address:
|
555
|
+
|
556
|
+
ip6 = IPAddress "1080:0000:0000:0000:0008:0800:200C:417A"
|
557
|
+
|
558
|
+
ip6 = IPAddress "1080:0:0:0:8:800:200C:417A"
|
559
|
+
|
560
|
+
ip6 = IPAddress "1080::8:800:200C:417A"
|
561
|
+
|
562
|
+
All three are giving out the same IPv6 object. The default subnet mask
|
563
|
+
for an IPv6 is 128, as IPv6 addresses don't have classes like IPv4
|
564
|
+
addresses. If you want a different mask, you can go ahead and explicit
|
565
|
+
it:
|
566
|
+
|
567
|
+
ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
568
|
+
|
569
|
+
Access the address portion and the prefix by using the respective
|
570
|
+
methods:
|
571
|
+
|
572
|
+
ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
573
|
+
|
574
|
+
ip6.address
|
575
|
+
#=> "2001:0db8:0000:0000:0008:0800:200c:417a"
|
576
|
+
|
577
|
+
ip6.prefix
|
578
|
+
#=> 64
|
579
|
+
|
580
|
+
A compressed version of the IPv6 address can be obtained with the
|
581
|
+
IPv6#compressed method:
|
582
|
+
|
583
|
+
ip6 = IPAddress "2001:0db8:0000:0000:0008:200c:417a:00ab/64"
|
584
|
+
|
585
|
+
ip6.compressed
|
586
|
+
#=> "2001:db8::8:800:200c:417a"
|
587
|
+
|
588
|
+
== Handling the IPv6 address
|
589
|
+
|
590
|
+
Accessing the groups that form an IPv6 address is very easy with the
|
591
|
+
IPv6#groups method:
|
592
|
+
|
593
|
+
ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
594
|
+
|
595
|
+
ip6.groups
|
596
|
+
#=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762]
|
597
|
+
|
598
|
+
As with IPv4 addresses, each individual group can be accessed using
|
599
|
+
the IPv6#[] shortcut method:
|
600
|
+
|
601
|
+
ip6[0]
|
602
|
+
#=> 8193
|
603
|
+
ip6[1]
|
604
|
+
#=> 3512
|
605
|
+
ip6[2]
|
606
|
+
#=> 0
|
607
|
+
ip6[3]
|
608
|
+
#=> 0
|
609
|
+
|
610
|
+
Note that each 16 bits group is expressed in its decimal form. You can
|
611
|
+
also obtain the groups into hexadecimal format using the IPv6#hexs
|
612
|
+
method:
|
613
|
+
|
614
|
+
ip6.hexs
|
615
|
+
#=> => ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"]
|
616
|
+
|
617
|
+
A few other methods are available to transform an IPv6 address into
|
618
|
+
decimal representation, with IPv6.to_i
|
619
|
+
|
620
|
+
ip6.to_i
|
621
|
+
#=> 42540766411282592856906245548098208122
|
622
|
+
|
623
|
+
or to hexadecimal representation
|
624
|
+
|
625
|
+
ip6.to_hex
|
626
|
+
#=> "20010db80000000000080800200c417a"
|
627
|
+
|
628
|
+
To print out an IPv6 address in human readable form, use the IPv6#to_s
|
629
|
+
and IPv6#to_string methods
|
630
|
+
|
631
|
+
ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
632
|
+
|
633
|
+
ip6.to_s
|
634
|
+
#=> "2001:db8::8:800:200c:417a/96"
|
635
|
+
|
636
|
+
ip6.to_string
|
637
|
+
#=> "2001:0db8:0000:0000:0008:0800:200c:417a/96"
|
638
|
+
|
639
|
+
As you can see, IPv6.to_s prints out the compressed form, while
|
640
|
+
IPv6.to_string uses the expanded version.
|
641
|
+
|
642
|
+
=== Compressing and uncompressing
|
643
|
+
|
644
|
+
If you have a string representing an IPv6 address, you can easily
|
645
|
+
compress it and uncompress it using the two class methods IPv6::expand
|
646
|
+
and IPv6::compress.
|
647
|
+
|
648
|
+
For example, let's say you have the following uncompressed IPv6
|
649
|
+
address:
|
650
|
+
|
651
|
+
ip6str = "2001:0DB8:0000:CD30:0000:0000:0000:0000"
|
652
|
+
|
653
|
+
Here is the compressed version:
|
654
|
+
|
655
|
+
IPAddress::IPv6.compress ip6str
|
656
|
+
#=> "2001:db8:0:cd30::"
|
657
|
+
|
658
|
+
The other way works as well:
|
659
|
+
|
660
|
+
ip6str = "2001:db8:0:cd30::"
|
661
|
+
|
662
|
+
IPAddress::IPv6.expand ip6str
|
663
|
+
#=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
|
664
|
+
|
665
|
+
These methods can be used when you don't want to create a new object
|
666
|
+
just for expanding or compressing an address (although a new object is
|
667
|
+
actually created internally).
|
668
|
+
|
669
|
+
== New IPv6 address from other formats
|
670
|
+
|
671
|
+
You can create a new IPv6 address from different formats than just a
|
672
|
+
string representing the colon-hex groups.
|
673
|
+
|
674
|
+
For instance, if you have a data stream, you can use IPv6::parse_data,
|
675
|
+
like in the following example:
|
676
|
+
|
677
|
+
data = " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
|
678
|
+
|
679
|
+
ip6 = IPAddress::IPv6::parse_data data
|
680
|
+
ip6.prefix = 64
|
681
|
+
|
682
|
+
ip6.to_s
|
683
|
+
#=> "2001:db8::8:800:200c:417a/64"
|
684
|
+
|
685
|
+
A new IPv6 address can also be created from an unsigned 128 bits
|
686
|
+
integer:
|
687
|
+
|
688
|
+
u128 = 21932261930451111902915077091070067066
|
689
|
+
|
690
|
+
ip6 = IPAddress::IPv6::parse_u128 u128
|
691
|
+
ip6.prefix = 64
|
692
|
+
|
693
|
+
ip6.to_s
|
694
|
+
#=> "1080::8:800:200c:417a/64"
|
695
|
+
|
696
|
+
Finally, a new IPv6 address can be created from an hex string:
|
697
|
+
|
698
|
+
hex = "20010db80000000000080800200c417a"
|
699
|
+
|
700
|
+
ip6 = IPAddress::IPv6::parse_hex hex
|
701
|
+
ip6.prefix = 64
|
702
|
+
|
703
|
+
ip6.to_s
|
704
|
+
#=> "2001:db8::8:800:200c:417a/64"
|
705
|
+
|
706
|
+
== Special IPv6 addresses
|
707
|
+
|
708
|
+
Some IPv6 have a special meaning and are expressed in a special form,
|
709
|
+
quite different than an usual IPv6 address. IPAddress has builtin
|
710
|
+
support for unspecified, loopback and mapped IPv6 addresses.
|
711
|
+
|
712
|
+
=== Unspecified address
|
713
|
+
|
714
|
+
The address with all zero bits is called the +unspecified+ address
|
715
|
+
(corresponding to 0.0.0.0 in IPv4). It should be something like this:
|
716
|
+
|
717
|
+
0000:0000:0000:0000:0000:0000:0000:0000
|
718
|
+
|
719
|
+
but, with the use of compression, it is usually written as just two
|
720
|
+
colons:
|
721
|
+
|
722
|
+
::
|
723
|
+
|
724
|
+
or, specifying the netmask:
|
725
|
+
|
726
|
+
::/128
|
727
|
+
|
728
|
+
With IPAddress, create a new unspecified IPv6 address using its own
|
729
|
+
subclass:
|
730
|
+
|
731
|
+
ip = IPAddress::IPv6::Unspecified.new
|
732
|
+
|
733
|
+
ip.to_s
|
734
|
+
#=> => "::/128"
|
735
|
+
|
736
|
+
You can easily check if an IPv6 object is an unspecified address by
|
737
|
+
using the IPv6#unspecified? method
|
738
|
+
|
739
|
+
ip.unspecified?
|
740
|
+
#=> true
|
741
|
+
|
742
|
+
An unspecified IPv6 address can also be created with the wrapper
|
743
|
+
method, like we've seen before
|
744
|
+
|
745
|
+
ip = IPAddress "::"
|
746
|
+
|
747
|
+
ip.unspecified?
|
748
|
+
#=> true
|
749
|
+
|
750
|
+
This address must never be assigned to an interface and is to be used
|
751
|
+
only in software before the application has learned its host's source
|
752
|
+
address appropriate for a pending connection. Routers must not forward
|
753
|
+
packets with the unspecified address.
|
754
|
+
|
755
|
+
=== Loopback address
|
756
|
+
|
757
|
+
The loopback address is a unicast localhost address. If an
|
758
|
+
application in a host sends packets to this address, the IPv6 stack
|
759
|
+
will loop these packets back on the same virtual interface.
|
760
|
+
|
761
|
+
Loopback addresses are expressed in the following form:
|
762
|
+
|
763
|
+
::1
|
764
|
+
|
765
|
+
or, with their appropriate prefix,
|
766
|
+
|
767
|
+
::1/128
|
768
|
+
|
769
|
+
As for the unspecified addresses, IPv6 loopbacks can be created with
|
770
|
+
IPAddress calling their own class:
|
771
|
+
|
772
|
+
ip = IPAddress::IPv6::Loopback.new
|
773
|
+
|
774
|
+
ip.to_s
|
775
|
+
#=> "::1/128"
|
776
|
+
|
777
|
+
or by using the wrapper:
|
778
|
+
|
779
|
+
ip = IPAddress "::1"
|
780
|
+
|
781
|
+
ip.to_s
|
782
|
+
#=> "::1/128"
|
783
|
+
|
784
|
+
Checking if an address is loopback is easy with the IPv6#loopback?
|
785
|
+
method:
|
786
|
+
|
787
|
+
ip.loopback?
|
788
|
+
#=> true
|
789
|
+
|
790
|
+
The IPv6 loopback address corresponds to 127.0.0.1 in IPv4.
|
791
|
+
|
792
|
+
=== Mapped address
|
793
|
+
|
794
|
+
It is usually identified as a IPv4 mapped IPv6 address, a particular
|
795
|
+
IPv6 address which aids the transition from IPv4 to IPv6. The
|
796
|
+
structure of the address is
|
797
|
+
|
798
|
+
::ffff:w.y.x.z
|
799
|
+
|
800
|
+
where w.x.y.z is a normal IPv4 address. For example, the following is
|
801
|
+
a mapped IPv6 address:
|
802
|
+
|
803
|
+
::ffff:192.168.100.1
|
804
|
+
|
805
|
+
IPAddress is very powerful in handling mapped IPv6 addresses, as the
|
806
|
+
IPv4 portion is stored internally as a normal IPv4 object. Let's have
|
807
|
+
a look at some examples. To create a new mapped address, just use the
|
808
|
+
class builder itself
|
809
|
+
|
810
|
+
ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
|
811
|
+
|
812
|
+
or just use the wrapper method
|
813
|
+
|
814
|
+
ip6 = IPAddress "::ffff:172.16.10.1/128"
|
815
|
+
|
816
|
+
Let's check it's really a mapped address:
|
817
|
+
|
818
|
+
ip6.mapped?
|
819
|
+
#=> true
|
820
|
+
|
821
|
+
ip6.to_s
|
822
|
+
#=> "::FFFF:172.16.10.1/128"
|
823
|
+
|
824
|
+
Now with the +ipv4+ attribute, we can easily access the IPv4 portion
|
825
|
+
of the mapped IPv6 address:
|
826
|
+
|
827
|
+
ip6.ipv4.address
|
828
|
+
#=> "172.16.10.1"
|
829
|
+
|
830
|
+
Internally, the IPv4 address is stored as two 16 bits
|
831
|
+
groups. Therefore all the usual methods for an IPv6 address are
|
832
|
+
working perfectly fine:
|
833
|
+
|
834
|
+
ip6.to_hex
|
835
|
+
#=> "00000000000000000000ffffac100a01"
|
836
|
+
|
837
|
+
ip6.address
|
838
|
+
#=> "0000:0000:0000:0000:0000:ffff:ac10:0a01"
|
839
|
+
|
840
|
+
A mapped IPv6 can also be created just by specify the address in the
|
841
|
+
following format:
|
842
|
+
|
843
|
+
ip6 = IPAddress "::172.16.10.1"
|
844
|
+
|
845
|
+
That is, two colons and the IPv4 address. However, as by RFC, the ffff
|
846
|
+
group will be automatically added at the beginning
|
847
|
+
|
848
|
+
ip6.to_s
|
849
|
+
=> "::ffff:172.16.10.1/128"
|
850
|
+
|
851
|
+
making it a mapped IPv6 compatible address.
|
852
|
+
|
853
|
+
= Future versions:
|
854
|
+
|
855
|
+
Some new features we'd like to add in the future versions:
|
856
|
+
|
857
|
+
* support for wildmasks
|
858
|
+
* network design methods for IPv6
|
859
|
+
* parameter to IPv4#subnet to select where to fill the space
|
860
|
+
(beginning or ending)
|
861
|
+
* method to check if a network is private
|
862
|
+
|
863
|
+
Stay tuned!
|
864
|
+
|
865
|
+
= Thanks to
|
866
|
+
|
867
|
+
Thanks to Luca Russo (vargolo) for all the support and technical
|
868
|
+
review.
|
869
|
+
|
870
|
+
= Copyright
|
871
|
+
|
872
|
+
Copyright (c) 2009-2010 Marco Ceresa. See LICENSE for details.
|
873
|
+
|
874
|
+
|
875
|
+
|