ruby-ip 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|