ruby-ip2 0.10.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.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
@@ -0,0 +1,58 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ (see COPYING.txt file), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) rename any non-standard executables so the names do not conflict
21
+ with standard executables, which must also be provided.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or executable
26
+ form, provided that you do at least ONE of the following:
27
+
28
+ a) distribute the executables and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard executables non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under this terms.
43
+
44
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
45
+ files under the ./missing directory. See each file for the copying
46
+ condition.
47
+
48
+ 5. The scripts and library files supplied as input to or produced as
49
+ output from the software do not automatically fall under the
50
+ copyright of the software, but belong to whomever generated them,
51
+ and may be sold commercially, and may be aggregated with this
52
+ software.
53
+
54
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
55
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
56
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
57
+ PURPOSE.
58
+
@@ -0,0 +1,198 @@
1
+ = ruby-ip library
2
+
3
+ This is a library for manipulating IP addresses.
4
+
5
+ {<img src="https://travis-ci.org/G7M3/ruby-ip2.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/G7M3/ruby-ip2]
6
+ {<img src="https://coveralls.io/repos/github/G7M3/ruby-ip2/badge.svg?branch=master" alt="Coverage Status" />}[https://coveralls.io/github/G7M3/ruby-ip2?branch=master]
7
+
8
+
9
+ == Installation
10
+
11
+ Gem:: <tt>gem install ruby-ip</tt>
12
+ Source:: <tt>git clone git://github.com/G7M3/ruby-ip2.git</tt>
13
+
14
+ == Feature overview
15
+
16
+ * Create from string and to string
17
+
18
+ require 'ip'
19
+ ip = IP.new("192.0.2.53/24")
20
+ ip.to_s # "192.0.2.53/24"
21
+ ip.to_i # 3221226037
22
+ ip.to_b # 11000000000000000000001000110101
23
+ ip.to_hex # "c0000235"
24
+ ip.to_addr # "192.0.2.53"
25
+ ip.to_arpa # "53.2.0.192.in-addr.arpa."
26
+ ip.pfxlen # 24
27
+
28
+ * Convert from IPAddr to IP and back to IPAddr
29
+
30
+ # new IPAddr
31
+ ipaddr = IPAddr.new('192.168.2.1')
32
+ # => #<IPAddr: IPv4:192.168.2.1/255.255.255.255>
33
+
34
+ # convert it to IP
35
+ ip_from_ipaddr = IP.new_ntoh(ipaddr.hton)
36
+ # => <IP::V4 192.168.2.1>
37
+
38
+ # and back to IPAddr
39
+ ipaddr_from_ip = IPAddr.new_ntoh(ip_from_ipaddr.hton)
40
+ # => #<IPAddr: IPv4:192.168.2.1/255.255.255.255>
41
+
42
+ * Qualify IP address with "routing context" (VRF)
43
+
44
+ ip = IP.new("192.0.2.53/24@cust1")
45
+ ip.to_s # "192.0.2.53/24@cust1"
46
+ ip.to_addrlen # "192.0.2.53/24"
47
+ ip.ctx # "cust1"
48
+
49
+ * Clean implementation of IP::V4 and IP::V6 as subclasses of IP
50
+
51
+ ip1 = IP.new("192.0.2.53/24") #<IP::V4 192.0.2.53/24>
52
+ ip2 = IP.new("2001:db8:be00::/48") #<IP::V6 2001:db8:be00::/48>
53
+ ip1.is_a?(IP::V4) # true
54
+
55
+ * Create directly from integers or hex
56
+
57
+ ip = IP::V4.new(3221226037, 24, "cust1")
58
+ ip = IP::V4.new("c0000235", 24, "cust1")
59
+
60
+ * Netmask manipulation
61
+
62
+ ip = IP.new("192.0.2.53/24")
63
+ ip.network #<IP::V4 192.0.2.0/24>
64
+ ip.network(1) #<IP::V4 192.0.2.1/24>
65
+ ip.broadcast #<IP::V4 192.0.2.255/24>
66
+ ip.broadcast(-1) #<IP::V4 192.0.2.254/24>
67
+ ip.mask # 255
68
+ ip.size # 256
69
+ ip.netmask.to_s # "255.255.255.0"
70
+ ip.wildmask.to_s # "0.0.0.255"
71
+
72
+ * Address masking
73
+
74
+ ip = IP.new("192.0.2.53/24")
75
+ ip.offset? # true
76
+ ip.offset # 53
77
+ ip.mask!
78
+ ip.to_s # "192.0.2.0/24"
79
+ ip.offset? # false
80
+
81
+ * Simple IP arithmetic
82
+
83
+ ip = IP.new("192.0.2.53/24")
84
+ ip + 4 #<IP::V4 192.0.2.57/24>
85
+ ip | 7 #<IP::V4 192.0.2.55/24>
86
+ ip ^ 7 #<IP::V4 192.0.2.50/24>
87
+ ~ip #<IP::V4 63.255.253.202/24>
88
+
89
+ * Advanced Subnet Operations
90
+ sn = IP.new('192.168.0.0/24')
91
+ ip = IP.new('192.168.0.48/32')
92
+ sn.split [#<IP::V4 192.168.0.0/25>,
93
+ #<IP::V4 192.168.0.128/25>] (2 evenly divided subnets)
94
+ sn.divide_by_subnets(3) [#<IP::V4 192.168.0.0/26>,
95
+ #<IP::V4 192.168.0.64/26>,
96
+ #<IP::V4 192.168.0.128/26>,
97
+ #<IP::V4 192.168.0.192/26>] (4 evenly divided subnets)
98
+ #keep in mind this always takes into account a network and broadcast address
99
+ sn.divide_by_hosts(100) [#<IP::V4 192.168.0.0/25>,
100
+ #<IP::V4 192.168.0.128/25>] (128 hosts each)
101
+ ip = IP.new('192.168.0.48/32')
102
+ ip.is_in?(sn)
103
+ => true
104
+
105
+ * Deaggregate subnets from range
106
+
107
+ IP.new('1.2.0.0').deaggregate(IP.new('1.3.255.255'))
108
+ => [#<IP::V4 1.2.0.0/15>]
109
+ IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255'))
110
+ => [#<IP::V4 1.2.0.0/15>, #<IP::V4 1.4.0.0/16>]
111
+ IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff'))
112
+ => [#<IP::V6 2001:db8:85a3:8d3::/64>]
113
+ IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::'))
114
+ => [#<IP::V6 2001:db8:85a3:8d3::/80>, #<IP::V6 2001:db8:85a3:8d3:1::>]
115
+
116
+ * Convert to and from a compact Array representation
117
+
118
+ ip1 = IP.new("192.0.2.53/24@cust1")
119
+ ip1.to_a # ["v4", 3221226037, 24, "cust1"]
120
+
121
+ ip2 = IP.new(["v4", 3221226037, 24, "cust1"])
122
+ ip1 == ip2 # true
123
+
124
+ * Hex array representation, useful when talking to languages which don't
125
+ have Bignum support
126
+
127
+ ip1 = IP.new("2001:db8:be00::/48@cust1")
128
+ ip1.to_ah # ["v6", "20010db8be0000000000000000000000", 48, "cust1"]
129
+
130
+ ip2 = IP.new(["v6", "20010db8be0000000000000000000000", 48, "cust1"])
131
+ ip1 == ip2 # true
132
+
133
+ * Addresses are Comparable, sortable, and can be used as Hash keys
134
+
135
+ * Addresses can be stored in a VARBINARY(16) database column as string of bytes
136
+
137
+ # == Schema Information
138
+ #
139
+ # Table name: ipaddresses
140
+ #
141
+ # id :integer not null, primary key
142
+ # address :binary(16)
143
+ # pfxlen :integer
144
+ # created_at :datetime not null
145
+ # updated_at :datetime not null
146
+ #
147
+ # Indexes
148
+ #
149
+ # index_ipaddresses_on_address (address)
150
+ # index_ipaddresses_on_pfxlen (pfxlen)
151
+ #
152
+
153
+ require 'ip'
154
+ class IpAddress < ActiveRecord::Base
155
+ validates_uniqueness_of :address, scope: [:pfxlen]
156
+ validates_presence_of :address, :pfxlen
157
+
158
+ # IpAddress.last.address returns an IP object
159
+ def address
160
+ IP.new("#{IP.new_ntoh(super).to_s}/#{pfxlen}")
161
+ end
162
+
163
+ # Address can be set with string:
164
+ # ipaddress = IpAddress.new({address: '2001:db8:be00::/128'})
165
+ # Or IP object:
166
+ # ip = IP.new('2001:db8:be00::/128')
167
+ # ipaddress = IpAddress.new({address: ip})
168
+ def address=(arg)
169
+ ip = IP.new(arg)
170
+ self.pfxlen = ip.pfxlen
171
+ super(ip.hton)
172
+ end
173
+ end
174
+
175
+ == Why not IPAddr?
176
+
177
+ Ruby bundles an IPAddr class (ipaddr.rb). However there are a number of
178
+ serious problems with this library.
179
+
180
+ 1. Given an IP address with a netmask or prefix (e.g. 192.0.2.0/24) it's
181
+ very hard to get access to the netmask part. It involves digging around
182
+ instance variables.
183
+
184
+ 2. It's impossible to deal with an off-base address with prefix, because
185
+ IPAddr forcibly masks it to the base. e.g. 192.0.2.53/24 is stored as
186
+ 192.0.2.0/24
187
+
188
+ 3. IPAddr uses calls to the socket library to validate IP addresses, and
189
+ this can trigger spurious DNS lookups when given an invalid IP address.
190
+ ruby-ip does not depend on the socket library at all, unless you
191
+ require 'ip/socket' to have access to the Socket::AF_INET and
192
+ Socket::AF_INET6 constants.
193
+
194
+ == Copying
195
+
196
+ You may use, distribute and modify this software under the same terms as
197
+ ruby itself (see LICENSE.txt and COPYING.txt)
198
+
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ip/version'
4
+ require 'ip/base'
@@ -0,0 +1,559 @@
1
+ # frozen_string_literal: false
2
+
3
+ # Copyright (C) 2009-2010 Brian Candler <http://www.deploy2.net/>
4
+ # Licensed under the same terms as ruby. See LICENCE.txt and COPYING.txt
5
+
6
+ # The IP Class is the base class for the module
7
+ class IP
8
+ PROTO_TO_CLASS = {}
9
+
10
+ class << self
11
+ alias orig_new new
12
+ # Examples:
13
+ # IP.new("1.2.3.4")
14
+ # IP.new("1.2.3.4/28")
15
+ # IP.new("1.2.3.4/28@routing_context")
16
+ #
17
+ # Array form (inverse of to_a and to_ah):
18
+ # IP.new(["v4", 0x01020304])
19
+ # IP.new(["v4", 0x01020304, 28])
20
+ # IP.new(["v4", 0x01020304, 28, "routing_context"])
21
+ # IP.new(["v4", "01020304", 28, "routing_context"])
22
+ #
23
+ # Note that this returns an instance of IP::V4 or IP::V6. IP is the
24
+ # base class of both of those, but cannot be instantiated itself.
25
+ def new(src)
26
+ case src
27
+ when String
28
+ parse(src) || (raise ArgumentError, 'invalid address')
29
+ when Array
30
+ (PROTO_TO_CLASS[src[0]] ||
31
+ (raise ArgumentError, 'invalid protocol')).new(*src[1..-1])
32
+ when IP
33
+ src.dup
34
+ else
35
+ raise ArgumentError, 'invalid address'
36
+ end
37
+ end
38
+
39
+ # Parse a string as an IP address - return a V4/V6 object or nil
40
+ def parse(str)
41
+ V4.parse(str) || V6.parse(str)
42
+ end
43
+ end
44
+
45
+ # Length of prefix (network portion) of address
46
+ attr_reader :pfxlen
47
+
48
+ # Routing Context indicates the scope of this address (e.g. virtual router)
49
+ attr_accessor :ctx
50
+
51
+ # Examples:
52
+ # IP::V4.new(0x01020304)
53
+ # IP::V4.new("01020304")
54
+ # IP::V4.new(0x01020304, 28)
55
+ # IP::V4.new(0x01020304, 28, "routing_context")
56
+ def initialize(addr, pfxlen = nil, ctx = nil)
57
+ @addr = addr.is_a?(String) ? addr.to_i(16) : addr.to_i
58
+ raise ArgumentError, 'Invalid address value' if @addr < 0 || @addr > self.class::MASK
59
+
60
+ self.pfxlen = pfxlen
61
+ self.ctx = ctx
62
+ end
63
+
64
+ # Return the protocol in string form, "v4" or "v6"
65
+ def proto
66
+ self.class::PROTO
67
+ end
68
+
69
+ # Creates a new ip containing the given network byte ordered
70
+ # string form of an IP address.
71
+ def self.new_ntoh(addr)
72
+ IP.new(IP.ntop(addr))
73
+ end
74
+
75
+ # Convert a network byte ordered string form of an IP address into
76
+ # human readable form.
77
+ def self.ntop(addr)
78
+ case addr.size
79
+ when 4
80
+ s = addr.unpack('C4').join('.')
81
+ when 16
82
+ s = (['%.4x'] * 8).join(':') % addr.unpack('n8')
83
+ else
84
+ raise ArgumentError, 'Invalid address value'
85
+ end
86
+ s
87
+ end
88
+
89
+ # Return the string representation of the address, x.x.x.x[/pfxlen][@ctx]
90
+ def to_s
91
+ ctx ? "#{to_addrlen}@#{ctx}" : to_addrlen
92
+ end
93
+
94
+ # Return the string representation of the IP address and prefix, or
95
+ # just the IP address if it's a single address
96
+ def to_addrlen
97
+ pfxlen == self.class::ADDR_BITS ? to_addr : "#{to_addr}/#{pfxlen}"
98
+ end
99
+
100
+ # Return the address as an Integer
101
+ def to_i
102
+ @addr
103
+ end
104
+
105
+ # returns the address in Binary
106
+ def to_b
107
+ @addr.to_s(2).to_i
108
+ end
109
+
110
+ # Return the address as a hexadecimal string (8 or 32 digits)
111
+ def to_hex
112
+ @addr.to_s(16).rjust(self.class::ADDR_BITS >> 2, '0')
113
+ end
114
+
115
+ # Return an array representation of the address, with 3 or 4 elements
116
+ # depending on whether there is a routing context set.
117
+ # ["v4", 16909060, 28]
118
+ # ["v4", 16909060, 28, "context"]
119
+ # (Removing the last element makes them Comparable, as nil.<=> doesn't exist)
120
+ def to_a
121
+ if @ctx
122
+ [self.class::PROTO, @addr, @pfxlen, @ctx]
123
+ else
124
+ [self.class::PROTO, @addr, @pfxlen]
125
+ end
126
+ end
127
+
128
+ # Return an array representation of the address, with 3 or 4 elements
129
+ # depending on whether there is a routing context set, using hexadecimal.
130
+ # ["v4", "01020304", 28]
131
+ # ["v4", "01020304", 28, "context"]
132
+ def to_ah
133
+ if @ctx
134
+ [self.class::PROTO, to_hex, @pfxlen, @ctx]
135
+ else
136
+ [self.class::PROTO, to_hex, @pfxlen]
137
+ end
138
+ end
139
+
140
+ # Change the prefix length. If nil, the maximum is used (32 or 128)
141
+ def pfxlen=(pfxlen)
142
+ @mask = nil
143
+ if pfxlen
144
+ pfxlen = pfxlen.to_i
145
+ raise ArgumentError, 'Invalid prefix length' if pfxlen < 0 || pfxlen > self.class::ADDR_BITS
146
+
147
+ @pfxlen = pfxlen
148
+ else
149
+ @pfxlen = self.class::ADDR_BITS
150
+ end
151
+ end
152
+
153
+ # Return the mask for this pfxlen as an integer. For example,
154
+ # a V4 /24 address has a mask of 255 (0x000000ff)
155
+ def mask
156
+ @mask ||= (1 << (self.class::ADDR_BITS - @pfxlen)) - 1
157
+ end
158
+
159
+ # Return a new IP object at the base of the subnet, with an optional
160
+ # offset applied.
161
+ # IP.new("1.2.3.4/24").network => #<IP::V4 1.2.3.0/24>
162
+ # IP.new("1.2.3.4/24").network(7) => #<IP::V4 1.2.3.7/24>
163
+ def network(offset = 0)
164
+ self.class.new((@addr & ~mask) + offset, @pfxlen, @ctx)
165
+ end
166
+
167
+ # Return a new IP object at the top of the subnet, with an optional
168
+ # offset applied.
169
+ # IP.new("1.2.3.4/24").broadcast => #<IP::V4 1.2.3.255/24>
170
+ # IP.new("1.2.3.4/24").broadcast(-1) => #<IP::V4 1.2.3.254/24>
171
+ def broadcast(offset = 0)
172
+ self.class.new((@addr | mask) + offset, @pfxlen, @ctx)
173
+ end
174
+
175
+ # Return a new IP object representing the netmask
176
+ # IP.new("1.2.3.4/24").netmask => #<IP::V4 255.255.255.0>
177
+ def netmask
178
+ self.class.new(self.class::MASK & ~mask)
179
+ end
180
+
181
+ # Return a new IP object representing the wildmask (inverse netmask)
182
+ # IP.new("1.2.3.4/24").netmask => #<IP::V4 0.0.0.255>
183
+ def wildmask
184
+ self.class.new(mask)
185
+ end
186
+
187
+ # Masks the address such that it is the base of the subnet
188
+ # IP.new("1.2.3.4/24").mask! => #<IP::V4 1.2.3.0/24>
189
+ def mask!
190
+ @addr &= ~mask
191
+ self
192
+ end
193
+
194
+ # Returns true if this is not the base address of the subnet implied
195
+ # from the prefix length (e.g. 1.2.3.4/24 is offset, because the base
196
+ # is 1.2.3.0/24)
197
+ def offset?
198
+ @addr != (@addr & ~mask)
199
+ end
200
+
201
+ # Returns offset from base of subnet to this address
202
+ # IP.new("1.2.3.4/24").offset => 4
203
+ def offset
204
+ @addr - (@addr & ~mask)
205
+ end
206
+
207
+ # If the address is not on the base, turn it into a single IP.
208
+ # IP.new("1.2.3.4/24").reset_pfxlen! => <IP::V4 1.2.3.4>
209
+ # IP.new("1.2.3.0/24").reset_pfxlen! => <IP::V4 1.2.3.0/24>
210
+ def reset_pfxlen!
211
+ self.pfxlen = nil if offset?
212
+ self
213
+ end
214
+
215
+ def to_irange
216
+ a1 = @addr & ~mask
217
+ a2 = a1 | mask
218
+ (a1..a2)
219
+ end
220
+
221
+ # QUERY: IPAddr (1.9) turns 1.2.3.0/24 into 1.2.3.0/24..1.2.3.255/24
222
+ # Here I turn it into 1.2.3.0..1.2.3.255. Which is better?
223
+ def to_range
224
+ self.class.new(@addr & ~mask, self.class::ADDR_BITS, @ctx)..self.class.new(@addr | mask, self.class::ADDR_BITS, @ctx)
225
+ end
226
+
227
+ # test if the address is in the provided subnet
228
+ def is_in?(subnet)
229
+ subnet.network.to_i <= network.to_i &&
230
+ subnet.broadcast.to_i >= broadcast.to_i
231
+ end
232
+
233
+ # this function sub-divides a subnet into two subnets of equal size
234
+ def split
235
+ nets = []
236
+ if pfxlen < self.class::ADDR_BITS
237
+ if self.class::ADDR_BITS == 32
238
+ new_base = IP::V4.new(network.to_i, (pfxlen + 1))
239
+ nets = [new_base, IP::V4.new((new_base.broadcast + 1).to_i, (pfxlen + 1))]
240
+ end
241
+ if self.class::ADDR_BITS == 128
242
+ new_base = IP::V6.new(network.to_i, (pfxlen + 1))
243
+ nets = [new_base, IP::V6.new((new_base.broadcast + 1).to_i, (pfxlen + 1))]
244
+ end
245
+ end
246
+ nets
247
+ end
248
+
249
+ # subdivide a larger subnet into smaller subnets by number of subnets of equal size,
250
+ # stop when subnets reach their smallest possible size (i.e. 31 for IP4)
251
+ def divide_by_subnets(number_subnets)
252
+ nets = []
253
+ return nets if split.empty?
254
+
255
+ nets << self
256
+ loop do
257
+ new_nets = []
258
+ nets.each do |net|
259
+ new_nets |= net.split
260
+ end
261
+ nets = new_nets
262
+ break if number_subnets <= nets.length &&
263
+ nets[0].pfxlen <= (self.class::ADDR_BITS - 1)
264
+ end
265
+ nets
266
+ end
267
+
268
+ # subdivide a larger subnet into smaller subnets by number of hosts
269
+ def divide_by_hosts(number_hosts)
270
+ nets = []
271
+ return nets if split.empty?
272
+
273
+ nets << self
274
+ while number_hosts <= (nets[0].split[0].size - 2) &&
275
+ nets[0].pfxlen <= (self.class::ADDR_BITS - 1)
276
+ new_nets = []
277
+ nets.each do |net|
278
+ new_nets |= net.split
279
+ end
280
+ nets = new_nets
281
+ end
282
+ nets
283
+ end
284
+
285
+ # deaggregate address range
286
+ # IP.new('1.2.0.0').deaggregate(IP.new('1.3.255.255'))
287
+ # => [#<IP::V4 1.2.0.0/15>]
288
+ # IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255'))
289
+ # => [#<IP::V4 1.2.0.0/15>, #<IP::V4 1.4.0.0/16>]
290
+ # IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff'))
291
+ # => [#<IP::V6 2001:db8:85a3:8d3::/64>]
292
+ # IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::'))
293
+ # => [#<IP::V6 2001:db8:85a3:8d3::/80>, #<IP::V6 2001:db8:85a3:8d3:1::>]
294
+ def deaggregate(other)
295
+ nets = []
296
+ base = to_i
297
+ while base <= other.to_i
298
+ step = 0
299
+ while (base | (1 << step)) != base
300
+ break if (base | (((~0) & self.class::ADDR_MAX) >> (self.class::ADDR_BITS - 1 - step))) > other.to_i
301
+
302
+ step += 1
303
+ end
304
+ nets << self.class.new(base, self.class::ADDR_BITS - step, @ctx)
305
+ base += 1 << step
306
+ end
307
+ nets
308
+ end
309
+
310
+ # The number of IP addresses in subnet
311
+ # IP.new("1.2.3.4/24").size => 256
312
+ def size
313
+ mask + 1
314
+ end
315
+
316
+ def +(other)
317
+ self.class.new(@addr + other.to_i, @pfxlen, @ctx)
318
+ end
319
+
320
+ def -(other)
321
+ self.class.new(@addr - other.to_i, @pfxlen, @ctx)
322
+ end
323
+
324
+ def &(other)
325
+ self.class.new(@addr & other.to_i, @pfxlen, @ctx)
326
+ end
327
+
328
+ def |(other)
329
+ self.class.new(@addr | other.to_i, @pfxlen, @ctx)
330
+ end
331
+
332
+ def ^(other)
333
+ self.class.new(@addr ^ other.to_i, @pfxlen, @ctx)
334
+ end
335
+
336
+ def ~
337
+ self.class.new(~@addr & self.class::MASK, @pfxlen, @ctx)
338
+ end
339
+
340
+ def succ
341
+ self.class.new(@addr + size, @pfxlen, @ctx)
342
+ end
343
+
344
+ def succ!
345
+ @addr += size
346
+ self
347
+ end
348
+
349
+ def inspect
350
+ "#<#{self.class} #{self}>"
351
+ end
352
+
353
+ def ipv4_mapped?
354
+ false
355
+ end
356
+
357
+ def ipv4_compat?
358
+ false
359
+ end
360
+
361
+ def native
362
+ self
363
+ end
364
+
365
+ def hash
366
+ to_a.hash
367
+ end
368
+
369
+ def freeze
370
+ mask
371
+ super
372
+ end
373
+
374
+ def eql?(other)
375
+ to_a.eql?(other.to_a)
376
+ end
377
+
378
+ def <=>(other)
379
+ to_a <=> other.to_a
380
+ end
381
+ include Comparable
382
+
383
+ class V4 < IP
384
+ class << self; alias new orig_new; end
385
+ PROTO = 'v4'
386
+ PROTO_TO_CLASS[PROTO] = self
387
+ ADDR_MAX = 4_294_967_295
388
+ ADDR_BITS = 32
389
+ MASK = (1 << ADDR_BITS) - 1
390
+ ARPA = '.in-addr.arpa.'
391
+
392
+ # Parse a string; return an V4 instance if it's a valid IPv4 address,
393
+ # nil otherwise
394
+ def self.parse(str)
395
+ if str =~ %r{\A(\d+)\.(\d+)\.(\d+)\.(\d+)(?:/(\d+))?(?:@(.*))?\z}
396
+ pfxlen = (Regexp.last_match[5] || ADDR_BITS).to_i
397
+ return nil if pfxlen > 32
398
+
399
+ addrs = [Regexp.last_match[1].to_i,
400
+ Regexp.last_match[2].to_i,
401
+ Regexp.last_match[3].to_i,
402
+ Regexp.last_match[4].to_i]
403
+ return nil if addrs.find { |n| n > 255 }
404
+
405
+ addr = (((((addrs[0] << 8) | addrs[1]) << 8) | addrs[2]) << 8) | addrs[3]
406
+ new(addr, pfxlen, Regexp.last_match[6])
407
+ end
408
+ end
409
+
410
+ # Return just the address part as a String in dotted decimal form
411
+ def to_addr
412
+ format('%d.%d.%d.%d',
413
+ (@addr >> 24) & 0xff,
414
+ (@addr >> 16) & 0xff,
415
+ (@addr >> 8) & 0xff,
416
+ @addr & 0xff)
417
+ end
418
+
419
+ # return the arpa version of the address for reverse DNS: http://en.wikipedia.org/wiki/Reverse_DNS_lookup
420
+ def to_arpa
421
+ format("%d.%d.%d.%d#{ARPA}",
422
+ @addr & 0xff,
423
+ (@addr >> 8) & 0xff,
424
+ (@addr >> 16) & 0xff,
425
+ (@addr >> 24) & 0xff)
426
+ end
427
+
428
+ # Returns a network byte ordered string form of the IP address.
429
+ def hton
430
+ [@addr].pack('N')
431
+ end
432
+ end
433
+
434
+ class V6 < IP
435
+ class << self; alias new orig_new; end
436
+ PROTO = 'v6'
437
+ PROTO_TO_CLASS[PROTO] = self
438
+ ADDR_MAX = 340_282_366_920_938_463_463_374_607_431_768_211_455
439
+ ADDR_BITS = 128
440
+ MASK = (1 << ADDR_BITS) - 1
441
+ ARPA = '.ip6.arpa'
442
+
443
+ # Parse a string; return an V6 instance if it's a valid IPv6 address,
444
+ # nil otherwise
445
+ #--
446
+ # FIXME: allow larger variations of mapped addrs like 0:0:0:0:ffff:1.2.3.4
447
+ #++
448
+ def self.parse(str)
449
+ case str
450
+ when %r{\A\[?::(ffff:)?(\d+\.\d+\.\d+\.\d+)\]?(?:/(\d+))?(?:@(.*))?\z}i
451
+ mapped = Regexp.last_match[1]
452
+ pfxlen = (Regexp.last_match[3] || 128).to_i
453
+ ctx = Regexp.last_match[4]
454
+ return nil if pfxlen > 128
455
+
456
+ v4 = (V4.parse(Regexp.last_match[2]) || return).to_i
457
+ v4 |= 0xffff00000000 if mapped
458
+ new(v4, pfxlen, ctx)
459
+ when %r{\A\[?([0-9a-f:]+)\]?(?:/(\d+))?(?:@(.*))?\z}i
460
+ addr = Regexp.last_match[1]
461
+ pfxlen = (Regexp.last_match[2] || 128).to_i
462
+ return nil if pfxlen > 128
463
+
464
+ ctx = Regexp.last_match[3]
465
+ return nil if pfxlen > 128
466
+
467
+ if addr =~ /\A(.*?)::(.*)\z/
468
+ left = Regexp.last_match[1]
469
+ right = Regexp.last_match[2]
470
+ l = left.split(':', -1)
471
+ r = right.split(':', -1)
472
+ rest = 8 - l.length - r.length
473
+ return nil if rest < 0
474
+ else
475
+ l = addr.split(':')
476
+ r = []
477
+ rest = 0
478
+ return nil if l.length != 8
479
+ end
480
+ out = ''
481
+ l.each do |quad|
482
+ return nil unless (1..4).include?(quad.length)
483
+
484
+ out << quad.rjust(4, '0')
485
+ end
486
+ rest.times { out << '0000' }
487
+ r.each do |quad|
488
+ return nil unless (1..4).include?(quad.length)
489
+
490
+ out << quad.rjust(4, '0')
491
+ end
492
+ new(out, pfxlen, ctx)
493
+ end
494
+ end
495
+
496
+ # Return just the address part as a String in compact decimal form
497
+ def to_addr
498
+ if ipv4_compat?
499
+ "::#{native.to_addr}"
500
+ elsif ipv4_mapped?
501
+ "::ffff:#{native.to_addr}"
502
+ elsif @addr.zero?
503
+ '::'
504
+ else
505
+ res = to_hex.scan(/..../).join(':')
506
+ res.gsub!(/\b0{1,3}/, '')
507
+ res.sub!(/\b0:0:0:0(:0)*\b/, ':') ||
508
+ res.sub!(/\b0:0:0\b/, ':') ||
509
+ res.sub!(/\b0:0\b/, ':')
510
+ res.sub!(/:::+/, '::')
511
+ res
512
+ end
513
+ end
514
+
515
+ # Return just the address in non-compact form, required for reverse IP.
516
+ def to_addr_full
517
+ if ipv4_compat?
518
+ "::#{native.to_addr}"
519
+ elsif ipv4_mapped?
520
+ "::ffff:#{native.to_addr}"
521
+ elsif @addr.zero?
522
+ '::'
523
+ else
524
+ to_hex.scan(/..../).join(':')
525
+ end
526
+ end
527
+
528
+ # Returns a network byte ordered string form of the IP address.
529
+ def hton
530
+ (0..7).map { |i| (@addr >> (112 - 16 * i)) & 0xffff }.pack('n8')
531
+ end
532
+
533
+ # Returns the address broken into an array of 32 nibbles. Useful for
534
+ # to_arpa and use in SPF - http://tools.ietf.org/html/rfc7208#section-7.3
535
+ def to_nibbles
536
+ to_hex.rjust(32, '0').split(//)
537
+ end
538
+
539
+ # return the arpa version of the address for reverse DNS: http://en.wikipedia.org/wiki/Reverse_DNS_lookup
540
+ def to_arpa
541
+ to_nibbles.reverse.join('.') + ARPA
542
+ end
543
+
544
+ def ipv4_mapped?
545
+ (@addr >> 32) == 0xffff
546
+ end
547
+
548
+ def ipv4_compat?
549
+ @addr > 1 && (@addr >> 32) == 0
550
+ end
551
+
552
+ # Convert an IPv6 mapped/compat address to a V4 native address
553
+ def native
554
+ return self unless (ipv4_mapped? || ipv4_compat?) && (@pfxlen >= 96)
555
+
556
+ V4.new(@addr & V4::MASK, @pfxlen - 96, @ctx)
557
+ end
558
+ end
559
+ end