ipaddr_extensions 1.0.0

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