construqt-ipaddress 0.8.1
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/.document +5 -0
- data/CHANGELOG.rdoc +105 -0
- data/LICENSE +20 -0
- data/README.rdoc +965 -0
- data/Rakefile +83 -0
- data/VERSION +1 -0
- data/ipaddress.gemspec +55 -0
- data/lib/ipaddress.rb +306 -0
- data/lib/ipaddress/ipv4.rb +1005 -0
- data/lib/ipaddress/ipv6.rb +1003 -0
- data/lib/ipaddress/prefix.rb +265 -0
- data/test/ipaddress/ipv4_test.rb +555 -0
- data/test/ipaddress/ipv6_test.rb +448 -0
- data/test/ipaddress/prefix_test.rb +159 -0
- data/test/ipaddress_test.rb +119 -0
- data/test/test_helper.rb +28 -0
- metadata +66 -0
data/Rakefile
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "ipaddress"
|
9
|
+
gem.summary = %Q{IPv4/IPv6 addresses manipulation library}
|
10
|
+
gem.email = "ceresa@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/bluemonk/ipaddress"
|
12
|
+
gem.authors = ["Marco Ceresa"]
|
13
|
+
gem.description = <<-EOD
|
14
|
+
IPAddress is a Ruby library designed to make manipulation
|
15
|
+
of IPv4 and IPv6 addresses both powerful and simple. It mantains
|
16
|
+
a layer of compatibility with Ruby's own IPAddr, while
|
17
|
+
addressing many of its issues.
|
18
|
+
EOD
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new(:test) do |test|
|
26
|
+
test.libs << 'lib' << 'test'
|
27
|
+
test.pattern = 'test/**/*_test.rb'
|
28
|
+
test.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'rcov/rcovtask'
|
33
|
+
Rcov::RcovTask.new do |test|
|
34
|
+
test.libs << 'test'
|
35
|
+
test.pattern = 'test/**/*_test.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
task :rcov do
|
40
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
require 'rdoc/task'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
if File.exist?('VERSION.yml')
|
50
|
+
config = YAML.load(File.read('VERSION.yml'))
|
51
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
52
|
+
else
|
53
|
+
version = ""
|
54
|
+
end
|
55
|
+
|
56
|
+
rdoc.rdoc_dir = 'rdoc'
|
57
|
+
rdoc.title = "ipaddress #{version}"
|
58
|
+
rdoc.rdoc_files.include('README*')
|
59
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "Open an irb session preloaded with this library"
|
63
|
+
task :console do
|
64
|
+
sh "irb -rubygems -I lib -r ipaddress.rb"
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Look for TODO and FIXME tags in the code"
|
68
|
+
task :todo do
|
69
|
+
def egrep(pattern)
|
70
|
+
Dir['**/*.rb'].each do |fn|
|
71
|
+
count = 0
|
72
|
+
open(fn) do |f|
|
73
|
+
while line = f.gets
|
74
|
+
count += 1
|
75
|
+
if line =~ pattern
|
76
|
+
puts "#{fn}:#{count}:#{line}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
egrep /(FIXME|TODO|TBD)/
|
83
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.8.0
|
data/ipaddress.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{construqt-ipaddress}
|
8
|
+
s.version = "0.8.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Marco Ceresa", "Meno Abels"]
|
12
|
+
s.date = %q{2014-11-29}
|
13
|
+
s.description = %q{ IPAddress is a Ruby library designed to make manipulation
|
14
|
+
of IPv4 and IPv6 addresses both powerful and simple. It mantains
|
15
|
+
a layer of compatibility with Ruby's own IPAddr, while
|
16
|
+
addressing many of its issues.
|
17
|
+
}
|
18
|
+
s.email = %q{meno.abels@construqt.me}
|
19
|
+
s.extra_rdoc_files = [
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc"
|
22
|
+
]
|
23
|
+
s.files = [
|
24
|
+
".document",
|
25
|
+
"CHANGELOG.rdoc",
|
26
|
+
"LICENSE",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"ipaddress.gemspec",
|
31
|
+
"lib/ipaddress.rb",
|
32
|
+
"lib/ipaddress/ipv4.rb",
|
33
|
+
"lib/ipaddress/ipv6.rb",
|
34
|
+
"lib/ipaddress/prefix.rb",
|
35
|
+
"test/ipaddress/ipv4_test.rb",
|
36
|
+
"test/ipaddress/ipv6_test.rb",
|
37
|
+
"test/ipaddress/prefix_test.rb",
|
38
|
+
"test/ipaddress_test.rb",
|
39
|
+
"test/test_helper.rb"
|
40
|
+
]
|
41
|
+
s.homepage = %q{http://github.com/bluemonk/ipaddress}
|
42
|
+
s.require_paths = ["lib"]
|
43
|
+
s.rubygems_version = %q{1.6.2}
|
44
|
+
s.summary = %q{IPv4/IPv6 addresses manipulation library}
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
else
|
51
|
+
end
|
52
|
+
else
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/lib/ipaddress.rb
ADDED
@@ -0,0 +1,306 @@
|
|
1
|
+
#
|
2
|
+
# = IPAddress
|
3
|
+
#
|
4
|
+
# A ruby library to manipulate IPv4 and IPv6 addresses
|
5
|
+
#
|
6
|
+
#
|
7
|
+
# Package:: IPAddress
|
8
|
+
# Author:: Marco Ceresa <ceresa@ieee.org>
|
9
|
+
# License:: Ruby License
|
10
|
+
#
|
11
|
+
#--
|
12
|
+
#
|
13
|
+
#++
|
14
|
+
|
15
|
+
require 'ipaddress/ipv4'
|
16
|
+
require 'ipaddress/ipv6'
|
17
|
+
|
18
|
+
module IPAddress
|
19
|
+
|
20
|
+
NAME = "IPAddress"
|
21
|
+
GEM = "ipaddress"
|
22
|
+
AUTHORS = ["Marco Ceresa <ceresa@ieee.org>"]
|
23
|
+
|
24
|
+
#
|
25
|
+
# Parse the argument string to create a new
|
26
|
+
# IPv4, IPv6 or Mapped IP object
|
27
|
+
#
|
28
|
+
# ip = IPAddress.parse "172.16.10.1/24"
|
29
|
+
# ip6 = IPAddress.parse "2001:db8::8:800:200c:417a/64"
|
30
|
+
# ip_mapped = IPAddress.parse "::ffff:172.16.10.1/128"
|
31
|
+
#
|
32
|
+
# All the object created will be instances of the
|
33
|
+
# correct class:
|
34
|
+
#
|
35
|
+
# ip.class
|
36
|
+
# #=> IPAddress::IPv4
|
37
|
+
# ip6.class
|
38
|
+
# #=> IPAddress::IPv6
|
39
|
+
# ip_mapped.class
|
40
|
+
# #=> IPAddress::IPv6::Mapped
|
41
|
+
#
|
42
|
+
def IPAddress::parse(str)
|
43
|
+
case str
|
44
|
+
when /:.+\./
|
45
|
+
IPAddress::IPv6::Mapped.new(str)
|
46
|
+
when /\./
|
47
|
+
IPAddress::IPv4.new(str)
|
48
|
+
when /:/
|
49
|
+
IPAddress::IPv6.new(str)
|
50
|
+
else
|
51
|
+
raise ArgumentError, "Unknown IP Address #{str}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# True if the object is an IPv4 address
|
57
|
+
#
|
58
|
+
# ip = IPAddress("192.168.10.100/24")
|
59
|
+
#
|
60
|
+
# ip.ipv4?
|
61
|
+
# #-> true
|
62
|
+
#
|
63
|
+
def ipv4?
|
64
|
+
self.kind_of? IPAddress::IPv4
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# True if the object is an IPv6 address
|
69
|
+
#
|
70
|
+
# ip = IPAddress("192.168.10.100/24")
|
71
|
+
#
|
72
|
+
# ip.ipv6?
|
73
|
+
# #-> false
|
74
|
+
#
|
75
|
+
def ipv6?
|
76
|
+
self.kind_of? IPAddress::IPv6
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Checks if the given string is a valid IP address,
|
81
|
+
# either IPv4 or IPv6
|
82
|
+
#
|
83
|
+
# Example:
|
84
|
+
#
|
85
|
+
# IPAddress::valid? "2002::1"
|
86
|
+
# #=> true
|
87
|
+
#
|
88
|
+
# IPAddress::valid? "10.0.0.256"
|
89
|
+
# #=> false
|
90
|
+
#
|
91
|
+
def self.valid?(addr)
|
92
|
+
valid_ipv4?(addr) || valid_ipv6?(addr)
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Checks if the given string is a valid IPv4 address
|
97
|
+
#
|
98
|
+
# Example:
|
99
|
+
#
|
100
|
+
# IPAddress::valid_ipv4? "2002::1"
|
101
|
+
# #=> false
|
102
|
+
#
|
103
|
+
# IPAddress::valid_ipv4? "172.16.10.1"
|
104
|
+
# #=> true
|
105
|
+
#
|
106
|
+
def self.valid_ipv4?(addr)
|
107
|
+
if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr
|
108
|
+
return $~.captures.all? {|i| i.to_i < 256}
|
109
|
+
end
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Checks if the argument is a valid IPv4 netmask
|
115
|
+
# expressed in dotted decimal format.
|
116
|
+
#
|
117
|
+
# IPAddress.valid_ipv4_netmask? "255.255.0.0"
|
118
|
+
# #=> true
|
119
|
+
#
|
120
|
+
def self.valid_ipv4_netmask?(addr)
|
121
|
+
arr = addr.split(".").map{|i| i.to_i}.pack("CCCC").unpack("B*").first.scan(/01/)
|
122
|
+
arr.empty? && valid_ipv4?(addr)
|
123
|
+
rescue
|
124
|
+
return false
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Checks if the given string is a valid IPv6 address
|
129
|
+
#
|
130
|
+
# Example:
|
131
|
+
#
|
132
|
+
# IPAddress::valid_ipv6? "2002::1"
|
133
|
+
# #=> true
|
134
|
+
#
|
135
|
+
# IPAddress::valid_ipv6? "2002::DEAD::BEEF"
|
136
|
+
# #=> false
|
137
|
+
#
|
138
|
+
def self.valid_ipv6?(addr)
|
139
|
+
# https://gist.github.com/cpetschnig/294476
|
140
|
+
# http://forums.intermapper.com/viewtopic.php?t=452
|
141
|
+
return true if /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/ =~ addr
|
142
|
+
false
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Deprecate method
|
147
|
+
#
|
148
|
+
def self.deprecate(message = nil) # :nodoc:
|
149
|
+
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
|
150
|
+
warn("DEPRECATION WARNING: #{message}")
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# private helper for summarize
|
155
|
+
# assumes that networks is output from reduce_networks
|
156
|
+
# means it should be sorted lowers first and uniq
|
157
|
+
#
|
158
|
+
def self.aggregate(networks)
|
159
|
+
stack = networks.map{|i| i.network }.sort! # make input imutable
|
160
|
+
pos = 0
|
161
|
+
while true
|
162
|
+
pos = pos < 0 ? 0 : pos # start @beginning
|
163
|
+
first = stack[pos]
|
164
|
+
unless first
|
165
|
+
break
|
166
|
+
end
|
167
|
+
pos += 1
|
168
|
+
second = stack[pos]
|
169
|
+
unless second
|
170
|
+
break
|
171
|
+
end
|
172
|
+
pos += 1
|
173
|
+
if first.include?(second)
|
174
|
+
pos -= 2
|
175
|
+
stack.delete_at(pos+1)
|
176
|
+
else
|
177
|
+
first.prefix -= 1
|
178
|
+
if first.prefix+1 == second.prefix && first.include?(second)
|
179
|
+
pos -= 2
|
180
|
+
stack[pos] = first
|
181
|
+
stack.delete_at(pos+1)
|
182
|
+
pos -= 1 # backtrack
|
183
|
+
else
|
184
|
+
first.prefix += 1 #reset prefix
|
185
|
+
pos -= 1 # do it with second as first
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
stack[0..pos-1]
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Summarization (or aggregation) is the process when two or more
|
194
|
+
# networks are taken together to check if a supernet, including all
|
195
|
+
# and only these networks, exists. If it exists then this supernet
|
196
|
+
# is called the summarized (or aggregated) network.
|
197
|
+
#
|
198
|
+
# It is very important to understand that summarization can only
|
199
|
+
# occur if there are no holes in the aggregated network, or, in other
|
200
|
+
# words, if the given networks fill completely the address space
|
201
|
+
# of the supernet. So the two rules are:
|
202
|
+
#
|
203
|
+
# 1) The aggregate network must contain +all+ the IP addresses of the
|
204
|
+
# original networks;
|
205
|
+
# 2) The aggregate network must contain +only+ the IP addresses of the
|
206
|
+
# original networks;
|
207
|
+
#
|
208
|
+
# A few examples will help clarify the above. Let's consider for
|
209
|
+
# instance the following two networks:
|
210
|
+
#
|
211
|
+
# ip1 = IPAddress("172.16.10.0/24")
|
212
|
+
# ip2 = IPAddress("172.16.11.0/24")
|
213
|
+
#
|
214
|
+
# These two networks can be expressed using only one IP address
|
215
|
+
# network if we change the prefix. Let Ruby do the work:
|
216
|
+
#
|
217
|
+
# IPAddress::IPv4::summarize(ip1,ip2).to_s
|
218
|
+
# #=> "172.16.10.0/23"
|
219
|
+
#
|
220
|
+
# We note how the network "172.16.10.0/23" includes all the addresses
|
221
|
+
# specified in the above networks, and (more important) includes
|
222
|
+
# ONLY those addresses.
|
223
|
+
#
|
224
|
+
# If we summarized +ip1+ and +ip2+ with the following network:
|
225
|
+
#
|
226
|
+
# "172.16.0.0/16"
|
227
|
+
#
|
228
|
+
# we would have satisfied rule #1 above, but not rule #2. So "172.16.0.0/16"
|
229
|
+
# is not an aggregate network for +ip1+ and +ip2+.
|
230
|
+
#
|
231
|
+
# If it's not possible to compute a single aggregated network for all the
|
232
|
+
# original networks, the method returns an array with all the aggregate
|
233
|
+
# networks found. For example, the following four networks can be
|
234
|
+
# aggregated in a single /22:
|
235
|
+
#
|
236
|
+
# ip1 = IPAddress("10.0.0.1/24")
|
237
|
+
# ip2 = IPAddress("10.0.1.1/24")
|
238
|
+
# ip3 = IPAddress("10.0.2.1/24")
|
239
|
+
# ip4 = IPAddress("10.0.3.1/24")
|
240
|
+
#
|
241
|
+
# IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_string
|
242
|
+
# #=> "10.0.0.0/22",
|
243
|
+
#
|
244
|
+
# But the following networks can't be summarized in a single network:
|
245
|
+
#
|
246
|
+
# ip1 = IPAddress("10.0.1.1/24")
|
247
|
+
# ip2 = IPAddress("10.0.2.1/24")
|
248
|
+
# ip3 = IPAddress("10.0.3.1/24")
|
249
|
+
# ip4 = IPAddress("10.0.4.1/24")
|
250
|
+
#
|
251
|
+
# IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string}
|
252
|
+
# #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"]
|
253
|
+
#
|
254
|
+
def self.summarize(networks)
|
255
|
+
aggregate(networks.map{|i| ((i.kind_of?(String)&&IPAddress.parse(i))||i) })
|
256
|
+
end
|
257
|
+
|
258
|
+
end # module IPAddress
|
259
|
+
|
260
|
+
#
|
261
|
+
# IPAddress is a wrapper method built around
|
262
|
+
# IPAddress's library classes. Its purpouse is to
|
263
|
+
# make you indipendent from the type of IP address
|
264
|
+
# you're going to use.
|
265
|
+
#
|
266
|
+
# For example, instead of creating the three types
|
267
|
+
# of IP addresses using their own contructors
|
268
|
+
#
|
269
|
+
# ip = IPAddress::IPv4.new "172.16.10.1/24"
|
270
|
+
# ip6 = IPAddress::IPv6.new "2001:db8::8:800:200c:417a/64"
|
271
|
+
# ip_mapped = IPAddress::IPv6::Mapped "::ffff:172.16.10.1/128"
|
272
|
+
#
|
273
|
+
# you can just use the IPAddress wrapper:
|
274
|
+
#
|
275
|
+
# ip = IPAddress "172.16.10.1/24"
|
276
|
+
# ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
|
277
|
+
# ip_mapped = IPAddress "::ffff:172.16.10.1/128"
|
278
|
+
#
|
279
|
+
# All the object created will be instances of the
|
280
|
+
# correct class:
|
281
|
+
#
|
282
|
+
# ip.class
|
283
|
+
# #=> IPAddress::IPv4
|
284
|
+
# ip6.class
|
285
|
+
# #=> IPAddress::IPv6
|
286
|
+
# ip_mapped.class
|
287
|
+
# #=> IPAddress::IPv6::Mapped
|
288
|
+
#
|
289
|
+
def IPAddress(str)
|
290
|
+
IPAddress::parse str
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Compatibility with Ruby 1.8
|
295
|
+
#
|
296
|
+
if RUBY_VERSION =~ /1\.8/
|
297
|
+
class Hash # :nodoc:
|
298
|
+
alias :key :index
|
299
|
+
end
|
300
|
+
module Math # :nodoc:
|
301
|
+
def Math.log2(n)
|
302
|
+
log(n) / log(2)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|