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.
@@ -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