ip_ranger 0.0.2
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/lib/ip_ranger.rb +1 -0
- data/lib/ip_ranger/ip_address.rb +47 -0
- data/lib/ip_ranger/ip_range.rb +93 -0
- data/spec/ip_ranger/ip_address_spec.rb +73 -0
- data/spec/ip_ranger/ip_range_spec.rb +88 -0
- data/spec/spec_helper.rb +19 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8974752f76e75a2534351500872629a61731fa1f
|
4
|
+
data.tar.gz: 8338b0d52238c2ff2264d4e8b0ada85893babb9a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e113d83b46a2897a7e9acabaec026cead9c1725607e537cb00184c72485307797f227735d58d18b6c6af4651ebfea32793b42a117bb026838a0a33087c4a0936
|
7
|
+
data.tar.gz: ccade4126370c6c62d7784e63e3880ba18bb39a77bc334c8e012fb0a11d60e467d6509987fd4e8a3eab0dbe0f80e1bb6f1f37611091909eb35a2594c6830c51d
|
data/lib/ip_ranger.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ip_ranger/ip_range'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module IPRanger
|
5
|
+
class IPAddress < DelegateClass(IPAddr)
|
6
|
+
def self.from_integer(i, family, mask = nil)
|
7
|
+
address = IPAddr.new(i, family)
|
8
|
+
address = address.mask(mask) if mask
|
9
|
+
|
10
|
+
new(address)
|
11
|
+
end
|
12
|
+
|
13
|
+
def width
|
14
|
+
ipv4? ? 32 : 128
|
15
|
+
end
|
16
|
+
|
17
|
+
def succ
|
18
|
+
self.class.new(super)
|
19
|
+
end
|
20
|
+
|
21
|
+
def pred
|
22
|
+
self.class.new(IPAddr.new(to_i - 1, family))
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_cidr
|
26
|
+
"#{to_string}/#{prefixlen}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def prefixlen
|
30
|
+
mask_addr.to_s(2).count('1')
|
31
|
+
end
|
32
|
+
|
33
|
+
def first
|
34
|
+
to_range.first.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def last
|
38
|
+
to_range.last.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def mask_addr
|
44
|
+
__getobj__.instance_variable_get('@mask_addr')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'ip_ranger/ip_address'
|
2
|
+
|
3
|
+
module IPRanger
|
4
|
+
class IPRange
|
5
|
+
attr_reader :start, :finish
|
6
|
+
|
7
|
+
def initialize(start, finish)
|
8
|
+
@start = IPAddress.new(start.is_a?(IPAddr) ? start : IPAddr.new(start))
|
9
|
+
@finish = IPAddress.new(finish.is_a?(IPAddr) ? finish : IPAddr.new(finish))
|
10
|
+
|
11
|
+
fail ArgumentError, 'IP sequence cannot contain both IPv4 and IPv6!' if @start.family != @finish.family
|
12
|
+
end
|
13
|
+
|
14
|
+
def cidrs
|
15
|
+
cidr_list = []
|
16
|
+
cidr_span = spanning_cidr
|
17
|
+
|
18
|
+
if cidr_span.first < first
|
19
|
+
exclude = first.pred
|
20
|
+
cidr_list = cidr_partition(cidr_span, exclude).last
|
21
|
+
cidr_span = cidr_list.pop
|
22
|
+
end
|
23
|
+
|
24
|
+
if cidr_span.last > last
|
25
|
+
exclude = last.succ
|
26
|
+
cidr_list += cidr_partition(cidr_span, exclude).first
|
27
|
+
else
|
28
|
+
cidr_list << cidr_span
|
29
|
+
end
|
30
|
+
|
31
|
+
cidr_list
|
32
|
+
end
|
33
|
+
|
34
|
+
def first
|
35
|
+
start.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def last
|
39
|
+
finish.last
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def spanning_cidr
|
45
|
+
ipnum = last
|
46
|
+
prefixlen = finish.prefixlen
|
47
|
+
lowest_ipnum = first
|
48
|
+
width = finish.width
|
49
|
+
|
50
|
+
while prefixlen > 0 && ipnum > lowest_ipnum
|
51
|
+
prefixlen -= 1
|
52
|
+
ipnum &= -(1 << (width - prefixlen))
|
53
|
+
end
|
54
|
+
|
55
|
+
IPAddress.from_integer(ipnum, finish.family, prefixlen)
|
56
|
+
end
|
57
|
+
|
58
|
+
def cidr_partition(target, exclude)
|
59
|
+
exclude = IPAddress.from_integer(exclude, target.family)
|
60
|
+
return [], [], [target] if exclude.last < target.first
|
61
|
+
return [target], [], [] if target.last < exclude.first
|
62
|
+
return [], [target], [] if target.prefixlen >= exclude.prefixlen
|
63
|
+
|
64
|
+
left = []
|
65
|
+
right = []
|
66
|
+
|
67
|
+
new_prefixlen = target.prefixlen + 1
|
68
|
+
target_width = target.width
|
69
|
+
|
70
|
+
target_first = target.first
|
71
|
+
i_lower = target_first
|
72
|
+
i_upper = target_first + (2 ** (target_width - new_prefixlen))
|
73
|
+
|
74
|
+
while exclude.prefixlen >= new_prefixlen
|
75
|
+
if exclude.first >= i_upper
|
76
|
+
left << IPAddress.from_integer(i_lower, target.family, new_prefixlen)
|
77
|
+
matched = i_upper
|
78
|
+
else
|
79
|
+
right << IPAddress.from_integer(i_upper, target.family, new_prefixlen)
|
80
|
+
matched = i_lower
|
81
|
+
end
|
82
|
+
|
83
|
+
new_prefixlen += 1
|
84
|
+
break if new_prefixlen > target_width
|
85
|
+
|
86
|
+
i_lower = matched
|
87
|
+
i_upper = matched + (2 ** (target_width - new_prefixlen))
|
88
|
+
end
|
89
|
+
|
90
|
+
[left, [exclude], right.reverse]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'ip_ranger'
|
2
|
+
|
3
|
+
RSpec.describe IPRanger::IPAddress do
|
4
|
+
describe '#to_cidr' do
|
5
|
+
it 'returns a full address with netmask' do
|
6
|
+
address = described_class.new(IPAddr.new('192.168.0.0/20'))
|
7
|
+
|
8
|
+
expect(address.to_cidr).to eq('192.168.0.0/20')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#width' do
|
13
|
+
it 'returns 32 for an IPv4' do
|
14
|
+
address = described_class.new(IPAddr.new('192.168.1.1'))
|
15
|
+
|
16
|
+
expect(address.width).to eq(32)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns 128 for an IPv6' do
|
20
|
+
address = described_class.new(IPAddr.new('::1'))
|
21
|
+
|
22
|
+
expect(address.width).to eq(128)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#succ' do
|
27
|
+
it 'returns the succeeding IPv4 address' do
|
28
|
+
address = described_class.new(IPAddr.new('192.168.1.1'))
|
29
|
+
|
30
|
+
expect(address.succ).to eq(IPAddr.new('192.168.1.2'))
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns the succeeding IPv6 address' do
|
34
|
+
address = described_class.new(IPAddr.new('::1'))
|
35
|
+
|
36
|
+
expect(address.succ).to eq(IPAddr.new('::2'))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#pred' do
|
41
|
+
it 'returns the preceeding IPv4 address' do
|
42
|
+
address = described_class.new(IPAddr.new('192.168.1.2'))
|
43
|
+
|
44
|
+
expect(address.pred).to eq(IPAddr.new('192.168.1.1'))
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns the preceeding IPv6 address' do
|
48
|
+
address = described_class.new(IPAddr.new('::2'))
|
49
|
+
|
50
|
+
expect(address.pred).to eq(IPAddr.new('::1'))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#prefixlen' do
|
55
|
+
it 'returns a length of 32 if a single IPv4 address' do
|
56
|
+
address = described_class.new(IPAddr.new('192.168.1.1'))
|
57
|
+
|
58
|
+
expect(address.prefixlen).to eq(32)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns a length of 128 if a single IPv6 address' do
|
62
|
+
address = described_class.new(IPAddr.new('::1'))
|
63
|
+
|
64
|
+
expect(address.prefixlen).to eq(128)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns the length if masked' do
|
68
|
+
address = described_class.new(IPAddr.new('192.168.1.1/24'))
|
69
|
+
|
70
|
+
expect(address.prefixlen).to eq(24)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'ip_ranger'
|
2
|
+
|
3
|
+
RSpec.describe IPRanger::IPRange do
|
4
|
+
it 'converts an IP range to two IPs' do
|
5
|
+
ip_range = described_class.new('1.1.1.1', '1.1.1.2')
|
6
|
+
|
7
|
+
expect(ip_range.cidrs).to contain_exactly(IPAddr.new('1.1.1.1/32'), IPAddr.new('1.1.1.2/32'))
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'converts an IP range with two common subnets to CIDR blocks' do
|
11
|
+
ip_range = described_class.new('1.1.3.5', '1.1.11.50')
|
12
|
+
|
13
|
+
expect(ip_range.cidrs).to contain_exactly(
|
14
|
+
IPAddr.new('1.1.3.5/32'),
|
15
|
+
IPAddr.new('1.1.3.6/31'),
|
16
|
+
IPAddr.new('1.1.3.8/29'),
|
17
|
+
IPAddr.new('1.1.3.16/28'),
|
18
|
+
IPAddr.new('1.1.3.32/27'),
|
19
|
+
IPAddr.new('1.1.3.64/26'),
|
20
|
+
IPAddr.new('1.1.3.128/25'),
|
21
|
+
IPAddr.new('1.1.4.0/22'),
|
22
|
+
IPAddr.new('1.1.8.0/23'),
|
23
|
+
IPAddr.new('1.1.10.0/24'),
|
24
|
+
IPAddr.new('1.1.11.0/27'),
|
25
|
+
IPAddr.new('1.1.11.32/28'),
|
26
|
+
IPAddr.new('1.1.11.48/31'),
|
27
|
+
IPAddr.new('1.1.11.50/32')
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'converts an IP range with no common subnets to CIDR blocks' do
|
32
|
+
ip_range = described_class.new('192.168.0.1', '200.50.50.50')
|
33
|
+
|
34
|
+
expect(ip_range.cidrs).to contain_exactly(
|
35
|
+
IPAddr.new('192.168.0.1/32'),
|
36
|
+
IPAddr.new('192.168.0.2/31'),
|
37
|
+
IPAddr.new('192.168.0.4/30'),
|
38
|
+
IPAddr.new('192.168.0.8/29'),
|
39
|
+
IPAddr.new('192.168.0.16/28'),
|
40
|
+
IPAddr.new('192.168.0.32/27'),
|
41
|
+
IPAddr.new('192.168.0.64/26'),
|
42
|
+
IPAddr.new('192.168.0.128/25'),
|
43
|
+
IPAddr.new('192.168.1.0/24'),
|
44
|
+
IPAddr.new('192.168.2.0/23'),
|
45
|
+
IPAddr.new('192.168.4.0/22'),
|
46
|
+
IPAddr.new('192.168.8.0/21'),
|
47
|
+
IPAddr.new('192.168.16.0/20'),
|
48
|
+
IPAddr.new('192.168.32.0/19'),
|
49
|
+
IPAddr.new('192.168.64.0/18'),
|
50
|
+
IPAddr.new('192.168.128.0/17'),
|
51
|
+
IPAddr.new('192.169.0.0/16'),
|
52
|
+
IPAddr.new('192.170.0.0/15'),
|
53
|
+
IPAddr.new('192.172.0.0/14'),
|
54
|
+
IPAddr.new('192.176.0.0/12'),
|
55
|
+
IPAddr.new('192.192.0.0/10'),
|
56
|
+
IPAddr.new('193.0.0.0/8'),
|
57
|
+
IPAddr.new('194.0.0.0/7'),
|
58
|
+
IPAddr.new('196.0.0.0/6'),
|
59
|
+
IPAddr.new('200.0.0.0/11'),
|
60
|
+
IPAddr.new('200.32.0.0/12'),
|
61
|
+
IPAddr.new('200.48.0.0/15'),
|
62
|
+
IPAddr.new('200.50.0.0/19'),
|
63
|
+
IPAddr.new('200.50.32.0/20'),
|
64
|
+
IPAddr.new('200.50.48.0/23'),
|
65
|
+
IPAddr.new('200.50.50.0/27'),
|
66
|
+
IPAddr.new('200.50.50.32/28'),
|
67
|
+
IPAddr.new('200.50.50.48/31'),
|
68
|
+
IPAddr.new('200.50.50.50/32')
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns one address when the range is a single IP' do
|
73
|
+
ip_range = described_class.new('192.168.1.1', '192.168.1.1')
|
74
|
+
|
75
|
+
expect(ip_range.cidrs).to contain_exactly(IPAddr.new('192.168.1.1/32'))
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'converts an IPv6 range to CIDR blocks' do
|
79
|
+
ip_range = described_class.new('2001:db8::', '2001:db8:0000:0000:0000:0000:0000:0001')
|
80
|
+
|
81
|
+
expect(ip_range.cidrs).to contain_exactly(IPAddr.new('2001:db8::/127'))
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'raises if given incompatible IP addresses' do
|
85
|
+
expect { described_class.new('192.168.1.1', '2001:0db8:0000:0042:0000:8a2e:0370:7334') }.
|
86
|
+
to raise_error('IP sequence cannot contain both IPv4 and IPv6!')
|
87
|
+
end
|
88
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.expect_with :rspec do |expectations|
|
3
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
4
|
+
end
|
5
|
+
|
6
|
+
config.mock_with :rspec do |mocks|
|
7
|
+
mocks.verify_partial_doubles = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
11
|
+
config.filter_run_when_matching :focus
|
12
|
+
config.example_status_persistence_file_path = 'spec/examples.txt'
|
13
|
+
config.disable_monkey_patching!
|
14
|
+
config.warnings = true
|
15
|
+
config.default_formatter = 'doc' if config.files_to_run.one?
|
16
|
+
config.profile_examples = 10
|
17
|
+
config.order = :random
|
18
|
+
Kernel.srand config.seed
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ip_ranger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Maciej Gajewski
|
8
|
+
- Paul Mucur
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-10-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.5'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.5'
|
28
|
+
description:
|
29
|
+
email: support@altmetric.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/ip_ranger.rb
|
35
|
+
- lib/ip_ranger/ip_address.rb
|
36
|
+
- lib/ip_ranger/ip_range.rb
|
37
|
+
- spec/ip_ranger/ip_address_spec.rb
|
38
|
+
- spec/ip_ranger/ip_range_spec.rb
|
39
|
+
- spec/spec_helper.rb
|
40
|
+
homepage: https://github.com/altmetric/ip_ranger
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 2.5.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: A utility for converting IP ranges to CIDR subnets
|
64
|
+
test_files:
|
65
|
+
- spec/ip_ranger/ip_address_spec.rb
|
66
|
+
- spec/ip_ranger/ip_range_spec.rb
|
67
|
+
- spec/spec_helper.rb
|