ip_ranger 0.0.2

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