ipaddr_extensions 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 833c7f6ca4de6a0bf24cec680c7d9a9530d59f2f
4
+ data.tar.gz: 38ed969a1184ea1c8b0f39ab8b0320a6a7a75629
5
+ SHA512:
6
+ metadata.gz: 82be92d53fbe46c35ad1ae487af3e8fc6dd2e89a43b7cf95d89b649df3585dca1ad5c61a6538a3cc6ca0ccee4cedde5630f3560f07ae4fe74bb3b88f588a60c8
7
+ data.tar.gz: dd11ed1b3fde478c5d4a209a035436fa75111dbf3b7022b1b0e98e05275e83112beab409c144f1201fcadcfb923aff1450b604a8a14cce5f9f6a167534f57136
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2010 Justin French
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 ADDED
@@ -0,0 +1,55 @@
1
+ IPAddrExtensions
2
+ ================
3
+
4
+ A selection of potentially useful extensions for IPAddr.
5
+
6
+ Example
7
+ =======
8
+
9
+ >> IPAddr.new("192.0.2.0/24").length
10
+ => 24
11
+ >> IPAddr.new("192.0.2.0/24").first
12
+ => #<IPAddr: IPv4:192.0.2.0/255.255.255.255>
13
+ >> IPAddr.new("192.0.2.0/24").last
14
+ => #<IPAddr: IPv4:192.0.2.255/255.255.255.255>
15
+ >> IPAddr.new("192.0.2.0/24").scope
16
+ => "DOCUMENTATION"
17
+ >> IPAddr.new("192.0.2.0/24").local?
18
+ => false
19
+ >> IPAddr.new("192.0.2.0/24").unicast?
20
+ => false
21
+ >> IPAddr.new("192.0.2.0/24").multicast?
22
+ => false
23
+ >> IPAddr.new("192.0.2.0/24").link?
24
+ => false
25
+ >> IPAddr.new("192.0.2.0/24").documentation?
26
+ => true
27
+ >> IPAddr.new("192.0.2.0/24").loopback?
28
+ => false
29
+ >> IPAddr.new("192.0.2.0/24").global?
30
+ => false
31
+ >> IPAddr.new("192.0.2.0/24").private?
32
+ => false
33
+ >> IPAddr.new("192.0.2.0/24").space
34
+ => 256
35
+ >> IPAddr.new("192.0.2.0/24").reverses
36
+ => ["192.0.2.in-addr.arpa"]
37
+ >> IPAddr.new("192.0.2.0/24").host?
38
+ => false
39
+ >> IPAddr.new("192.0.2.0/24").prefix?
40
+ => true
41
+ >> IPAddr.new("2001:db8::/64").eui_64('00:0D:60:0F:3C:A8')
42
+ => #<IPAddr: IPv6:2001:0db8:0000:0000:020d:60ff:fe0f:3ca8/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>
43
+ >> IPAddr.new("2001:db8::/56").reverses
44
+ => ["0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "2.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "3.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "4.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "5.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "6.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "7.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "8.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "9.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "a.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "b.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "c.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "d.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "e.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "f.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"]
45
+ >> "192.0.2.0/24".to_ip
46
+ => #<IPAddr: IPv4:192.0.2.0/255.255.255.0>
47
+ >> 12345.to_ip
48
+ => #<IPAddr: IPv4:0.0.48.57/255.255.255.255>
49
+ >> 12345.to_ip(Socket::AF_INET6)
50
+ => #<IPAddr: IPv6:0000:0000:0000:0000:0000:0000:0000:3039/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>
51
+ >> ("2001:db8::/32".to_ip / 16).collect { |prefix| prefix.to_string_including_length }
52
+ => ["2001:db8::/36", "2001:db8:1000::/36", "2001:db8:2000::/36", "2001:db8:3000::/36", "2001:db8:4000::/36", "2001:db8:5000::/36", "2001:db8:6000::/36", "2001:db8:7000::/36", "2001:db8:8000::/36", "2001:db8:9000::/36", "2001:db8:a000::/36", "2001:db8:b000::/36", "2001:db8:c000::/36", "2001:db8:d000::/36", "2001:db8:e000::/36", "2001:db8:f000::/36"]
53
+
54
+
55
+ Copyright (c) 2010,2011 James Harto, Sociable Limited, released under the Mozilla Public License.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'ipaddr_extensions'
6
+ s.version = '1.0.0'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["James Harton"]
9
+ s.email = %q{james@resistor.io}
10
+ s.homepage = %q{http://github.com/jamesotron/IPAddrExtensions}
11
+ s.summary = %q{A small gem that adds extra functionality to Rubys IPAddr class}
12
+
13
+ s.files = ["MIT-LICENSE", "README", "Rakefile", "ipaddr_extensions.gemspec"] + Dir["lib/**/*"]
14
+ s.require_paths = ["lib"]
15
+
16
+ s.add_development_dependency 'rake'
17
+ end
@@ -0,0 +1,719 @@
1
+ require 'ipaddr'
2
+ require 'scanf'
3
+ require 'digest/sha1'
4
+
5
+ module IPAddrExtensions
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ base.class_eval do
10
+ alias_method :mask_without_a_care!, :mask!
11
+ alias_method :mask!, :mask_with_a_care!
12
+ end
13
+ end
14
+
15
+ # Return the bit length of the prefix
16
+ # ie:
17
+ # IPAddr.new("2001:db8::/32").length
18
+ # => 32
19
+ # IPAddr.new("192.0.2.0/255.255.255.0").length
20
+ # => 24
21
+ def length
22
+ # nasty hack, but works well enough.
23
+ @mask_addr.to_s(2).count("1")
24
+ end
25
+
26
+ # Modify the bit length of the prefix
27
+ def length=(length)
28
+ if self.ipv4?
29
+ @mask_addr=((1<<32)-1) - ((1<<32-length)-1)
30
+ elsif self.ipv6?
31
+ @mask_addr=((1<<128)-1) - ((1<<128-length)-1)
32
+ end
33
+ end
34
+
35
+ # Return an old-style subnet mask
36
+ # ie:
37
+ # IPAddr.new("2001:db8::/32").subnet_mask
38
+ # => #<IPAddr: IPv6:ffff:ffff:0000:0000:0000:0000:0000:0000/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>
39
+ # IPAddr.new("192.0.2.0/255.255.255.0").subnet_mask
40
+ # => #<IPAddr: IPv4:255.255.255.0/255.255.255.255>
41
+ def subnet_mask
42
+ @mask_addr.to_ip
43
+ end
44
+
45
+ # Return a "cisco style" subnet mask for use in ACLs:
46
+ #
47
+ # IPAddr.new("2001:db8::/32").wildcard_mask
48
+ # => #<IPAddr: IPv6:0000:0000:ffff:ffff:ffff:ffff:ffff:ffff/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>
49
+ # IPAddr.new("192.0.2.0/255.255.255.0").wildcard_mask
50
+ # => #<IPAddr: IPv4:0.0.0.255/255.255.255.255>
51
+ def wildcard_mask
52
+ if self.ipv4?
53
+ (@mask_addr ^ IPAddr::IN4MASK).to_ip
54
+ else
55
+ (@mask_addr ^ IPAddr::IN6MASK).to_ip
56
+ end
57
+ end
58
+
59
+ # Retrieve the first address in this prefix
60
+ # (called a network address in IPv4 land)
61
+ def first
62
+ IPAddr.new(@addr & @mask_addr, @family)
63
+ end
64
+
65
+ # Retrieve the last address in this prefix
66
+ # (called a broadcast address in IPv4 land)
67
+ def last
68
+ if @family == Socket::AF_INET
69
+ IPAddr.new(first.to_i | (@mask_addr ^ IPAddr::IN4MASK), @family)
70
+ elsif @family == Socket::AF_INET6
71
+ IPAddr.new(first.to_i | (@mask_addr ^ IPAddr::IN6MASK), @family)
72
+ else
73
+ raise "unsupported address family."
74
+ end
75
+ end
76
+
77
+ # Return an EUI-64 host address for the current
78
+ # prefix (must be a 64 bit long IPv6 prefix).
79
+ def eui_64(mac)
80
+ if @family != Socket::AF_INET6
81
+ raise Exception, "EUI-64 only makes sense on IPv6 prefixes."
82
+ elsif self.length != 64
83
+ raise Exception, "EUI-64 only makes sense on 64 bit IPv6 prefixes."
84
+ end
85
+ if mac.is_a? Integer
86
+ mac = "%:012x" % mac
87
+ end
88
+ if mac.is_a? String
89
+ mac.gsub!(/[^0-9a-fA-F]/, "")
90
+ if mac.match(/^[0-9a-f]{12}/).nil?
91
+ raise ArgumentError, "Second argument must be a valid MAC address."
92
+ end
93
+ e64 = (mac[0..5] + "fffe" + mac[6..11]).to_i(16) ^ 0x0200000000000000
94
+ IPAddr.new(self.first.to_i + e64, Socket::AF_INET6)
95
+ end
96
+ end
97
+
98
+ def eui_64?
99
+ if @family != Socket::AF_INET6
100
+ raise Exception, "EUI-64 only makes sense on IPv6 prefixes."
101
+ #elsif self.length != 64
102
+ # raise Exception, "EUI-64 only makes sense on 64 bit IPv6 prefixes."
103
+ end
104
+ (self.to_i & 0x20000fffe000000) == 0x20000fffe000000
105
+ end
106
+
107
+ def mac
108
+ if eui_64?
109
+ network_bits = self.to_i & 0xffffffffffffffff
110
+ top_chunk = network_bits >> 40
111
+ bottom_chunk = network_bits & 0xffffff
112
+ mac = ((top_chunk << 24) + bottom_chunk) ^ 0x20000000000
113
+ result = []
114
+ 5.downto(0).each do |i|
115
+ result << sprintf("%02x", (mac >> i * 8) & 0xff)
116
+ end
117
+ result * ':'
118
+ end
119
+ end
120
+
121
+ # Call the original mask! method but don't allow it
122
+ # to change the internally stored address, since we
123
+ # might actually need that.
124
+ def mask_with_a_care!(mask)
125
+ original_addr = @addr
126
+ mask_without_a_care!(mask)
127
+ @addr = original_addr unless self.class.mask_by_default
128
+ return self
129
+ end
130
+
131
+ MSCOPES = {
132
+ 1 => "INTERFACE LOCAL MULTICAST",
133
+ 2 => "LINK LOCAL MULTICAST",
134
+ 4 => "ADMIN LOCAL MULTICAST",
135
+ 5 => "SITE LOCAL MULTICAST",
136
+ 8 => "ORGANISATION LOCAL MULTICAST",
137
+ 0xe => "GLOBAL MULTICAST"
138
+ }
139
+
140
+ MDESTS = {
141
+ 1 => "ALL NODES",
142
+ 2 => "ALL ROUTERS",
143
+ 3 => "ALL DHCP SERVERS",
144
+ 4 => "DVMRP ROUTERS",
145
+ 5 => "OSPFIGP",
146
+ 6 => "OSPFIGP DESIGNATED ROUTERS",
147
+ 7 => "ST ROUTERS",
148
+ 8 => "ST HOSTS",
149
+ 9 => "RIP ROUTERS",
150
+ 0xa => "EIGRP ROUTERS",
151
+ 0xb => "MOBILE-AGENTS",
152
+ 0xc => "SSDP",
153
+ 0xd => "ALL PIM ROUTERS",
154
+ 0xe => "RSVP ENCAPSULATION",
155
+ 0xf => "UPNP",
156
+ 0x16 => "ALL MLDV2 CAPABLE ROUTERS",
157
+ 0x6a => "ALL SNOOPERS",
158
+ 0x6b => "PTP-PDELAY",
159
+ 0x6c => "SARATOGA",
160
+ 0x6d => "LL MANET ROUTERS",
161
+ 0xfb => "MDNSV6",
162
+ 0x100 => "VMTP MANAGERS GROUP",
163
+ 0x101 => "NTP",
164
+ 0x102 => "SGI-DOGFIGHT",
165
+ 0x103 => "RWHOD",
166
+ 0x104 => "VNP",
167
+ 0x105 => "ARTIFICIAL HORIZONS",
168
+ 0x106 => "NSS",
169
+ 0x107 => "AUDIONEWS",
170
+ 0x108 => "SUN NIS+",
171
+ 0x109 => "MTP",
172
+ 0x10a => "IETF-1-LOW-AUDIO",
173
+ 0x10b => "IETF-1-AUDIO",
174
+ 0x10c => "IETF-1-VIDEO",
175
+ 0x10d => "IETF-2-LOW-AUDIO",
176
+ 0x10e => "IETF-2-AUDIO",
177
+ 0x10f => "IETF-2-VIDEO",
178
+ 0x110 => "MUSIC-SERVICE",
179
+ 0x111 => "SEANET-TELEMETRY",
180
+ 0x112 => "SEANET-IMAGE",
181
+ 0x113 => "MLOADD",
182
+ 0x114 => "ANY PRIVATE EXPERIMENT",
183
+ 0x115 => "DVMRP on MOSPF",
184
+ 0x116 => "SVRLOC",
185
+ 0x117 => "XINGTV",
186
+ 0x118 => "MICROSOFT-DS",
187
+ 0x119 => "NBC-PRO",
188
+ 0x11a => "NBC-PFN",
189
+ 0x10001 => "LINK NAME",
190
+ 0x10002 => "ALL DHCP AGENTS",
191
+ 0x10003 => "LINK LOCAL MULTICAST NAME",
192
+ 0x10004 => "DTCP ANNOUNCEMENT",
193
+ }
194
+
195
+ # Returns a string describing the scope of the
196
+ # address.
197
+ def scope
198
+ if @family == Socket::AF_INET
199
+ if IPAddr.new("0.0.0.0/8").include? self
200
+ "CURRENT NETWORK"
201
+ elsif IPAddr.new("10.0.0.0/8").include? self
202
+ "RFC1918 PRIVATE"
203
+ elsif IPAddr.new("127.0.0.0/8").include? self
204
+ "LOOPBACK"
205
+ elsif IPAddr.new("168.254.0.0/16").include? self
206
+ "AUTOCONF PRIVATE"
207
+ elsif IPAddr.new("172.16.0.0/12").include? self
208
+ "RFC1918 PRIVATE"
209
+ elsif IPAddr.new("192.0.0.0/24").include? self
210
+ "RESERVED (IANA)"
211
+ elsif IPAddr.new("192.0.2.0/24").include? self
212
+ "DOCUMENTATION"
213
+ elsif IPAddr.new("192.88.99.0/24").include? self
214
+ "6to4 ANYCAST"
215
+ elsif IPAddr.new("192.168.0.0/16").include? self
216
+ "RFC1918 PRIVATE"
217
+ elsif IPAddr.new("198.18.0.0/15").include? self
218
+ "NETWORK BENCHMARK TESTS"
219
+ elsif IPAddr.new("198.51.100.0/24").include? self
220
+ "DOCUMENTATION"
221
+ elsif IPAddr.new("203.0.113.0/24").include? self
222
+ "DOCUMENTATION"
223
+ elsif IPAddr.new("224.0.0.0/4").include? self
224
+ if IPAddr.new("239.0.0.0/8").include? self
225
+ "LOCAL MULTICAST"
226
+ else
227
+ "GLOBAL MULTICAST"
228
+ end
229
+ elsif IPAddr.new("240.0.0.0/4").include? self
230
+ "RESERVED"
231
+ elsif IPAddr.new("255.255.255.255") == self
232
+ "GLOBAL BROADCAST"
233
+ else
234
+ "GLOBAL UNICAST"
235
+ end
236
+ elsif @family == Socket::AF_INET6
237
+ if IPAddr.new("2000::/3").include? self
238
+ require 'scanf'
239
+ if is_6to4?
240
+ "GLOBAL UNICAST (6to4: #{from_6to4})"
241
+ elsif is_teredo?
242
+ "GLOBAL UNICAST (Teredo #{from_teredo[:client].to_s}:#{from_teredo[:port].to_s} -> #{from_teredo[:server].to_s}:#{from_teredo[:port].to_s})"
243
+ elsif IPAddr.new("2001:10::/28").include? self
244
+ "ORCHID"
245
+ elsif IPAddr.new("2001:db8::/32").include? self
246
+ "DOCUMENTATION"
247
+ else
248
+ "GLOBAL UNICAST"
249
+ end
250
+ elsif IPAddr.new("::/128") == self
251
+ "UNSPECIFIED ADDRESS"
252
+ elsif IPAddr.new("::1/128") == self
253
+ "LINK LOCAL LOOPBACK"
254
+ elsif IPAddr.new("::ffff:0:0/96").include? self
255
+ a,b,c,d = self.to_string.scanf("%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%4x:%4x:%4x:%4x")
256
+ "IPv4 MAPPED (#{a.to_s}.#{b.to_s}.#{c.to_s}.#{d.to_s})"
257
+ elsif IPAddr.new("::/96").include? self
258
+ a,b,c,d = self.to_string.scanf("%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%*4x:%4x:%4x:%4x:%4x")
259
+ "IPv4 TRANSITION (#{a.to_s}.#{b.to_s}.#{c.to_s}.#{d.to_s}, deprecated)"
260
+ elsif IPAddr.new("fc00::/7").include? self
261
+ "UNIQUE LOCAL UNICAST"
262
+ elsif IPAddr.new("fec0::/10").include? self
263
+ "SITE LOCAL (deprecated)"
264
+ elsif IPAddr.new("fe80::/10").include? self
265
+ "LINK LOCAL UNICAST"
266
+ elsif IPAddr.new("ff00::/8").include? self
267
+ mscope,mdesta,mdestb = self.to_string.scanf("%*1x%*1x%*1x%1x:%*4x:%*4x:%*4x:%*4x:%*4x:%4x:%4x")
268
+ mdest = (mdesta << 16) + mdestb
269
+ s = "MULTICAST"
270
+ if MSCOPES[mscope]
271
+ s += " #{MSCOPES[mscope]}"
272
+ end
273
+ if MDESTS[mdest]
274
+ s += " #{MDEST[mdest]}"
275
+ end
276
+ if multicast_from_prefix?
277
+ s += " (prefix = #{prefix_from_multicast.to_string_including_length})"
278
+ end
279
+ s
280
+ else
281
+ "RESERVED"
282
+ end
283
+ end
284
+ end
285
+
286
+ # Some scope tests
287
+ def local?
288
+ self.scope.split(' ').member? 'LOCAL'
289
+ end
290
+ def unicast?
291
+ !self.scope.split(' ').any? { |scope| ['BROADCAST', 'MULTICAST'].member? scope }
292
+ end
293
+ def multicast?
294
+ self.scope.split(' ').member? 'MULTICAST'
295
+ end
296
+ def link?
297
+ self.scope.split(' ').member? 'LINK'
298
+ end
299
+ def documentation?
300
+ self.scope.split(' ').member? 'DOCUMENTATION'
301
+ end
302
+ def loopback?
303
+ self.scope.split(' ').member? 'LOOPBACK'
304
+ end
305
+ def global?
306
+ self.scope.split(' ').member? 'GLOBAL'
307
+ end
308
+ def private?
309
+ self.scope.split(' ').member? 'PRIVATE'
310
+ end
311
+ def multicast_from_prefix?
312
+ ipv6? && ('ff00::/8'.to_ip.include? self) && ((self.to_i >> 116) & 0x03 == 3)
313
+ end
314
+
315
+ # Returns the original prefix a Multicast address was generated from
316
+ # see RFC3306
317
+ def prefix_from_multicast
318
+ if ipv6? && multicast_from_prefix?
319
+ prefix_length = (to_i >> 92) & 0xff
320
+ if (prefix_length == 0xff) && (((to_i >> 112) & 0xf) >= 2)
321
+ # Link local prefix
322
+ #(((to_i >> 32) & 0xffffffffffffffff) + (0xfe80 << 112)).to_ip(Socket::AF_INET6).tap { |p| p.length = 64 }
323
+ return nil # See http://redmine.ruby-lang.org/issues/5468
324
+ else
325
+ # Global unicast prefix
326
+ (((to_i >> 32) & 0xffffffffffffffff) << 64).to_ip(Socket::AF_INET6).tap { |p| p.length = prefix_length }
327
+ end
328
+ end
329
+ end
330
+
331
+ # Convert an IPv4 address into an IPv6
332
+ # 6to4 address.
333
+ def to_6to4
334
+ if @family == Socket::AF_INET
335
+ IPAddr.new((0x2002 << 112) + (@addr << 80), Socket::AF_INET6).tap { |p| p.length = 48 }
336
+ end
337
+ end
338
+
339
+ # Return the space available inside this prefix
340
+ def space
341
+ self.last.to_i - self.first.to_i + 1
342
+ end
343
+
344
+ # Return usable address space inside this prefix
345
+ def usable
346
+ if ipv6?
347
+ space
348
+ else
349
+ space - 2
350
+ end
351
+ end
352
+
353
+ # Return likely reverse zones for the Address or prefix
354
+ # (differs from reverse() because it will return the correct
355
+ # number of zones to adequately delegate the prefix).
356
+ def reverses
357
+ if @family == Socket::AF_INET
358
+ if self.length == 32
359
+ [ self.reverse ]
360
+ else
361
+ boundary = self.length % 8 == 0 && self.length != 0 ? self.length / 8 - 1 : self.length / 8
362
+ divisor = (boundary + 1) * 8
363
+ count = (self.last.to_i - self.first.to_i) / (1 << 32 - divisor)
364
+ res = []
365
+ (0..count).each do |i|
366
+ octets = IPAddr.new(first.to_i + ((1<<32-divisor)*i), Socket::AF_INET).to_s.split('.')[0..boundary]
367
+ res << "#{octets.reverse * '.'}.in-addr.arpa"
368
+ end
369
+ res
370
+ end
371
+ elsif @family == Socket::AF_INET6
372
+ if self.length == 128
373
+ [ self.reverse ]
374
+ else
375
+ boundary = self.length % 16 == 0 && self.length != 0 ? self.length / 4 - 1 : self.length / 4
376
+ divisor = (boundary + 1) * 4
377
+ count = (self.last.to_i - self.first.to_i) / (1 << 128-divisor)
378
+ res = []
379
+ (0..count).each do |i|
380
+ baseaddr = self.first.to_i + (1<<128-divisor)*i
381
+ octets = ("%032x" % baseaddr).split('')[0..boundary]
382
+ res << octets.reverse * '.' + '.ip6.arpa'
383
+ end
384
+ res
385
+ end
386
+ end
387
+ end
388
+
389
+ # Extra quick tests
390
+ def host?
391
+ (@family == Socket::AF_INET && self.length == 32) ||
392
+ (@family == Socket::AF_INET6 && self.length == 128)
393
+ end
394
+
395
+ def prefix?
396
+ !self.host?
397
+ end
398
+
399
+ def to_string_including_length
400
+ if host?
401
+ to_s
402
+ else
403
+ "#{to_s}/#{length.to_s}"
404
+ end
405
+ end
406
+
407
+ alias bitmask length
408
+
409
+ def /(by)
410
+ if self.ipv4?
411
+ space = 1 << 32 - length
412
+ if space % by == 0
413
+ newmask = (((1<<32)-1) ^ (space/by-1)).to_s(2).count("1")
414
+ (0..by-1).collect do |i|
415
+ ip = (self.to_i + ((1 << 32 - newmask)*i)).to_ip(Socket::AF_INET)
416
+ ip.length = newmask
417
+ ip
418
+ end
419
+ else
420
+ raise ArgumentError.new "Cannot evenly devide by #{by}"
421
+ end
422
+ elsif self.ipv6?
423
+ space = 1 << 128 - length
424
+ if space % by == 0
425
+ newmask = (((1<<128)-1) ^ (space/by-1)).to_s(2).count("1")
426
+ (0..by-1).collect do |i|
427
+ ip = (self.to_i + ((1 << 128 - newmask)*i)).to_ip(Socket::AF_INET6)
428
+ ip.length = newmask
429
+ ip
430
+ end
431
+ else
432
+ raise ArgumentError.new "Cannot evenly devide by #{by}"
433
+ end
434
+ end
435
+ end
436
+
437
+ def is_teredo?
438
+ IPAddr.new("2001::/32").include? self
439
+ end
440
+
441
+ def from_teredo
442
+ is_teredo? && { :server => IPAddr.new((@addr >> 64) & ((1<<32)-1), Socket::AF_INET), :client => IPAddr.new((@addr & ((1<<32)-1)) ^ ((1<<32)-1), Socket::AF_INET), :port => ((@addr >> 32) & ((1<<16)-1)) }
443
+ end
444
+
445
+ def is_6to4?
446
+ IPAddr.new("2002::/16").include? self
447
+ end
448
+ def from_6to4
449
+ x = self.to_string.scanf("%*4x:%4x:%4x:%s")
450
+ IPAddr.new((x[0]<<16)+x[1], Socket::AF_INET)
451
+ end
452
+
453
+ module ClassMethods
454
+
455
+ # By default IPAddr masks a non all-ones prefix so that the
456
+ # "network address" is all that's stored. This loses data
457
+ # for some applications and isn't really necessary since
458
+ # anyone expecting that should use #first instead.
459
+ # This defaults to on to retain compatibility with the
460
+ # rubycore IPAddr class.
461
+ def mask_by_default
462
+ # You can't use ||= for bools.
463
+ if @mask_by_default.nil?
464
+ @mask_by_default = true
465
+ end
466
+ @mask_by_default
467
+ end
468
+ def mask_by_default=(x)
469
+ @mask_by_default = !!x
470
+ end
471
+
472
+ # Generate an IPv6 Unique Local Address using the supplied system MAC address.
473
+ # Note that the MAC address is just used as a source of randomness, so where you
474
+ # get it from is not important and doesn't restrict this ULA to just that system.
475
+ # See RFC4193
476
+ def generate_ULA(mac, subnet_id = 0, locally_assigned=true)
477
+ now = Time.now.utc
478
+ ntp_time = ((now.to_i + 2208988800) << 32) + now.nsec # Convert time to an NTP timstamp.
479
+ system_id = '::/64'.to_ip.eui_64(mac).to_i # Generate an EUI64 from the provided MAC address.
480
+ key = [ ntp_time, system_id ].pack('QQ') # Pack the ntp timestamp and the system_id into a binary string
481
+ global_id = Digest::SHA1.digest( key ).unpack('QQ').last & 0xffffffffff # Use only the last 40 bytes of the SHA1 digest.
482
+
483
+ prefix =
484
+ (126 << 121) + # 0xfc (bytes 0..6)
485
+ ((locally_assigned ? 1 : 0) << 120) + # locally assigned? (byte 7)
486
+ (global_id << 80) + # 40 bit global idenfitier (bytes 8..48)
487
+ ((subnet_id & 0xffff) << 64) # 16 bit subnet_id (bytes 48..64)
488
+
489
+ prefix.to_ip(Socket::AF_INET6).tap { |p| p.length = 64 }
490
+ end
491
+
492
+ end
493
+
494
+ end
495
+
496
+ module StringIPExtensions
497
+ def to_ip
498
+ begin
499
+ IPAddr.new(self.to_s)
500
+ rescue ArgumentError => e
501
+ raise ArgumentError, "invalid address #{self.inspect}"
502
+ end
503
+ end
504
+ end
505
+ module IntIPExtensions
506
+ def to_ip(af=nil)
507
+ if af.nil?
508
+ ## If there is no address family specified then try to guess...
509
+ if self.to_i > 0xffffffff
510
+ # If the integer is bigger than any possible IPv4 address
511
+ # then presume it's an IPv6 address
512
+ af = Socket::AF_INET6
513
+ else
514
+ # otherwise presume it's IPv4
515
+ af = Socket::AF_INET
516
+ end
517
+ end
518
+ IPAddr.new(self.to_i, af)
519
+ end
520
+ end
521
+
522
+ class IPTuple
523
+
524
+ attr_accessor :source_ip, :destination_ip, :ip_protocol, :source_port, :destination_port
525
+
526
+ def initialize src_ip=nil, dst_ip=nil, ip_proto=nil, src_port=nil, dst_port=nil
527
+ @source_ip = src_ip if src_ip.is_a? IPAddr
528
+ @source_ip = src_ip.to_ip if src_ip.is_a? String
529
+ @destination_ip = dst_ip if dst_ip.is_a? IPAddr
530
+ @destination_ip = dst_ip.to_ip if dst_ip.is_a? String
531
+ @ip_protocol = ip_proto if ip_proto.is_a? IPProtocol
532
+ @ip_protocol = IPProtocol.new(ip_proto) if ip_proto.is_a? Integer
533
+ @source_port = src_port if ((1..65535).include? src_port)
534
+ @destination_port = dst_port if ((1..65535).include? dst_port)
535
+ end
536
+
537
+ def ipv4?
538
+ if @source_ip && @destination_ip
539
+ @source_ip.ipv4? && @destination_ip.ipv4?
540
+ elsif @source_ip
541
+ @source_ip.ipv4?
542
+ elsif @destination_ip
543
+ @destination_ip.ipv4?
544
+ end
545
+ end
546
+
547
+ def ipv6?
548
+ if @source_ip && @destination_ip
549
+ @source_ip.ipv6? && @destination_ip.ipv6?
550
+ elsif @source_ip
551
+ @source_ip.ipv6?
552
+ elsif @destination_ip
553
+ @destination_ip.ipv6?
554
+ end
555
+ end
556
+ end
557
+
558
+ class IPProtocol
559
+ # See http://www.iana.org/assignments/protocol-numbers/protocol-numbers.txt
560
+ NUMBERS = (1..142).to_a + [ 255 ]
561
+ NAMES = {
562
+ 0 => "HOPOPT",
563
+ 1 => "ICMP",
564
+ 2 => "IGMP",
565
+ 3 => "GGP",
566
+ 4 => "IPv4",
567
+ 5 => "ST",
568
+ 6 => "TCP",
569
+ 7 => "CBT",
570
+ 8 => "EGP",
571
+ 9 => "IGP",
572
+ 10 => "BBN-RCC-MON",
573
+ 11 => "NVP-II",
574
+ 12 => "PUP",
575
+ 13 => "ARGUS",
576
+ 14 => "EMCON",
577
+ 15 => "XNET",
578
+ 16 => "CHAOS",
579
+ 17 => "UDP",
580
+ 18 => "MUX",
581
+ 19 => "DCN-MEAS",
582
+ 20 => "HMP",
583
+ 21 => "PRM",
584
+ 22 => "XNS-IDP",
585
+ 23 => "TRUNK-1",
586
+ 24 => "TRUNK-2",
587
+ 25 => "LEAF-1",
588
+ 26 => "LEAF-2",
589
+ 27 => "RDP",
590
+ 28 => "IRTP",
591
+ 29 => "ISO-TP4",
592
+ 30 => "NETBLT",
593
+ 31 => "MFE-NSP",
594
+ 32 => "MERIT-INP",
595
+ 33 => "DCCP",
596
+ 34 => "3PC",
597
+ 35 => "IDPR",
598
+ 36 => "XTP",
599
+ 37 => "DDP",
600
+ 38 => "IDPR-CMTP",
601
+ 39 => "TP++",
602
+ 40 => "IL",
603
+ 41 => "IPv6",
604
+ 42 => "SDRP",
605
+ 43 => "IPv6-Route",
606
+ 44 => "IPv6-Frag",
607
+ 45 => "IDRP",
608
+ 46 => "RSVP",
609
+ 47 => "GRE",
610
+ 48 => "DSR",
611
+ 49 => "BNA",
612
+ 50 => "ESP",
613
+ 51 => "AH",
614
+ 52 => "I-NLSP",
615
+ 53 => "SWIPE",
616
+ 54 => "NARP",
617
+ 55 => "MOBILE",
618
+ 56 => "TLSP",
619
+ 57 => "SKIP",
620
+ 58 => "IPv6-ICMP",
621
+ 59 => "IPv6-NoNxt",
622
+ 60 => "IPv6-Opts",
623
+ 62 => "CFTP",
624
+ 64 => "SAT-EXPAK",
625
+ 65 => "KRYPTOLAN",
626
+ 66 => "RVD",
627
+ 67 => "IPPC",
628
+ 69 => "SAT-MON",
629
+ 70 => "VISA",
630
+ 71 => "IPCV",
631
+ 72 => "CPNX",
632
+ 73 => "CPHB",
633
+ 74 => "WSN",
634
+ 75 => "PVP",
635
+ 76 => "BR-SAT-MON",
636
+ 77 => "SUN-ND",
637
+ 78 => "WB-MON",
638
+ 79 => "WB-EXPAK",
639
+ 80 => "ISO-IP",
640
+ 81 => "VMTP",
641
+ 82 => "SECURE-VMTP",
642
+ 83 => "VINES",
643
+ 84 => "TTP",
644
+ 84 => "IPTM",
645
+ 85 => "NSFNET-IGP",
646
+ 86 => "DGP",
647
+ 87 => "TCF",
648
+ 88 => "EIGRP",
649
+ 89 => "OSPFIGP",
650
+ 90 => "Sprite-RPC",
651
+ 91 => "LARP",
652
+ 92 => "MTP",
653
+ 93 => "AX.25",
654
+ 94 => "IPIP",
655
+ 95 => "MICP",
656
+ 96 => "SCC-SP",
657
+ 97 => "ETHERIP",
658
+ 98 => "ENCAP",
659
+ 100 => "GMTP",
660
+ 101 => "IFMP",
661
+ 102 => "PNNI",
662
+ 103 => "PIM",
663
+ 104 => "ARIS",
664
+ 105 => "SCPS",
665
+ 106 => "QNX",
666
+ 107 => "A/N",
667
+ 108 => "IPComp",
668
+ 109 => "SNP",
669
+ 110 => "Compaq-Peer",
670
+ 111 => "IPX-in-IP",
671
+ 112 => "VRRP",
672
+ 113 => "PGM",
673
+ 115 => "L2TP",
674
+ 116 => "DDX",
675
+ 117 => "IATP",
676
+ 118 => "STP",
677
+ 119 => "SRP",
678
+ 120 => "UTI",
679
+ 121 => "SMP",
680
+ 122 => "SM",
681
+ 123 => "PTP",
682
+ 124 => "ISIS over IPv4",
683
+ 125 => "FIRE",
684
+ 126 => "CRTP",
685
+ 127 => "CRUDP",
686
+ 128 => "SSCOPMCE",
687
+ 129 => "IPLT",
688
+ 130 => "SPS",
689
+ 131 => "PIPE",
690
+ 132 => "SCTP",
691
+ 133 => "FC",
692
+ 134 => "RSVP-E2E-IGNORE",
693
+ 135 => "Mobility Header",
694
+ 136 => "UDPLite",
695
+ 137 => "MPLS-in-IP",
696
+ 138 => "manet",
697
+ 139 => "HIP",
698
+ 140 => "Shim6",
699
+ 141 => "WESP",
700
+ 142 => "ROHC",
701
+ 255 => "Reserved",
702
+ }
703
+
704
+ def initialize proto_num
705
+ @number = proto_num if NUMBERS.member? proto_num
706
+ end
707
+
708
+ def to_ip
709
+ @number
710
+ end
711
+
712
+ def to_s
713
+ NAMES[@number]
714
+ end
715
+ end
716
+
717
+ IPAddr.send(:include, IPAddrExtensions)
718
+ String.send(:include, StringIPExtensions)
719
+ Integer.send(:include, IntIPExtensions)
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ipaddr_extensions
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - James Harton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email: james@resistor.io
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - MIT-LICENSE
34
+ - README
35
+ - Rakefile
36
+ - ipaddr_extensions.gemspec
37
+ - lib/ipaddr_extensions.rb
38
+ homepage: http://github.com/jamesotron/IPAddrExtensions
39
+ licenses: []
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 2.0.0
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: A small gem that adds extra functionality to Rubys IPAddr class
61
+ test_files: []