ruby-ip2 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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