better_ipaddr 0.1.2

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,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