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