ip 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,393 @@
1
+ #
2
+ # IP::Address - base class for IP::Address::IPv4 and IP::Address::IPv6
3
+ #
4
+
5
+ class IP::Address
6
+
7
+ #
8
+ # This original IP Address you passed it, returned as a string.
9
+ #
10
+ attr_reader :ip_address
11
+ #
12
+ # This returns an Array of Integer which contains the octets of
13
+ # the IP, in descending order.
14
+ #
15
+ attr_reader :octets
16
+
17
+ #
18
+ # Returns an octet given the proper index. The octets returned are
19
+ # Integer types.
20
+ #
21
+ def [](num)
22
+ if @octets[num].nil?
23
+ raise IP::BoundaryException.new("Invalid octet")
24
+ end
25
+ return @octets[num]
26
+ end
27
+
28
+ #
29
+ # See [].
30
+ #
31
+ alias_method :octet, :[]
32
+
33
+ #
34
+ # Returns a 128-bit integer representing the address.
35
+ #
36
+ def pack
37
+ fail "This method is abstract."
38
+ end
39
+ end
40
+
41
+ #
42
+ # Support for IPv4
43
+ #
44
+
45
+ class IP::Address::IPv4 < IP::Address
46
+ #
47
+ # Constructs an IP::Address::IPv4 object.
48
+ #
49
+ # This can take two types of input. Either a string that contains
50
+ # a dotted-quad formatted address, or an integer that contains the
51
+ # data. This integer is expected to be constructed in the way that
52
+ # IP::Address::Util.pack_ipv4 would generate such an integer.
53
+ #
54
+ # This constructor will throw IP::AddressException on any parse
55
+ # errors.
56
+ #
57
+ def initialize(ip_address)
58
+ if ip_address.kind_of? Integer
59
+ # unpack to generate a string, and parse that.
60
+ # overwrites 'ip_address'
61
+ # horribly inefficient, but general.
62
+
63
+ raw = IP::Address::Util.raw_unpack(ip_address)[0..1]
64
+ octets = []
65
+
66
+ 2.times do |x|
67
+ octets.push(raw[x] & 0x00FF)
68
+ octets.push((raw[x] & 0xFF00) >> 8)
69
+ end
70
+
71
+ ip_address = octets.reverse.join(".")
72
+ end
73
+
74
+ if ! ip_address.kind_of? String
75
+ raise IP::AddressException.new("Fed IP address '#{ip_address}' is not String or Fixnum")
76
+ end
77
+
78
+ @ip_address = ip_address
79
+
80
+ #
81
+ # Unbeknowest by me, to_i will not throw an exception if the string
82
+ # can't be converted cleanly - it just truncates, similar to atoi() and perl's int().
83
+ #
84
+ # Code below does a final sanity check.
85
+ #
86
+
87
+ octets = ip_address.split(/\./)
88
+ octets_i = octets.collect { |x| x.to_i }
89
+
90
+ 0.upto(octets.length - 1) do |octet|
91
+ if octets[octet] != octets_i[octet].to_s
92
+ raise IP::AddressException.new("Integer conversion failed")
93
+ end
94
+ end
95
+
96
+ @octets = octets_i
97
+
98
+ # I made a design decision to allow 0.0.0.0 here.
99
+ if @octets.length != 4 or @octets.find_all { |x| x > 255 }.length > 0
100
+ raise IP::AddressException.new("IP address is improperly formed")
101
+ end
102
+ end
103
+
104
+ #
105
+ # Returns a 128-bit integer representing the address.
106
+ #
107
+ def pack
108
+ # this routine does relatively little. all it does is ensure
109
+ # that the IP address is of a certain size and has certain numeric limits.
110
+ myip = self.octets
111
+ packval = [0] * 6
112
+
113
+ #
114
+ # this ensures that the octets are 8 bit, and combines the octets in order to
115
+ # form two 16-bit integers suitable for pushing into the last places in 'packval'
116
+ #
117
+
118
+ (0..3).step(2) { |x| packval.push(((myip[x] & 0xFF) << 8) | (myip[x+1] & 0xFF)) }
119
+
120
+ return IP::Address::Util.raw_pack(packval)
121
+ end
122
+ end
123
+
124
+ class IP::Address::IPv6 < IP::Address
125
+
126
+ #
127
+ # Construct a new IP::Address::IPv6 object.
128
+ #
129
+ # It can be passed two different types for construction:
130
+ #
131
+ # * A string which contains a valid, RFC4291-compliant IPv6 address
132
+ # (all forms are supported, including the
133
+ # backwards-compatibility IPv4 methods)
134
+ #
135
+ # * A 128-bit integer which is a sum of all the octets, left-most
136
+ # octet being the highest 32-bit portion (see IP::Address::Util
137
+ # for help generating this value)
138
+ #
139
+
140
+ def initialize(ip_address)
141
+ if ip_address.kind_of? Integer
142
+ # unpack to generate a string, and parse that.
143
+ # overwrites 'ip_address'
144
+ # horribly inefficient, but general.
145
+
146
+ raw = IP::Address::Util.raw_unpack(ip_address)
147
+
148
+ ip_address = format_address(raw.reverse)
149
+ end
150
+
151
+ if ! ip_address.kind_of? String
152
+ raise IP::AddressException.new("Fed IP address '#{ip_address}' is not String or Fixnum")
153
+ end
154
+
155
+ @ip_address = ip_address
156
+
157
+ octets = parse_address(ip_address)
158
+
159
+ if octets.length != 8
160
+ puts octets
161
+ raise IP::AddressException.new("IPv6 address '#{ip_address}' does not have 8 octets or a floating range specifier")
162
+ end
163
+
164
+ #
165
+ # Now we check the contents of the address, to be sure we have
166
+ # proper hexidecimal values
167
+ #
168
+
169
+ @octets = octets_atoi(octets)
170
+ end
171
+
172
+ #
173
+ # returns an octet in its hexidecimal representation.
174
+ #
175
+
176
+ def octet_as_hex(index)
177
+ return format_octet(self[index])
178
+ end
179
+
180
+ #
181
+ # Returns an address with no floating range specifier.
182
+ #
183
+ # Ex:
184
+ #
185
+ # IP::Address::IPv6.new("DEAD::BEEF").long_address => "DEAD:0:0:0:0:0:0:BEEF"
186
+ #
187
+
188
+ def long_address
189
+ return format_address
190
+ end
191
+
192
+ #
193
+ # Returns a shortened address using the :: range specifier.
194
+ #
195
+ # This will replace any sequential octets that are equal to '0' with '::'.
196
+ # It does this searching from right to left, looking for a sequence
197
+ # of them. Per specification, only one sequence can be replaced in
198
+ # this fashion. It will return a long address if it can't find
199
+ # something suitable.
200
+ #
201
+ # Ex:
202
+ #
203
+ # "DEAD:0:0:0:BEEF:0:0:0" => "DEAD:0:0:0:BEEF::"
204
+
205
+ def short_address
206
+ octets = @octets.dup
207
+
208
+ # short circuit: if less than 2 octets are equal to 0, don't
209
+ # bother - return a long address.
210
+
211
+ if octets.find_all { |x| x == 0 }.length < 2
212
+ return format_address(octets)
213
+ end
214
+
215
+ filling = false
216
+
217
+ left = []
218
+ right = []
219
+
220
+ 7.downto(0) do |x|
221
+ if !filling and left.length == 0 and octets[x] == 0
222
+ filling = true
223
+ elsif filling
224
+ if octets[x] != 0
225
+ left.push(octets[x])
226
+ filling = false
227
+ end
228
+ elsif left.length > 0
229
+ left.push(octets[x])
230
+ else
231
+ right.push(octets[x])
232
+ end
233
+ end
234
+
235
+ return format_address(left.reverse) + "::" + format_address(right.reverse)
236
+
237
+ end
238
+
239
+ #
240
+ # Returns a 128-bit integer representing the address.
241
+ #
242
+ def pack
243
+ return IP::Address::Util.raw_pack(self.octets.dup)
244
+ end
245
+
246
+ protected
247
+
248
+ #
249
+ # will parse a string address and return an 8 index array containing
250
+ # all the octets. Should handle IPv4 to IPv6 and wildcards properly.
251
+ #
252
+
253
+ def parse_address(address)
254
+
255
+ #
256
+ # since IPv6 can have missing parts, we have to scan sequentially.
257
+ # fill the LHS until we encounter '::', at which point we fill the
258
+ # RHS.
259
+ #
260
+ # If the LHS is 8 octets, we're done. Otherwise, we take a
261
+ # difference of 8 and the sum of the number of octets from the LHS
262
+ # from the number of octets from the RHS. We then fill abs(num) at
263
+ # the end of the LHS array with 0, and combine them to form the
264
+ # address.
265
+ #
266
+ # There can only be one '::' in an address, so if we are filling
267
+ # the RHS and encounter another '::', we throw AddressException.
268
+ #
269
+
270
+ # catches ::XXXX::
271
+ if address.scan(/::/).length > 1
272
+ raise IP::AddressException.new("IPv6 address '#{ip_address}' has more than one floating range ('::') specifier")
273
+ end
274
+
275
+ octets = address.split(":")
276
+
277
+ if octets.length < 8
278
+
279
+ if octets[-1].index(".").nil? and address.match(/::/)
280
+ octets = handle_wildcard_in_address(octets)
281
+ elsif octets[-1].index(".")
282
+ # we have a dotted quad IPv4 compatibility address.
283
+ # create an IPv4 object, get the raw value and stuff it into
284
+ # the lower two octets.
285
+
286
+ raw = IP::Address::IPv4.new(octets.pop).pack
287
+ raw = raw & 0xFFFFFFFF
288
+ low = raw & 0xFFFF
289
+ high = (raw >> 16) & 0xFFFF
290
+ octets = handle_wildcard_in_address(octets)[0..5] + ([high, low].collect { |x| format_octet(x) })
291
+ else
292
+ raise IP::AddressException.new("IPv6 address '#{address}' has less than 8 octets")
293
+ end
294
+
295
+ elsif octets.length > 8
296
+ raise IP::AddressException.new("IPv6 address '#{address}' has more than 8 octets")
297
+ end
298
+
299
+ return octets
300
+ end
301
+
302
+
303
+ #
304
+ # This handles :: addressing in IPv6 and generates a full set of
305
+ # octets in response.
306
+ #
307
+ # The series of octets handed to this routine are expected to be the
308
+ # result of splitting the address by ':'.
309
+ #
310
+
311
+ def handle_wildcard_in_address(octets)
312
+ lhs = []
313
+ rhs = []
314
+
315
+ i = octets.index("") # find ::
316
+
317
+ # easy out for xxxx:xxxx:: and so on
318
+ if i.nil?
319
+ lhs = octets.dup
320
+ elsif i == 0
321
+ # for some reason "::123:123".split(":") returns two empty
322
+ # strings in the array, yet a trailing "::" doesn't.
323
+ rhs = octets[2..-1]
324
+ else
325
+ lhs = octets[0..(i-1)]
326
+ rhs = octets[(i+1)..-1]
327
+ end
328
+
329
+ unless rhs.index("").nil?
330
+ raise IP::AddressException.new("IPv6 address '#{ip_address}' has more than one floating range ('::') specifier")
331
+ end
332
+
333
+ missing = (8 - (lhs.length + rhs.length))
334
+ missing.times { lhs.push("0") }
335
+
336
+ octets = lhs + rhs
337
+
338
+ return octets
339
+ end
340
+
341
+ #
342
+ # Converts (and checks) a series of octets from ascii to integer.
343
+ #
344
+
345
+ def octets_atoi(octets)
346
+ new_octets = []
347
+
348
+ octets.each do |x|
349
+ if x.length > 4
350
+ raise IP::AddressException.new("IPv6 address '#{ip_address}' has an octet that is larger than 32 bits")
351
+ end
352
+
353
+ octet = x.hex
354
+
355
+ # normalize the octet to 4 places with leading zeroes, uppercase.
356
+ x = ("0" * (4 - x.length)) + x.upcase
357
+
358
+ unless ("%0.4X" % octet) == x
359
+ raise IP::AddressException.new("IPv6 address '#{ip_address}' has octets that contain non-hexidecimal data")
360
+ end
361
+
362
+ new_octets.push(octet)
363
+ end
364
+
365
+ return new_octets
366
+
367
+ end
368
+
369
+
370
+ #
371
+ # Formats the integer octets in ruby into their respective hex values.
372
+ #
373
+
374
+ def format_octet(octet)
375
+ # this isn't required by the spec, but makes the output quite a bit
376
+ # prettier.
377
+ if octet < 10
378
+ return octet.to_s
379
+ end
380
+
381
+ return "%0.4X" % octet
382
+ end
383
+
384
+ #
385
+ # The same as +format_octet+, but processes an array (intended to be
386
+ # a whole address).
387
+ #
388
+
389
+ def format_address(octets=@octets)
390
+ return octets.collect { |x| format_octet(x) }.join(":")
391
+ end
392
+
393
+ end
@@ -0,0 +1,192 @@
1
+ #
2
+ # IP::CIDR - Works with Classless Inter-Domain Routing formats, such
3
+ # as 10.0.0.1/32 or 10.0.0.1/255.255.255.255
4
+ #
5
+
6
+ class IP::CIDR
7
+ #
8
+ # Contains the original CIDR you fed it, returned as a string.
9
+ #
10
+ attr_reader :cidr
11
+ #
12
+ # Contains the IP address (LHS) only. Returned as an IP::Address
13
+ # object.
14
+ #
15
+ attr_reader :ip
16
+ #
17
+ # Contains the integer-based (short) netmask (RHS) only. Returned as an
18
+ # integer.
19
+ #
20
+ attr_reader :mask
21
+
22
+ #
23
+ # Given a string of format X.X.X.X/X, in standard CIDR notation,
24
+ # this will construct a IP::CIDR object.
25
+ #
26
+ def initialize(cidr)
27
+ if !cidr.kind_of? String
28
+ raise IP::AddressException.new("CIDR value is not of type String")
29
+ end
30
+
31
+ @cidr = cidr
32
+ @ip, @mask = cidr.split(/\//, 2)
33
+
34
+ @ip = IP::Address::Util.string_to_ip(@ip)
35
+
36
+ if @ip.nil? or @mask.nil?
37
+ raise IP::AddressException.new("CIDR is not valid - invalid format")
38
+ end
39
+
40
+ if @mask.length == 0 or /[^0-9.]/.match @mask or
41
+ (@ip.kind_of? IP::Address::IPv6 and @mask.to_i.to_s != @mask)
42
+ raise IP::AddressException.new("CIDR RHS is not valid - #{@mask}")
43
+ end
44
+
45
+ if @ip.kind_of? IP::Address::IPv4 and @mask.length > 2
46
+ # get the short netmask for IPv4 - this will throw an exception if the netmask is malformed.
47
+ @mask = IP::Address::Util.short_netmask(IP::Address::IPv4.new(@mask))
48
+ end
49
+
50
+ @mask = @mask.to_i
51
+ end
52
+
53
+ def netmask
54
+ warn "IP::CIDR#netmask is deprecated. Please use IP::CIDR#long_netmask instead."
55
+ return self.long_netmask
56
+ end
57
+
58
+ #
59
+ # This produces the long netmask (eg. 255.255.255.255) of the CIDR in an
60
+ # IP::Address object.
61
+ #
62
+ # This will throw an exception for IPv6 addresses.
63
+ #
64
+ def long_netmask
65
+ if @ip.kind_of? IP::Address::IPv6
66
+ raise IP::AddressException.new("IPv6 does not support a long netmask.")
67
+ end
68
+
69
+ return IP::Address::Util.long_netmask_ipv4(@mask)
70
+ end
71
+
72
+ #
73
+ # This produces the short netmask (eg. 32) of the CIDR in an IP::Address
74
+ # object.
75
+ #
76
+
77
+ def short_netmask
78
+ return @mask
79
+ end
80
+
81
+ #
82
+ # This produces a range ala IP::Range, but only for the subnet
83
+ # defined by the CIDR object.
84
+ #
85
+ def range
86
+ return IP::Range[self.first_ip, self.last_ip]
87
+ end
88
+
89
+ #
90
+ # This returns the first ip address of the cidr as an IP::Address object.
91
+ #
92
+ def first_ip
93
+ rawip = @ip.pack
94
+
95
+ #
96
+ # since our actual mask calculation is done with the full 128 bits,
97
+ # we have to shift calculations that we want in IPv4 to the left to
98
+ # get proper return values.
99
+ #
100
+
101
+ if @ip.kind_of? IP::Address::IPv4
102
+ rawip = rawip << 96
103
+ end
104
+
105
+ rawnm = (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) << (128 - @mask)
106
+ lower = rawip & rawnm
107
+
108
+ if @ip.kind_of? IP::Address::IPv4
109
+ lower = lower & (0xFFFFFFFF000000000000000000000000)
110
+ lower = lower >> 96
111
+ end
112
+
113
+ case @ip.class.object_id
114
+ when IP::Address::IPv4.object_id
115
+ return IP::Address::IPv4.new(lower)
116
+ when IP::Address::IPv6.object_id
117
+ return IP::Address::IPv6.new(lower)
118
+ else
119
+ raise IP::AddressException.new("Cannot determine type of IP address")
120
+ end
121
+
122
+ end
123
+
124
+ #
125
+ # This returns the last ip address of the cidr as an IP::Address object.
126
+ #
127
+ def last_ip
128
+ rawip = @ip.pack
129
+
130
+ # see #first_ip for the reason that we shift this way for IPv4.
131
+ if @ip.kind_of? IP::Address::IPv4
132
+ rawip = rawip << 96
133
+ end
134
+
135
+ rawnm = (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) << (128 - @mask)
136
+ upper = rawip | ~rawnm
137
+
138
+ if @ip.kind_of? IP::Address::IPv4
139
+ upper = upper & (0xFFFFFFFF000000000000000000000000)
140
+ upper = upper >> 96
141
+ end
142
+
143
+ case @ip.class.object_id
144
+ when IP::Address::IPv4.object_id
145
+ return IP::Address::IPv4.new(upper)
146
+ when IP::Address::IPv6.object_id
147
+ return IP::Address::IPv6.new(upper)
148
+ else
149
+ raise IP::AddressException.new("Cannot determine type of IP address")
150
+ end
151
+ end
152
+
153
+ #
154
+ # This will take another IP::CIDR object as an argument and check to see
155
+ # if it overlaps with this cidr object. Returns true/false on overlap.
156
+ #
157
+ # This also throws a TypeError if passed invalid data.
158
+ #
159
+ def overlaps?(other_cidr)
160
+ raise TypeError.new("Expected object of type IP::CIDR") unless(other_cidr.kind_of?(IP::CIDR))
161
+
162
+ myfirst = self.first_ip.pack
163
+ mylast = self.last_ip.pack
164
+
165
+ otherfirst = other_cidr.first_ip.pack
166
+ otherlast = other_cidr.last_ip.pack
167
+
168
+ return ((myfirst >= otherfirst && myfirst <= otherlast) ||
169
+ (mylast <= otherlast && mylast >= otherfirst) ||
170
+ (otherfirst >= myfirst && otherfirst <= mylast)) ? true : false;
171
+ end
172
+
173
+ #
174
+ # Given an IP::Address object, will determine if it is included in
175
+ # the current subnet. This does not generate a range and is
176
+ # comparatively much faster.
177
+ #
178
+ # This will *not* generate an exception when IPv4 objects are
179
+ # compared to IPv6, and vice-versa. This is intentional.
180
+ #
181
+
182
+ def includes?(address)
183
+ raise TypeError.new("Expected type of IP::Address or derived") unless (address.kind_of? IP::Address)
184
+
185
+ raw = address.pack
186
+ first = first_ip.pack
187
+ last = last_ip.pack
188
+
189
+ return (raw >= first) && (raw <= last)
190
+ end
191
+
192
+ end