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.
@@ -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
@@ -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
@@ -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