ip 0.1.1 → 0.2.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,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