ruby-ip 0.9.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/COPYING.txt +340 -0
- data/LICENSE.txt +58 -0
- data/README.rdoc +104 -0
- data/Rakefile +37 -0
- data/lib/ip.rb +1 -0
- data/lib/ip/base.rb +354 -0
- data/lib/ip/cpal.rb +22 -0
- data/lib/ip/socket.rb +34 -0
- data/test/ip_cpal_test.rb +33 -0
- data/test/ip_test.rb +521 -0
- data/test/test_helper.rb +4 -0
- metadata +66 -0
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
|
3
|
+
#### TESTING ####
|
4
|
+
require 'rake/testtask'
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
Rake::TestTask.new do |t|
|
8
|
+
t.libs << "test"
|
9
|
+
t.test_files = FileList['test/*_test.rb']
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
#### COVERAGE ####
|
14
|
+
begin
|
15
|
+
require 'rcov/rcovtask'
|
16
|
+
|
17
|
+
Rcov::RcovTask.new do |t|
|
18
|
+
t.libs << "test"
|
19
|
+
t.test_files = FileList['test/*_test.rb']
|
20
|
+
t.verbose = true
|
21
|
+
t.rcov_opts << '--exclude "gems/*"'
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
end
|
25
|
+
|
26
|
+
#### DOCUMENTATION ####
|
27
|
+
require 'rake/rdoctask'
|
28
|
+
Rake::RDocTask.new { |rdoc|
|
29
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
30
|
+
rdoc.template = ENV['template'] if ENV['template']
|
31
|
+
rdoc.title = "Ruby-IP Documentation"
|
32
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
33
|
+
rdoc.options << '--charset' << 'utf-8'
|
34
|
+
rdoc.rdoc_files.include('README.rdoc')
|
35
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
36
|
+
}
|
37
|
+
|
data/lib/ip.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ip/base'
|
data/lib/ip/base.rb
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
# Copyright (C) 2009-2010 Brian Candler <http://www.deploy2.net/>
|
2
|
+
# Licensed under the same terms as ruby. See LICENCE.txt and COPYING.txt
|
3
|
+
|
4
|
+
class IP
|
5
|
+
PROTO_TO_CLASS = {}
|
6
|
+
|
7
|
+
class << self
|
8
|
+
alias :orig_new :new
|
9
|
+
# Examples:
|
10
|
+
# IP.new("1.2.3.4")
|
11
|
+
# IP.new("1.2.3.4/28")
|
12
|
+
# IP.new("1.2.3.4/28@routing_context")
|
13
|
+
#
|
14
|
+
# Array form (inverse of to_a and to_ah):
|
15
|
+
# IP.new(["v4", 0x01020304])
|
16
|
+
# IP.new(["v4", 0x01020304, 28])
|
17
|
+
# IP.new(["v4", 0x01020304, 28, "routing_context"])
|
18
|
+
# IP.new(["v4", "01020304", 28, "routing_context"])
|
19
|
+
#
|
20
|
+
# Note that this returns an instance of IP::V4 or IP::V6. IP is the
|
21
|
+
# base class of both of those, but cannot be instantiated itself.
|
22
|
+
def new(src)
|
23
|
+
case src
|
24
|
+
when String
|
25
|
+
parse(src) || (raise ArgumentError, "invalid address")
|
26
|
+
when Array
|
27
|
+
(PROTO_TO_CLASS[src[0]] || (raise ArgumentError, "invalid protocol")).new(*src[1..-1])
|
28
|
+
when IP
|
29
|
+
src.dup
|
30
|
+
else
|
31
|
+
raise ArgumentError, "invalid address"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Parse a string as an IP address - return a V4/V6 object or nil
|
36
|
+
def parse(str)
|
37
|
+
V4.parse(str) || V6.parse(str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Length of prefix (network portion) of address
|
42
|
+
attr_reader :pfxlen
|
43
|
+
|
44
|
+
# Routing Context indicates the scope of this address (e.g. virtual router)
|
45
|
+
attr_accessor :ctx
|
46
|
+
|
47
|
+
# Examples:
|
48
|
+
# IP::V4.new(0x01020304)
|
49
|
+
# IP::V4.new("01020304")
|
50
|
+
# IP::V4.new(0x01020304, 28)
|
51
|
+
# IP::V4.new(0x01020304, 28, "routing_context")
|
52
|
+
def initialize(addr, pfxlen=nil, ctx=nil)
|
53
|
+
@addr = addr.is_a?(String) ? addr.to_i(16) : addr.to_i
|
54
|
+
raise ArgumentError, "Invalid address value" if @addr < 0 || @addr > self.class::MASK
|
55
|
+
self.pfxlen = pfxlen
|
56
|
+
self.ctx = ctx
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return the protocol in string form, "v4" or "v6"
|
60
|
+
def proto
|
61
|
+
self.class::PROTO
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return the string representation of the address, x.x.x.x[/pfxlen][@ctx]
|
65
|
+
def to_s
|
66
|
+
ctx ? "#{to_addrlen}@#{ctx}" : to_addrlen
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return the string representation of the IP address and prefix, or
|
70
|
+
# just the IP address if it's a single address
|
71
|
+
def to_addrlen
|
72
|
+
pfxlen == self.class::ADDR_BITS ? to_addr : "#{to_addr}/#{pfxlen}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return the address as an Integer
|
76
|
+
def to_i
|
77
|
+
@addr
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return the address as a hexadecimal string (8 or 32 digits)
|
81
|
+
def to_hex
|
82
|
+
@addr.to_s(16).rjust(self.class::ADDR_BITS>>2,"0")
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return an array representation of the address, with 3 or 4 elements
|
86
|
+
# depending on whether there is a routing context set.
|
87
|
+
# ["v4", 16909060, 28]
|
88
|
+
# ["v4", 16909060, 28, "context"]
|
89
|
+
# (Removing the last element makes them Comparable, as nil.<=> doesn't exist)
|
90
|
+
def to_a
|
91
|
+
@ctx ? [self.class::PROTO, @addr, @pfxlen, @ctx] :
|
92
|
+
[self.class::PROTO, @addr, @pfxlen]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return an array representation of the address, with 3 or 4 elements
|
96
|
+
# depending on whether there is a routing context set, using hexadecimal.
|
97
|
+
# ["v4", "01020304", 28]
|
98
|
+
# ["v4", "01020304", 28, "context"]
|
99
|
+
def to_ah
|
100
|
+
@ctx ? [self.class::PROTO, to_hex, @pfxlen, @ctx] :
|
101
|
+
[self.class::PROTO, to_hex, @pfxlen]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Change the prefix length. If nil, the maximum is used (32 or 128)
|
105
|
+
def pfxlen=(pfxlen)
|
106
|
+
@mask = nil
|
107
|
+
if pfxlen
|
108
|
+
pfxlen = pfxlen.to_i
|
109
|
+
raise ArgumentError, "Invalid prefix length" if pfxlen < 0 || pfxlen > self.class::ADDR_BITS
|
110
|
+
@pfxlen = pfxlen
|
111
|
+
else
|
112
|
+
@pfxlen = self.class::ADDR_BITS
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return the mask for this pfxlen as an integer. For example,
|
117
|
+
# a V4 /24 address has a mask of 255 (0x000000ff)
|
118
|
+
def mask
|
119
|
+
@mask ||= (1 << (self.class::ADDR_BITS - @pfxlen)) - 1
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return a new IP object at the base of the subnet, with an optional
|
123
|
+
# offset applied.
|
124
|
+
# IP.new("1.2.3.4/24").network => #<IP::V4 1.2.3.0/24>
|
125
|
+
# IP.new("1.2.3.4/24").network(7) => #<IP::V4 1.2.3.7/24>
|
126
|
+
def network(offset=0)
|
127
|
+
self.class.new((@addr & ~mask) + offset, @pfxlen, @ctx)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return a new IP object at the top of the subnet, with an optional
|
131
|
+
# offset applied.
|
132
|
+
# IP.new("1.2.3.4/24").broadcast => #<IP::V4 1.2.3.255/24>
|
133
|
+
# IP.new("1.2.3.4/24").broadcast(-1) => #<IP::V4 1.2.3.254/24>
|
134
|
+
def broadcast(offset=0)
|
135
|
+
self.class.new((@addr | mask) + offset, @pfxlen, @ctx)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return a new IP object representing the netmask
|
139
|
+
# IP.new("1.2.3.4/24").netmask => #<IP::V4 255.255.255.0>
|
140
|
+
def netmask
|
141
|
+
self.class.new(self.class::MASK & ~mask)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return a new IP object representing the wildmask (inverse netmask)
|
145
|
+
# IP.new("1.2.3.4/24").netmask => #<IP::V4 0.0.0.255>
|
146
|
+
def wildmask
|
147
|
+
self.class.new(mask)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Masks the address such that it is the base of the subnet
|
151
|
+
# IP.new("1.2.3.4/24").mask! => #<IP::V4 1.2.3.0/24>
|
152
|
+
def mask!
|
153
|
+
@addr &= ~mask
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns true if this is not the base address of the subnet implied
|
158
|
+
# from the prefix length (e.g. 1.2.3.4/24 is offset, because the base
|
159
|
+
# is 1.2.3.0/24)
|
160
|
+
def offset?
|
161
|
+
@addr != (@addr & ~mask)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns offset from base of subnet to this address
|
165
|
+
# IP.new("1.2.3.4/24").offset => 4
|
166
|
+
def offset
|
167
|
+
@addr - (@addr & ~mask)
|
168
|
+
end
|
169
|
+
|
170
|
+
# If the address is not on the base, turn it into a single IP.
|
171
|
+
# IP.new("1.2.3.4/24").reset_pfxlen! => <IP::V4 1.2.3.4>
|
172
|
+
# IP.new("1.2.3.0/24").reset_pfxlen! => <IP::V4 1.2.3.0/24>
|
173
|
+
def reset_pfxlen!
|
174
|
+
self.pfxlen = nil if offset?
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_range
|
179
|
+
a1 = @addr & ~mask
|
180
|
+
a2 = a1 | mask
|
181
|
+
(a1..a2)
|
182
|
+
end
|
183
|
+
|
184
|
+
# The number of IP addresses in subnet
|
185
|
+
# IP.new("1.2.3.4/24").size => 256
|
186
|
+
def size
|
187
|
+
mask + 1
|
188
|
+
end
|
189
|
+
|
190
|
+
def +(other)
|
191
|
+
self.class.new(@addr + other.to_int, @pfxlen, @ctx)
|
192
|
+
end
|
193
|
+
|
194
|
+
def -(other)
|
195
|
+
self.class.new(@addr - other.to_int, @pfxlen, @ctx)
|
196
|
+
end
|
197
|
+
|
198
|
+
def &(other)
|
199
|
+
self.class.new(@addr & other.to_int, @pfxlen, @ctx)
|
200
|
+
end
|
201
|
+
|
202
|
+
def |(other)
|
203
|
+
self.class.new(@addr | other.to_int, @pfxlen, @ctx)
|
204
|
+
end
|
205
|
+
|
206
|
+
def ^(other)
|
207
|
+
self.class.new(@addr ^ other.to_int, @pfxlen, @ctx)
|
208
|
+
end
|
209
|
+
|
210
|
+
def ~
|
211
|
+
self.class.new(~@addr & self.class::MASK, @pfxlen, @ctx)
|
212
|
+
end
|
213
|
+
|
214
|
+
def succ!
|
215
|
+
@addr += 1
|
216
|
+
self
|
217
|
+
end
|
218
|
+
|
219
|
+
def inspect
|
220
|
+
res = "#<#{self.class} #{to_s}>"
|
221
|
+
end
|
222
|
+
|
223
|
+
def ipv4_mapped?
|
224
|
+
false
|
225
|
+
end
|
226
|
+
|
227
|
+
def ipv4_compat?
|
228
|
+
false
|
229
|
+
end
|
230
|
+
|
231
|
+
def native
|
232
|
+
self
|
233
|
+
end
|
234
|
+
|
235
|
+
def hash
|
236
|
+
to_a.hash
|
237
|
+
end
|
238
|
+
|
239
|
+
def eql?(other)
|
240
|
+
to_a.eql?(other.to_a)
|
241
|
+
end
|
242
|
+
|
243
|
+
def <=>(other)
|
244
|
+
to_a <=> other.to_a
|
245
|
+
end
|
246
|
+
include Comparable
|
247
|
+
|
248
|
+
class V4 < IP
|
249
|
+
class << self; alias :new :orig_new; end
|
250
|
+
PROTO = "v4".freeze
|
251
|
+
PROTO_TO_CLASS[PROTO] = self
|
252
|
+
ADDR_BITS = 32
|
253
|
+
MASK = (1 << ADDR_BITS) - 1
|
254
|
+
|
255
|
+
# Parse a string; return an V4 instance if it's a valid IPv4 address,
|
256
|
+
# nil otherwise
|
257
|
+
def self.parse(str)
|
258
|
+
if str =~ /\A(\d+)\.(\d+)\.(\d+)\.(\d+)(?:\/(\d+))?(?:@(.*))?\z/
|
259
|
+
pfxlen = ($5 || ADDR_BITS).to_i
|
260
|
+
return nil if pfxlen > 32
|
261
|
+
addrs = [$1.to_i, $2.to_i, $3.to_i, $4.to_i]
|
262
|
+
return nil if addrs.find { |n| n>255 }
|
263
|
+
addr = (((((addrs[0] << 8) | addrs[1]) << 8) | addrs[2]) << 8) | addrs[3]
|
264
|
+
new(addr, pfxlen, $6)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Return just the address part as a String in dotted decimal form
|
269
|
+
def to_addr
|
270
|
+
sprintf("%d.%d.%d.%d",
|
271
|
+
(@addr>>24)&0xff, (@addr>>16)&0xff, (@addr>>8)&0xff, @addr&0xff)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
class V6 < IP
|
276
|
+
class << self; alias :new :orig_new; end
|
277
|
+
PROTO = "v6".freeze
|
278
|
+
PROTO_TO_CLASS[PROTO] = self
|
279
|
+
ADDR_BITS = 128
|
280
|
+
MASK = (1 << ADDR_BITS) - 1
|
281
|
+
|
282
|
+
# Parse a string; return an V6 instance if it's a valid IPv6 address,
|
283
|
+
# nil otherwise
|
284
|
+
#--
|
285
|
+
# FIXME: allow larger variations of mapped addrs like 0:0:0:0:ffff:1.2.3.4
|
286
|
+
#++
|
287
|
+
def self.parse(str)
|
288
|
+
case str
|
289
|
+
when /\A\[?::(ffff:)?(\d+\.\d+\.\d+\.\d+)\]?(?:\/(\d+))?(?:@(.*))?\z/i
|
290
|
+
mapped = $1
|
291
|
+
pfxlen = ($3 || 128).to_i
|
292
|
+
ctx = $4
|
293
|
+
return nil if pfxlen > 128
|
294
|
+
v4 = (V4.parse($2) || return).to_i
|
295
|
+
v4 |= 0xffff00000000 if mapped
|
296
|
+
new(v4, pfxlen, ctx)
|
297
|
+
when /\A\[?([0-9a-f:]+)\]?(?:\/(\d+))?(?:@(.*))?\z/i
|
298
|
+
addr = $1
|
299
|
+
pfxlen = ($2 || 128).to_i
|
300
|
+
return nil if pfxlen > 128
|
301
|
+
ctx = $3
|
302
|
+
return nil if pfxlen > 128
|
303
|
+
if addr =~ /\A(.*?)::(.*)\z/
|
304
|
+
left, right = $1, $2
|
305
|
+
l = left.split(':')
|
306
|
+
r = right.split(':')
|
307
|
+
rest = 8 - l.length - r.length
|
308
|
+
return nil if rest < 0
|
309
|
+
else
|
310
|
+
l = addr.split(':')
|
311
|
+
r = []
|
312
|
+
rest = 0
|
313
|
+
return nil if l.length != 8
|
314
|
+
end
|
315
|
+
out = ""
|
316
|
+
l.each { |quad| return nil if quad.length>4; out << quad.rjust(4,"0") }
|
317
|
+
rest.times { out << "0000" }
|
318
|
+
r.each { |quad| return nil if quad.length>4; out << quad.rjust(4,"0") }
|
319
|
+
new(out, pfxlen, ctx)
|
320
|
+
else
|
321
|
+
nil
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Return just the address part as a String in compact decimal form
|
326
|
+
def to_addr
|
327
|
+
if ipv4_compat?
|
328
|
+
"::#{native.to_addr}"
|
329
|
+
elsif ipv4_mapped?
|
330
|
+
"::ffff:#{native.to_addr}"
|
331
|
+
else
|
332
|
+
res = to_hex.scan(/..../).join(':')
|
333
|
+
res.gsub!(/\b0{1,3}/,'')
|
334
|
+
res.gsub!(/(\A|:)(0:)+/,'::')
|
335
|
+
res.gsub!(/::0\z/, '::')
|
336
|
+
res
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def ipv4_mapped?
|
341
|
+
(@addr >> 32) == 0xffff
|
342
|
+
end
|
343
|
+
|
344
|
+
def ipv4_compat?
|
345
|
+
@addr > 1 && (@addr >> 32) == 0
|
346
|
+
end
|
347
|
+
|
348
|
+
# Convert an IPv6 mapped/compat address to a V4 native address
|
349
|
+
def native
|
350
|
+
return self unless (ipv4_mapped? || ipv4_compat?) && (@pfxlen >= 96)
|
351
|
+
V4.new(@addr & V4::MASK, @pfxlen - 96, @ctx)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
data/lib/ip/cpal.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright (C) 2009-2010 Brian Candler <http://www.deploy2.net/>
|
2
|
+
# Licensed under the same terms as ruby. See LICENCE.txt and COPYING.txt
|
3
|
+
|
4
|
+
class IP
|
5
|
+
# Create an instance from an alternative array format:
|
6
|
+
# [context, protocol, address, prefix_length]
|
7
|
+
def self.from_cpal(cpal)
|
8
|
+
new([cpal[1], cpal[2], cpal[3], cpal[0]])
|
9
|
+
end
|
10
|
+
|
11
|
+
# Return an alternative 4-element array format with the routing context
|
12
|
+
# as the first element. Useful for grouping by context.
|
13
|
+
# cpal = [context, proto, address, prefix_length]
|
14
|
+
def to_cpal
|
15
|
+
[@ctx, self.class::PROTO, @addr, @pfxlen]
|
16
|
+
end
|
17
|
+
|
18
|
+
# As cpal but with a hex string for the address part
|
19
|
+
def to_cphl
|
20
|
+
[@ctx, self.class::PROTO, to_hex, @pfxlen]
|
21
|
+
end
|
22
|
+
end
|
data/lib/ip/socket.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright (C) 2009-2010 Brian Candler <http://www.deploy2.net/>
|
2
|
+
# Licensed under the same terms as ruby. See LICENCE.txt and COPYING.txt
|
3
|
+
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
class IP
|
7
|
+
# Return the address family, Socket::AF_INET or Socket::AF_INET6
|
8
|
+
def af
|
9
|
+
self.class::AF
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convert to a packed sockaddr structure
|
13
|
+
def to_sockaddr(port=0)
|
14
|
+
Socket.pack_sockaddr_in(port, to_addr)
|
15
|
+
end
|
16
|
+
|
17
|
+
class V4
|
18
|
+
AF = Socket::AF_INET
|
19
|
+
PROTO_TO_CLASS[AF] = self
|
20
|
+
|
21
|
+
# Avoid the string conversion when building sockaddr. Unfortunately this
|
22
|
+
# fails 32-bit machines with 1.8.6 for addrs >= 0x80000000. There is
|
23
|
+
# also no corresponding Socket.pack_sockaddr_in6 we could use for V6.
|
24
|
+
|
25
|
+
#def to_sockaddr(port=0)
|
26
|
+
# Socket.pack_sockaddr_in(port, to_i)
|
27
|
+
#end
|
28
|
+
end
|
29
|
+
|
30
|
+
class V6
|
31
|
+
AF = Socket::AF_INET6
|
32
|
+
PROTO_TO_CLASS[AF] = self
|
33
|
+
end
|
34
|
+
end
|