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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +98 -0
- data/.travis.yml +5 -0
- data/COPYING.txt +340 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +58 -0
- data/README.rdoc +198 -0
- data/Rakefile +12 -0
- data/lib/ip.rb +4 -0
- data/lib/ip/base.rb +559 -0
- data/lib/ip/cpal.rb +24 -0
- data/lib/ip/socket.rb +36 -0
- data/lib/ip/version.rb +3 -0
- data/ruby-ip2.gemspec +36 -0
- metadata +139 -0
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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
|
+
|
data/README.rdoc
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
data/lib/ip.rb
ADDED
data/lib/ip/base.rb
ADDED
@@ -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
|