subnets 1.0.0pre
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/COPYING +30 -0
- data/README.md +86 -0
- data/ext/subnets/ext.c +1007 -0
- data/ext/subnets/extconf.rb +6 -0
- data/ext/subnets/ipaddr.c +402 -0
- data/ext/subnets/ipaddr.h +105 -0
- data/test/benchmark_helper.rb +59 -0
- data/test/eql_and_hash.rb +35 -0
- data/test/private_networks_benchmark.rb +53 -0
- data/test/rack_ip_benchmark.rb +45 -0
- data/test/rails_remote_ip_benchmark.rb +104 -0
- data/test/subnets/net4_test.rb +74 -0
- data/test/subnets/net6_test.rb +125 -0
- data/test/subnets_test.rb +35 -0
- data/test/test_helper.rb +30 -0
- data/test/well_known_subnets.rb +68 -0
- metadata +204 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'ipaddr'
|
3
|
+
require 'ipaddress'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
require 'subnets'
|
7
|
+
require 'well_known_subnets'
|
8
|
+
|
9
|
+
# RANDOM_IPS = (
|
10
|
+
# (1..1000).map{random_ipv4(CLOUDFRONT_SUBNETS.sample)} +
|
11
|
+
# (1..3000).map{PRIVATE_SUBNETS_IPV4.sample}
|
12
|
+
# (1..4000).map{random_ipv4}
|
13
|
+
# ).shuffle
|
14
|
+
|
15
|
+
def random_ip(subnet)
|
16
|
+
net = Subnets.parse(subnet)
|
17
|
+
Subnets::IP4.random & ~net.mask | net.address
|
18
|
+
end
|
19
|
+
|
20
|
+
def plotbarslogscale(prefix: '%-15.15s %5.2f ', width:, min:, max:, tics:[], data:{})
|
21
|
+
pos = proc { |x| (Math.log10(x) - Math.log10(min)) * width/Math.log10(max) }
|
22
|
+
|
23
|
+
# https://en.wikipedia.org/wiki/Block_Elements
|
24
|
+
eighths = {
|
25
|
+
8 => "\u2588",
|
26
|
+
7 => "\u2589",
|
27
|
+
6 => "\u258a",
|
28
|
+
5 => "\u258b",
|
29
|
+
4 => "\u258c",
|
30
|
+
3 => "\u258d",
|
31
|
+
2 => "\u258e",
|
32
|
+
1 => "\u258f",
|
33
|
+
0 => "",
|
34
|
+
}
|
35
|
+
|
36
|
+
bar = proc do |w|
|
37
|
+
eighths[8]*(w.floor) + eighths[((w - w.floor)*8).round]
|
38
|
+
end
|
39
|
+
|
40
|
+
prefixwidth = 0
|
41
|
+
data.each do |k,v|
|
42
|
+
str = prefix % [k,v]
|
43
|
+
prefixwidth = [prefixwidth, str.size].max
|
44
|
+
puts(str + bar.call(pos.call(v)))
|
45
|
+
end
|
46
|
+
|
47
|
+
if tics.size > 0
|
48
|
+
ticbar = ''
|
49
|
+
labelbar = ''
|
50
|
+
tics.each do |tic|
|
51
|
+
x = pos.call(tic).round
|
52
|
+
ticbar += (' '*(x - ticbar.size) + "'")
|
53
|
+
labelbar += (' '*(x - labelbar.size) + "#{tic}")
|
54
|
+
end
|
55
|
+
|
56
|
+
puts(' '*prefixwidth + ticbar)
|
57
|
+
puts(' '*prefixwidth + labelbar)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module EqlAndHash
|
2
|
+
def new_obj(c=klass)
|
3
|
+
c.new(*constructor_args)
|
4
|
+
end
|
5
|
+
|
6
|
+
def new_obj_subclass
|
7
|
+
new_obj(Class.new(klass))
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_eq
|
11
|
+
a, b = [new_obj, new_obj]
|
12
|
+
refute_equal a.object_id, b.object_id
|
13
|
+
assert_equal a, b
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_eq_subclass
|
17
|
+
refute_equal new_obj, new_obj_subclass
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_eq_string
|
21
|
+
refute_equal new_obj, 'str'
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_hash
|
25
|
+
a, b = [new_obj, new_obj]
|
26
|
+
refute_equal a.object_id, b.object_id
|
27
|
+
assert_equal a.hash, b.hash
|
28
|
+
|
29
|
+
h = {}
|
30
|
+
h[a] = 'found'
|
31
|
+
assert_equal 'found', h[a]
|
32
|
+
assert_equal 'found', h[b]
|
33
|
+
assert_nil h[new_obj_subclass]
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'benchmark_helper'
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
require 'ipaddress'
|
5
|
+
require 'netaddr'
|
6
|
+
require 'subnets'
|
7
|
+
require 'rack'
|
8
|
+
|
9
|
+
nets = PRIVATE_SUBNETS_IPV4
|
10
|
+
ipaddr_nets = nets.map(&IPAddr.method(:new))
|
11
|
+
ipaddress_nets = nets.map(&IPAddress::IPv4.method(:new))
|
12
|
+
netaddr_nets = nets.map(&NetAddr::IPv4Net.method(:parse))
|
13
|
+
subnets_nets = nets.map(&Subnets.method(:parse))
|
14
|
+
rack_nets = Rack::Request.new({})
|
15
|
+
|
16
|
+
# note: ipaddress and netaddr checks would need additional cases to handle ipv6 too
|
17
|
+
ipaddr_check = lambda {|ip| ipaddr_nets.any? { |net| net.include? ip } }
|
18
|
+
ipaddress_check = lambda {|ip| ipaddress_nets.any? { |net| net.include?(IPAddress::IPv4.new(ip)) } }
|
19
|
+
netaddr_check = lambda {|ip| netaddr_nets.any? { |net| net.contains(NetAddr::IPv4.parse(ip)) } }
|
20
|
+
subnets_check = lambda {|ip| Subnets.include?(subnets_nets, ip) }
|
21
|
+
rack_check = lambda {|ip| rack_nets.trusted_proxy?(ip) }
|
22
|
+
|
23
|
+
def ipaddr_check.name; 'ipaddr'; end
|
24
|
+
def ipaddress_check.name; 'ipaddress'; end
|
25
|
+
def netaddr_check.name; 'netaddr'; end
|
26
|
+
def subnets_check.name; 'subnets'; end
|
27
|
+
def rack_check.name; 'rack (regexp)'; end
|
28
|
+
|
29
|
+
results = {}
|
30
|
+
|
31
|
+
puts '#'*60
|
32
|
+
puts "# check if single IP is in the private IPv4 subnets"
|
33
|
+
[ipaddr_check, ipaddress_check, netaddr_check, subnets_check, rack_check].each do |check|
|
34
|
+
total = count = hits = 0
|
35
|
+
until total >= 2
|
36
|
+
net = Subnets.parse((['0.0.0.0/0'] + PRIVATE_SUBNETS_IPV4).sample)
|
37
|
+
ip = (Subnets::IP4.random & ~net.mask | net.address).to_s
|
38
|
+
|
39
|
+
total += Benchmark.measure {
|
40
|
+
100.times do |i|
|
41
|
+
hits += 1 if check.call(ip)
|
42
|
+
end
|
43
|
+
}.total
|
44
|
+
count += 100
|
45
|
+
end
|
46
|
+
puts "%10.10s: checked %8d (%6d hits, %2d%%) in %2.2f for %7.2fus/ip" %
|
47
|
+
[check.name, count, hits, 100.0*hits/count, total, total/count*1e6]
|
48
|
+
|
49
|
+
results[check.name] = total/count*1e6
|
50
|
+
end
|
51
|
+
|
52
|
+
puts
|
53
|
+
plotbarslogscale(prefix: '%-15.15s: %5.2fus/ip ', width: 46, min: 2, max: 65, tics: [2,5,10,20,50], data: results)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'benchmark_helper'
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
def rack_trusted_proxy_benchmark(name, request)
|
6
|
+
total = count = hits = 0
|
7
|
+
until total >= 3
|
8
|
+
ip = random_ip((['0.0.0.0/0'] + PRIVATE_SUBNETS_IPV4).sample).to_s
|
9
|
+
|
10
|
+
100.times {
|
11
|
+
is_trusted = false
|
12
|
+
total += Benchmark.measure {
|
13
|
+
is_trusted = request.trusted_proxy?(ip)
|
14
|
+
}.total
|
15
|
+
hits += 1 if is_trusted
|
16
|
+
count += 1
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
puts
|
21
|
+
puts "checked %d IPs @ %.2fμs/ip (%d%% trusted)" %
|
22
|
+
[count, 1e6*total/count, 100.0*hits/count]
|
23
|
+
plotbarslogscale(
|
24
|
+
prefix: '%-12.12s %4.2fμs/ip ', width: 36, min: 1, max: 5, tics: [1,2,5],
|
25
|
+
data: { name => 1e6*total/count })
|
26
|
+
end
|
27
|
+
|
28
|
+
rack_trusted_proxy_benchmark(
|
29
|
+
'rack', Rack::Request.new({}))
|
30
|
+
|
31
|
+
subnets = PRIVATE_SUBNETS.map(&Subnets.method(:parse))
|
32
|
+
def subnets.trusted_proxy?(ip)
|
33
|
+
Subnets.include?(self, ip)
|
34
|
+
end
|
35
|
+
|
36
|
+
rack_trusted_proxy_benchmark(
|
37
|
+
'subnets', subnets)
|
38
|
+
|
39
|
+
subnets_cf = (PRIVATE_SUBNETS + CLOUDFRONT_SUBNETS).map(&Subnets.method(:parse))
|
40
|
+
def subnets_cf.trusted_proxy?(ip)
|
41
|
+
Subnets.include?(self, ip)
|
42
|
+
end
|
43
|
+
|
44
|
+
rack_trusted_proxy_benchmark(
|
45
|
+
'subnets +CF', subnets_cf)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'benchmark_helper'
|
2
|
+
|
3
|
+
require 'action_dispatch/middleware/remote_ip'
|
4
|
+
|
5
|
+
def benchmark(name, getip, mkrequest)
|
6
|
+
total = count = request_count = untrusted = 0
|
7
|
+
|
8
|
+
until total >= 1
|
9
|
+
request = mkrequest.call
|
10
|
+
|
11
|
+
total += Benchmark.measure {
|
12
|
+
untrusted += getip.send(:filter_proxies, request).size
|
13
|
+
}.total
|
14
|
+
|
15
|
+
count += request.size
|
16
|
+
request_count += 1
|
17
|
+
end
|
18
|
+
|
19
|
+
trusted = count - untrusted
|
20
|
+
puts
|
21
|
+
puts "checked %d requests at %.2fμs/req finding %d trusted ips (%d%%)" %
|
22
|
+
[request_count, total/request_count*1e6, trusted, 100.0*trusted/count]
|
23
|
+
|
24
|
+
plotbarslogscale(prefix: '%-15.15s %7.2fμs/req ',
|
25
|
+
width: 36, min: 1, max: 2000, tics: [1,10,100,1000],
|
26
|
+
data: { name => total/request_count*1e6 })
|
27
|
+
end
|
28
|
+
|
29
|
+
pub_priv_priv = ->() {
|
30
|
+
[
|
31
|
+
random_ip('0.0.0.0/0'),
|
32
|
+
random_ip(PRIVATE_SUBNETS_IPV4.sample),
|
33
|
+
random_ip(PRIVATE_SUBNETS_IPV4.sample),
|
34
|
+
].map(&:to_s)
|
35
|
+
}
|
36
|
+
|
37
|
+
############################################################
|
38
|
+
# as-is rails remote_ip
|
39
|
+
|
40
|
+
proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES
|
41
|
+
getip = ActionDispatch::RemoteIp::GetIp.new(nil, nil, proxies)
|
42
|
+
benchmark('remote_ip', getip, pub_priv_priv)
|
43
|
+
|
44
|
+
############################################################
|
45
|
+
# as-is rails remote_ip with CloudFront
|
46
|
+
|
47
|
+
pub_cf_priv_priv = ->() {
|
48
|
+
[
|
49
|
+
random_ip('0.0.0.0/0'),
|
50
|
+
random_ip(CLOUDFRONT_SUBNETS.sample),
|
51
|
+
random_ip(PRIVATE_SUBNETS_IPV4.sample),
|
52
|
+
random_ip(PRIVATE_SUBNETS_IPV4.sample),
|
53
|
+
].map(&:to_s)
|
54
|
+
}
|
55
|
+
|
56
|
+
proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES +
|
57
|
+
CLOUDFRONT_SUBNETS.map(&IPAddr.method(:new))
|
58
|
+
getip = ActionDispatch::RemoteIp::GetIp.new(nil, nil, proxies)
|
59
|
+
benchmark('remote_ip +CF', getip, pub_cf_priv_priv)
|
60
|
+
|
61
|
+
############################################################
|
62
|
+
# naive replacement of IPAddr with Subnets-derived objects
|
63
|
+
|
64
|
+
proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES.
|
65
|
+
map{|p| "#{p}/#{p.prefix}"}.map(&Subnets.method(:parse))
|
66
|
+
getip = ActionDispatch::RemoteIp::GetIp.new(nil, nil, proxies)
|
67
|
+
benchmark('subnets', getip, pub_priv_priv)
|
68
|
+
|
69
|
+
proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES.
|
70
|
+
map{|p| "#{p}/#{p.prefix}"}.map(&Subnets.method(:parse)) +
|
71
|
+
CLOUDFRONT_SUBNETS.map(&Subnets.method(:parse))
|
72
|
+
getip = ActionDispatch::RemoteIp::GetIp.new(nil, nil, proxies)
|
73
|
+
benchmark('subnets +CF', getip, pub_cf_priv_priv)
|
74
|
+
|
75
|
+
############################################################
|
76
|
+
# hack to use fast Subnets.include?
|
77
|
+
|
78
|
+
Identity = Object.new
|
79
|
+
def Identity.===(v); v; end
|
80
|
+
|
81
|
+
# instead of letting Ruby call the block for every element of the
|
82
|
+
# list, call it once to extract the ip to be tested, then use fast
|
83
|
+
# Subnets.include? method.
|
84
|
+
proxies2 = proxies.dup
|
85
|
+
|
86
|
+
def proxies2.any?(&block)
|
87
|
+
ip = block.call(Identity)
|
88
|
+
Subnets.include?(self, ip)
|
89
|
+
end
|
90
|
+
|
91
|
+
getip = ActionDispatch::RemoteIp::GetIp.new(nil, nil, proxies2)
|
92
|
+
benchmark('hack +CF', getip, pub_cf_priv_priv)
|
93
|
+
|
94
|
+
############################################################
|
95
|
+
# Alternate impl to really use fast Subnets.include?
|
96
|
+
|
97
|
+
Alternate = Struct.new(:proxies) do
|
98
|
+
def filter_proxies(ips)
|
99
|
+
ips.reject{|ip| Subnets.include?(self.proxies, ip)}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
getip = Alternate.new(proxies)
|
104
|
+
benchmark('alt +CF', getip, pub_cf_priv_priv)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Subnets
|
4
|
+
class TestNet4 < Minitest::Test
|
5
|
+
include EqlAndHash
|
6
|
+
|
7
|
+
def klass
|
8
|
+
Net4
|
9
|
+
end
|
10
|
+
|
11
|
+
def constructor_args
|
12
|
+
[1,24]
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_new_creates_net4
|
16
|
+
assert_instance_of Net4, Net4.new(1, 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_new_rejects_invalid_prefixlen
|
20
|
+
assert_raises(ArgumentError) { Net4.new(1, 33) }
|
21
|
+
end
|
22
|
+
|
23
|
+
class ParseBadInput < Minitest::Test
|
24
|
+
data = [
|
25
|
+
'a', '12', '1.2.3', '1/2', '',
|
26
|
+
]
|
27
|
+
|
28
|
+
data.each_with_index do |s, i|
|
29
|
+
define_method("test_parse_fail_%02d" % i) do
|
30
|
+
assert_raises(ParseError) { Net4.parse(s) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Parse < Minitest::Test
|
36
|
+
data = ['127.0.0.1/32', '192.168.0.0/24', '10.1.0.0/16', '1.2.3.4/2']
|
37
|
+
|
38
|
+
data.each_with_index do |cidr, i|
|
39
|
+
define_method("test_parse_%02d" % i) do
|
40
|
+
assert_instance_of Net4, Net4.parse(cidr)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_includes_ip
|
46
|
+
net = Net4.parse '192.168.0.0/24'
|
47
|
+
assert_include net, '192.168.0.0'
|
48
|
+
assert_include net, Subnets.parse('192.168.0.2')
|
49
|
+
assert_include net, '192.168.0.255'
|
50
|
+
|
51
|
+
refute_include net, '192.168.1.0'
|
52
|
+
refute_include net, Subnets.parse('10.168.0.2')
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_includes_net
|
56
|
+
net = Net4.parse '192.168.0.0/24'
|
57
|
+
assert_include net, '192.168.0.0/24'
|
58
|
+
assert_include net, Net4.parse('192.168.0.2/26')
|
59
|
+
refute_include net, '192.168.0.0/22'
|
60
|
+
refute_include net, Net4.parse('10.168.0.0/16')
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_random_roundtrip
|
64
|
+
random = Random.new
|
65
|
+
start = Time.now
|
66
|
+
until Time.now - start > TIMED_TEST_DURATION
|
67
|
+
1000.times do
|
68
|
+
net = Net4.random(random).to_s
|
69
|
+
assert_equal net, Net4.parse(net).to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'ipaddr'
|
3
|
+
|
4
|
+
module Subnets
|
5
|
+
class TestNet6 < Minitest::Test
|
6
|
+
include EqlAndHash
|
7
|
+
|
8
|
+
# for EqlAndHash
|
9
|
+
def klass
|
10
|
+
Net6
|
11
|
+
end
|
12
|
+
|
13
|
+
# for EqlAndHash
|
14
|
+
def constructor_args
|
15
|
+
[[1,2,3,4,5,6,7,8], 96]
|
16
|
+
end
|
17
|
+
|
18
|
+
class New < Minitest::Test
|
19
|
+
def test_rejects_invalid_prefixlen
|
20
|
+
assert_raises(ArgumentError) { Net6.new([0,0,0,0,0,0,0,0], 129) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_rejects_invalid_hextets
|
24
|
+
assert_raises(ArgumentError) { Net6.new([0,0,0,0,0,0,0], 128) }
|
25
|
+
assert_raises(ArgumentError) { Net6.new([0,0,0,0,0,0,0,0,0], 128) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_creates_net6
|
29
|
+
assert_equal Net6.parse('1:2:44:55:ef::/128'),
|
30
|
+
Net6.new([0x1,0x2,0x44,0x55,0xef,0,0,0], 128)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ParseBadInput < Minitest::Test
|
35
|
+
data = [
|
36
|
+
{ name: 'leading single colon', s: ':1::' },
|
37
|
+
{ name: 'non-hexadecimal', s: 'u' },
|
38
|
+
{ name: 'too short', s: '12:' },
|
39
|
+
{ name: 'too short', s: ':1' },
|
40
|
+
{ name: 'missing compression', s: '1/2' },
|
41
|
+
{ name: 'trailing single colon', s: '1::1:/1' },
|
42
|
+
{ name: 'leading zero in hextet', s: '045:1:2::/32' },
|
43
|
+
{ name: 'prefixlen too large', s: '1::/129' },
|
44
|
+
]
|
45
|
+
|
46
|
+
data.each_with_index do |d, i|
|
47
|
+
define_method("test_parse_fail_%02d #{d[:name]}" % i) do
|
48
|
+
assert_raises(ParseError) { Net6.parse(d[:s]) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Parse < Minitest::Test
|
54
|
+
data = [
|
55
|
+
{ name: 'zero', s: '::/0' },
|
56
|
+
{ name: 'localhost', s: '::1/128' },
|
57
|
+
{ name: 'first-one', s: '1::/128' },
|
58
|
+
{ name: 'one-one', s: '1::1/32' },
|
59
|
+
{ name: 'ip', s: 'ffe3::ff01/32' },
|
60
|
+
{ name: 'ip', s: '46db:20af:2b68:4034::871f:0/83' },
|
61
|
+
{ name: 'full', s: '1:2:3:4:5:6:7:8/96' },
|
62
|
+
{ name: 'many-zeros', s: '0:0:0:1:0:0:0:0/44', to_s: '0:0:0:1::/44' },
|
63
|
+
{ name: 'embedded ipv4', s: '::1.2.3.4/96', to_s: '::102:304/96' },
|
64
|
+
{ name: 'embedded ipv4', s: 'fe:55::1.2.3.4/96', to_s: 'fe:55::102:304/96' },
|
65
|
+
{ name: 'embedded ipv4', s: 'a:b:c:d:e:f:1.2.3.4/128', to_s: 'a:b:c:d:e:f:102:304/128' },
|
66
|
+
]
|
67
|
+
|
68
|
+
data.each_with_index do |d, i|
|
69
|
+
define_method("test_parse_%02d #{d[:name]}" % i) do
|
70
|
+
assert_instance_of Net6, Net6.parse(d[:s])
|
71
|
+
end
|
72
|
+
|
73
|
+
define_method("test_to_s_%02d #{d[:name]}" % i) do
|
74
|
+
assert_equal (d[:to_s] || d[:s]), Net6.parse(d[:s]).to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method("test_prefixlen_%02d #{d[:name]}" % i) do
|
78
|
+
raise "#{d[:s]} didn't match regex" unless d[:s] =~ /\/(\d+)\z/
|
79
|
+
assert_equal $1.to_i, Net6.parse(d[:s]).prefixlen
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_random_roundtrip
|
85
|
+
random = Random.new
|
86
|
+
start = Time.now
|
87
|
+
until Time.now - start > TIMED_TEST_DURATION
|
88
|
+
1000.times do
|
89
|
+
net = Net6.random(random, zeros: true).to_s
|
90
|
+
assert_equal net, Net6.parse(net).to_s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_prefixlen
|
96
|
+
(0..128).each do |i|
|
97
|
+
assert_equal i, Net6.parse("::1/#{i}").prefixlen
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Include < Minitest::Test
|
102
|
+
def setup
|
103
|
+
@net = Net6.parse '1:2:3:4:5:6:7:8/96'
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_includes_ips
|
107
|
+
['1:2:3:4:5:6:7:9', '1:2:3:4:5:6:99::'].each do |ip|
|
108
|
+
assert_include @net, ip
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_excludes_ips
|
113
|
+
['5::', '1:2:3:99::'].each do |ip|
|
114
|
+
refute_include @net, ip
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_returns_false_with_non_ips_or_nets
|
119
|
+
['a', /a/, 2].each do |obj|
|
120
|
+
refute_include @net, obj
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|