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