better_ipaddr 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,118 @@
1
+ # BetterIpaddr
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/better_ipaddr.svg)](https://rubygems.org/gems/better_ipaddr)
4
+ [![Build Status](https://travis-ci.org/bjmllr/better_ipaddr.svg)](https://travis-ci.org/bjmllr/better_ipaddr)
5
+
6
+ The `IPAddr` class that network engineers always wanted.
7
+
8
+ ```ruby
9
+ require "better_ipaddr"
10
+ addr = IPAddr::V4[some_source] # shortcut for .new, because your test suite
11
+ # contains a zillion IP addresses and you're
12
+ # tired of typing Socket::AF_INET
13
+ addr.host? # is it a host address?
14
+ addr.network? # or a network address?
15
+ addr.cidr # what is the CIDR representation?
16
+ addr.grow(1) # what is the next larger enclosing network?
17
+ addr + 1 # what address comes after this one?
18
+ addr.size # how many host addresses fit in this network?
19
+ addr.mask_addr # what's the netmask (as an integer)?
20
+ addr.prefix_length # what's the prefix length (as an integer)?
21
+ addr == other_addr # are these the same network?
22
+ addr.cover?(other_addr) # does this network contain that one?
23
+ addr.overlap?(other_addr) # do these networks share any hosts?
24
+ addr.each { ... } # do something with each host in this network
25
+ addr.first # what's the network address?
26
+ addr.last # what's the broadcast address?
27
+ addr.wildcard # what's the wildcard mask for this network?
28
+ addr.summarize_with(other) # can these networks be summarized?
29
+ ```
30
+
31
+ Bonus: comes with an IP space finder.
32
+
33
+ ```ruby
34
+ require "better_ipaddr/space"
35
+ space = BetterIpaddr::Space.new(some_array_of_ipaddrs,
36
+ space: IPAddr::V4["10.0.0.0/8"])
37
+
38
+ space.find_by_minimum_prefix_length(26) # => a subnet with at least 64 addresses
39
+ space.find_by_minimum_size(256) # => a /24 or larger
40
+ space.gaps # => all your free subnets
41
+ ```
42
+
43
+ Coming soon: MAC addresses and their various notations.
44
+
45
+ ## Installation
46
+
47
+ Ruby 2.0 or later is required. There are no other dependencies.
48
+
49
+ Add this line to your application's Gemfile:
50
+
51
+ ```ruby
52
+ gem 'better_ipaddr'
53
+ ```
54
+
55
+ And then execute:
56
+
57
+ $ bundle
58
+
59
+ Or install it yourself as:
60
+
61
+ $ gem install better_ipaddr
62
+
63
+ ## Usage
64
+
65
+ There are multiple ways to load this gem.
66
+
67
+ The quick and dirty way is to `require
68
+ "better_ipaddr/core_extension"`, which adds all the additional methods
69
+ directly to `IPAddr`. This is "monkey patching", so there may be
70
+ unintended consequences. If you use this approach in another library
71
+ or framework, be very clear about it with your users.
72
+
73
+ ```ruby
74
+ require "better_ipaddr/core_extension"
75
+ addr = IPAddr["1.0.0.1"]
76
+ class_c = addr << 8 # => IPAddr::V4["1.0.0.0/24"]
77
+ IPAddr.new("1.0.0.0/24").summarize_with(IPAddr["1.0.1.0/24"]) # => IPAddr::V4["1.0.0.0/23"]
78
+ ```
79
+
80
+ The recommended way is to `require "better_ipaddr"` and use
81
+ the `IPAddr` subclasses explicitly.
82
+
83
+ ```ruby
84
+ require "better_ipaddr"
85
+ addr = IPAddr::V4["1.0.0.1"]
86
+ class_c = addr << 8 # => IPAddr::V4["1.0.0.0/24"]
87
+ ```
88
+
89
+ Another way is to `require "better_ipaddr/methods"` and mix
90
+ `BetterIpaddr::InstanceMethods` into your own class which implements
91
+ the rest of the `IPAddr` API, or into individual `IPAddr` objects.
92
+
93
+ `BetterIpaddr::Space`, a collection class for dealing with sets of network addresses, is also available but not loaded by default.
94
+
95
+ ```ruby
96
+ require "better_ipaddr/space"
97
+ space = BetterIpaddr::Space.new([IPAddr::V4["10.0.0.0/24"],
98
+ IPAddr::V4["10.0.2.0/24"]])
99
+ space.gaps # => BetterIpaddr::Space.new([IPAddr::V4["10.0.1.0/24"]])
100
+ ```
101
+
102
+ The available methods are described in the [API docs](http://www.rubydoc.info/gems/better_ipaddr).
103
+
104
+ ## Development
105
+
106
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
107
+
108
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
109
+
110
+ ## Contributing
111
+
112
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bjmllr/better_ipaddr. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
113
+
114
+ ## Copyright and License
115
+
116
+ Copyright (C) 2016 Ben Miller
117
+
118
+ The gem is available as free software under the terms of the [GNU General Public License, Version 3](http://www.gnu.org/licenses/gpl-3.0.html).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:spec) do |t|
5
+ t.libs << "spec"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["spec/**/*_spec.rb"]
8
+ end
9
+
10
+ task default: :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'better_ipaddr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "better_ipaddr"
8
+ spec.version = BetterIpaddr::VERSION
9
+ spec.authors = ["Ben Miller"]
10
+ spec.email = ["bmiller@rackspace.com"]
11
+
12
+ spec.summary = "IPAddr enhancements for network management."
13
+ spec.homepage = "https://github.com/bjmllr/better_ipaddr"
14
+ spec.license = "GPL-3.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r(^exe/)) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.0"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest", "~> 5.0"
25
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "better_ipaddr"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,2 @@
1
+ require "ipaddr"
2
+ require "better_ipaddr/classes"
@@ -0,0 +1,72 @@
1
+ require "better_ipaddr/methods"
2
+
3
+ class IPAddr
4
+ class Base < IPAddr
5
+ include BetterIpaddr::Constants
6
+ include BetterIpaddr::InstanceMethods
7
+ include Comparable
8
+ include Enumerable
9
+
10
+ def inherited(cls)
11
+ cls.extend BetterIpaddr::ClassMethods
12
+ end
13
+
14
+ def self.[](address, mask = nil, family: self::FAMILY)
15
+ if mask
16
+ new(address, family).mask(new(mask, family).to_s)
17
+ else
18
+ new(address, family)
19
+ end
20
+ end
21
+
22
+ # Return the given address as an instance of a class specific to
23
+ # its address family.
24
+ #
25
+ # @param address [IPAddr] the address to convert
26
+ # @return [IPAddr::V4, IPAddr::V6, IPAddr::EUI48]
27
+ def self.specialize(address)
28
+ return address unless address.class == IPAddr
29
+ case address.family
30
+ when Family::IPV4
31
+ IPAddr::V4[address.to_i, address.instance_variable_get(:@mask_addr)]
32
+ when Family::IPV6
33
+ IPAddr::V6[address.to_i, address.instance_variable_get(:@mask_addr)]
34
+ when Family::EUI48
35
+ IPAddr::MAC[address.to_i, address.instance_variable_get(:@mask_addr)]
36
+ end
37
+ end
38
+
39
+ def self.specialize_constants(family)
40
+ const_set(:FAMILY, family)
41
+ const_set(:BIT_LENGTH, FAMILY_TO_BIT_LENGTH.fetch(self::FAMILY))
42
+ const_set(:NETMASK_TO_PREFIX_LENGTH,
43
+ NETMASK_TO_PREFIX_LENGTH.fetch(self::FAMILY))
44
+ const_set(:PREFIX_LENGTH_TO_NETMASK,
45
+ PREFIX_LENGTH_TO_NETMASK.fetch(self::FAMILY))
46
+ end
47
+
48
+ def address_family_bit_length
49
+ self.class::BIT_LENGTH
50
+ end
51
+
52
+ def network?
53
+ prefix_length < self.class::BIT_LENGTH
54
+ end
55
+
56
+ def prefix_length
57
+ self.class::NETMASK_TO_PREFIX_LENGTH[mask_addr]
58
+ end
59
+ end
60
+
61
+ class V4 < Base
62
+ specialize_constants Family::IPV4
63
+ end
64
+
65
+ class V6 < Base
66
+ specialize_constants Family::IPV6
67
+ end
68
+
69
+ class MAC < Base
70
+ specialize_constants Family::EUI48
71
+ end
72
+ end
@@ -0,0 +1,11 @@
1
+ require "ipaddr"
2
+ require "better_ipaddr/methods"
3
+ require "better_ipaddr/classes"
4
+
5
+ class IPAddr
6
+ include BetterIpaddr::Constants
7
+ extend BetterIpaddr::ClassMethods
8
+ prepend BetterIpaddr::InstanceMethods
9
+ include Comparable
10
+ include Enumerable
11
+ end
@@ -0,0 +1,332 @@
1
+ require "ipaddr"
2
+ require "socket"
3
+
4
+ module BetterIpaddr
5
+ module Constants
6
+ # Integer codes representing supported address clases.
7
+ # Reuse values from Socket namespace where possible.
8
+ module Family
9
+ IPV4 = Socket::AF_INET
10
+ IPV6 = Socket::AF_INET6
11
+ EUI48 = 48
12
+ EUI64 = 64
13
+ end
14
+
15
+ # Map well known address family names to constants.
16
+ SYMBOL_TO_FAMILY = {
17
+ ipv4: Family::IPV4,
18
+ ipv6: Family::IPV6,
19
+ eui48: Family::EUI48,
20
+ eui64: Family::EUI64,
21
+ mac: Family::EUI48
22
+ }
23
+
24
+ # Map each address family to the size of its address space, in bits.
25
+ FAMILY_TO_BIT_LENGTH = {
26
+ Family::IPV4 => 32,
27
+ Family::IPV6 => 128,
28
+ Family::EUI48 => 48,
29
+ Family::EUI64 => 64
30
+ }
31
+
32
+ # Map all possible prefix lengths to the corresponding netmasks.
33
+ PREFIX_LENGTH_TO_NETMASK = {}
34
+ FAMILY_TO_BIT_LENGTH.each_pair do |family, size|
35
+ netmasks = []
36
+ (0..size).each do |prefix_length|
37
+ netmasks[prefix_length] = 2**size - 2**(size - prefix_length)
38
+ end
39
+ PREFIX_LENGTH_TO_NETMASK[family] = netmasks
40
+ end
41
+
42
+ # Map all possible netmasks to the corresponding prefix lengths.
43
+ NETMASK_TO_PREFIX_LENGTH = {}
44
+ PREFIX_LENGTH_TO_NETMASK.each_pair do |family, hash|
45
+ NETMASK_TO_PREFIX_LENGTH[family] =
46
+ Hash[hash.map.with_index { |e, i| [e, i] }]
47
+ end
48
+ end
49
+
50
+ module ClassMethods
51
+ include Constants
52
+
53
+ # @overload [](address, family)
54
+ # @param address [Integer] the integer representation of the address
55
+ # @param family [Symbol] a symbol named for the address's
56
+ # address family, one of +:ipv4+, +:ipv6+, or +:mac+.
57
+ # @return [IPAddr]
58
+ # Wrapper for IPAddr.new that accepts a symbolic family name and
59
+ # returns a specialized IPAddr subclass.
60
+ #
61
+ # @overload [](address, family)
62
+ # @param address [Integer] the integer representation of the address
63
+ # @param family [Integer] the magic number representing the address's
64
+ # address family.
65
+ # @return [IPAddr]
66
+ # Wrapper for IPAddr.new that accepts a symbolic family name and
67
+ # returns a specialized IPAddr subclass.
68
+ #
69
+ # @overload [](address)
70
+ # @param address [String] the string representation of the address
71
+ # @return [IPAddr]
72
+ # Wrapper for IPAddr.new that accepts the string representation
73
+ # of an address returns a specialized IPAddr subclass.
74
+
75
+ def [](address, family = nil)
76
+ instance = case family
77
+ when Symbol
78
+ self[address, SYMBOL_TO_FAMILY.fetch(family)]
79
+ when IPAddr
80
+ address
81
+ when nil
82
+ new(address)
83
+ else
84
+ new(address, family)
85
+ end
86
+ IPAddr::Base.specialize(instance)
87
+ end
88
+ end
89
+
90
+ module InstanceMethods
91
+ include Constants
92
+
93
+ # Return the magic number representing the address family.
94
+ # @return [Integer]
95
+ attr_reader :family
96
+
97
+ # Return the integer representation of the netmask.
98
+ # @return [Integer]
99
+ attr_reader :mask_addr
100
+ alias_method :netmask, :mask_addr
101
+
102
+ # Return the address greater than the original address by the
103
+ # given offset.
104
+ # @param offset [Integer] the difference between the original
105
+ # address and the returned address
106
+ # @return [IPAddr]
107
+
108
+ def +(offset)
109
+ self.class.new(@addr + offset, family)
110
+ end
111
+
112
+ # Return the address less than the original address by the given
113
+ # offset.
114
+ # @param offset [Integer] the difference between the original
115
+ # address and the returned address
116
+ # @return [IPAddr]
117
+
118
+ def -(offset)
119
+ self + (-offset)
120
+ end
121
+
122
+ # @overload <=>(other)
123
+ # Compare this address with another address of the same address
124
+ # family.
125
+ # @param [IPAddr] other
126
+ # @return [Integer]
127
+
128
+ # @overload <=>(other)
129
+ # Compare this address with the integer representation of another
130
+ # address of the same address family.
131
+ # @param [Integer] other
132
+ # @return [Integer]
133
+
134
+ def <=>(other)
135
+ if other.is_a?(IPAddr)
136
+ family_difference = family <=> other.family
137
+ return family_difference unless family_difference == 0
138
+ elsif !other.is_a?(Integer)
139
+ fail ArgumentError, "Can't compare #{self.class} with #{other.class}"
140
+ end
141
+
142
+ address_difference = to_i <=> other.to_i
143
+
144
+ if address_difference != 0 || !other.is_a?(IPAddr)
145
+ return address_difference
146
+ end
147
+
148
+ other.instance_variable_get(:@mask_addr) <=> @mask_addr
149
+ end
150
+
151
+ # Test the equality of two IP addresses, or an IP address an
152
+ # integer representing an address in the same address family.
153
+ # @param other [IPAddr, Integer] the address to compare with
154
+ # @return [Boolean]
155
+
156
+ def ==(other)
157
+ return false if other.nil?
158
+ (self <=> other) == 0
159
+ end
160
+
161
+ # The address at the given offset relative to the network address
162
+ # of the network. A negative offset will be used to count
163
+ # backwards from the highest addresses within the network.
164
+ # @param offset [Integer] the index within the network of the
165
+ # desired address
166
+ # @return [IPAddr] the address at the given index
167
+
168
+ def [](offset)
169
+ offset2 = offset >= 0 ? offset : size + offset
170
+ self.class[to_i + offset2, family: family]
171
+ end
172
+
173
+ # Returns the number of bits allowed by the address family.
174
+ # A more efficient form of this method is available to the
175
+ # specialized IPAddr child classes.
176
+ #
177
+ # @return [Integer]
178
+
179
+ def address_family_bit_length
180
+ FAMILY_TO_BIT_LENGTH.fetch(family)
181
+ end
182
+
183
+ # Returns a string representation of the address without a prefix length.
184
+ #
185
+ # @return [String]
186
+
187
+ def base
188
+ _to_string(@addr)
189
+ end
190
+
191
+ # Return a string containing the CIDR representation of the address.
192
+ #
193
+ # @return [String]
194
+
195
+ def cidr
196
+ return _to_string(@addr) unless ipv4? || ipv6?
197
+ "#{_to_string(@addr)}/#{prefixlen}"
198
+ end
199
+
200
+ # Test whether or not this address completely encloses the other address.
201
+
202
+ def cover?(other)
203
+ first <= other.first && other.last <= last
204
+ end
205
+
206
+ # @overload each
207
+ # Yield each host address contained within the network. A host
208
+ # address, such as +1.1.1.1/32+, will yield only itself. Returns
209
+ # the original object.
210
+ # @yield [IPAddr]
211
+ # @return [IPAddr]
212
+
213
+ # @overload each
214
+ # Return an enumerator with the behavior described above.
215
+ # @return [Enumerator]
216
+
217
+ def each
218
+ if block_given?
219
+ (0...size).each do |offset|
220
+ yield self[offset]
221
+ end
222
+ self
223
+ else
224
+ enum_for(:each)
225
+ end
226
+ end
227
+
228
+ # The first host address in the network.
229
+ # @return [IPAddr]
230
+
231
+ def first
232
+ self[0]
233
+ end
234
+
235
+ # Return a new address with the prefix length reduced by the given
236
+ # amount. The new address will cover the original address.
237
+ # @param shift [Integer] the decrease in the prefix length
238
+ # @return [IPAddr]
239
+
240
+ def grow(shift)
241
+ mask(prefix_length - shift)
242
+ end
243
+
244
+ # Return true if the address represents a host (i.e., only one address).
245
+
246
+ def host?
247
+ prefix_length >= address_family_bit_length
248
+ end
249
+
250
+ # Return the last address in the network, which by convention is
251
+ # the broadcast address in IP networks.
252
+ # @return [IPAddr]
253
+
254
+ def last
255
+ self[-1]
256
+ end
257
+
258
+ alias_method :broadcast, :last
259
+
260
+ # Test whether or not two networks have any addresses in common
261
+ # (i.e., if either entirely encloses the other).
262
+
263
+ def overlap?(other)
264
+ cover?(other) || other.cover?(self)
265
+ end
266
+
267
+ # Return the prefix length.
268
+ # A more efficient form of this method is available to the
269
+ # specialized +IPAddr+ child classes.
270
+ # @return [Integer]
271
+
272
+ def prefix_length
273
+ NETMASK_TO_PREFIX_LENGTH[family][mask_addr]
274
+ end
275
+
276
+ alias_method :prefixlen, :prefix_length
277
+
278
+ # Return a new address with the prefix length increased by the
279
+ # given amount. The old address will cover the new address.
280
+ # @param shift [Integer] the increase in the prefix length
281
+ # @return [Boolean]
282
+
283
+ def shrink(shift)
284
+ mask(prefix_length + shift)
285
+ end
286
+
287
+ # Return the number of host addresses representable by the network
288
+ # given its size.
289
+ # @return [Integer]
290
+
291
+ def size
292
+ 2**(address_family_bit_length - prefix_length)
293
+ end
294
+
295
+ # Returns a summary address if the two networks can be combined
296
+ # into a single network without covering any other networks.
297
+ # Returns +nil+ if the two networks can't be combined this way.
298
+ # @return [IPAddr?]
299
+
300
+ def summarize_with(other)
301
+ if other.nil?
302
+ nil
303
+ elsif cover?(other)
304
+ self
305
+ elsif other.cover?(self)
306
+ other
307
+ elsif other.grow(1) == grow(1)
308
+ grow(1)
309
+ end
310
+ end
311
+
312
+ # Return a range representing the network. A block can be given to
313
+ # specify a conversion procedure, for example to convert the first
314
+ # and last addresses to integers before building the range.
315
+ # @return [Range(IPAddr)]
316
+
317
+ def to_range
318
+ if block_given?
319
+ (yield first)..(yield last)
320
+ else
321
+ first..last
322
+ end
323
+ end
324
+
325
+ # Return a wildcard mask representing the network.
326
+ # @return [IPAddr]
327
+
328
+ def wildcard
329
+ _to_string(@mask_addr.to_i ^ (2**address_family_bit_length - 1))
330
+ end
331
+ end
332
+ end