ruby-ip 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -0,0 +1 @@
1
+ require 'ip/base'
@@ -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
@@ -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
@@ -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