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