irrc 0.1.0
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/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +195 -0
- data/Rakefile +8 -0
- data/bin/irrc +5 -0
- data/irrc.gemspec +23 -0
- data/lib/irrc.rb +2 -0
- data/lib/irrc/cli.rb +1 -0
- data/lib/irrc/cli/client.rb +108 -0
- data/lib/irrc/cli/yaml_printer.rb +13 -0
- data/lib/irrc/client.rb +109 -0
- data/lib/irrc/connecting.rb +39 -0
- data/lib/irrc/irr.rb +123 -0
- data/lib/irrc/irrd.rb +1 -0
- data/lib/irrc/irrd/api.rb +69 -0
- data/lib/irrc/irrd/client.rb +90 -0
- data/lib/irrc/logging.rb +13 -0
- data/lib/irrc/parameter.rb +37 -0
- data/lib/irrc/prefix.rb +19 -0
- data/lib/irrc/query.rb +78 -0
- data/lib/irrc/query_status.rb +19 -0
- data/lib/irrc/runner.rb +37 -0
- data/lib/irrc/socket.rb +5 -0
- data/lib/irrc/subquery.rb +24 -0
- data/lib/irrc/version.rb +3 -0
- data/lib/irrc/whoisd.rb +1 -0
- data/lib/irrc/whoisd/api.rb +54 -0
- data/lib/irrc/whoisd/client.rb +88 -0
- data/spec/features/irr_as_set_resolution_spec.rb +193 -0
- data/spec/features/irr_invalid_object_resolution_spec.rb +32 -0
- data/spec/features/whois_as_set_resolution_spec.rb +193 -0
- data/spec/features/whois_invalid_object_resolution_spec.rb +32 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/contexts.rb +12 -0
- data/spec/support/query.rb +7 -0
- metadata +123 -0
data/lib/irrc/runner.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Irrc
|
2
|
+
module Runner
|
3
|
+
def run
|
4
|
+
done = []
|
5
|
+
|
6
|
+
loop do
|
7
|
+
if queue.empty?
|
8
|
+
close
|
9
|
+
return done
|
10
|
+
end
|
11
|
+
|
12
|
+
query = queue.pop
|
13
|
+
connect unless established?
|
14
|
+
|
15
|
+
begin
|
16
|
+
process query
|
17
|
+
query.success
|
18
|
+
rescue
|
19
|
+
logger.error $!.message
|
20
|
+
query.fail
|
21
|
+
end
|
22
|
+
|
23
|
+
done << query
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def execute(command)
|
31
|
+
return if command.nil? || command == ''
|
32
|
+
|
33
|
+
logger.debug "Executing: #{command}"
|
34
|
+
connection.cmd(command).tap {|result| logger.debug "Returned: #{result}" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/irrc/socket.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Irrc
|
2
|
+
module Subquery
|
3
|
+
# Public: Generate a child query to resolve IRR / Whois object recursively.
|
4
|
+
#
|
5
|
+
# object - IRR / Whois object to extract. (eg: as-set, route-set, aut-num object)
|
6
|
+
def fork(object)
|
7
|
+
Query.new(object, source: sources, protocol: protocols).tap {|q|
|
8
|
+
q.parent = self
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: Returns the parent (associated) Query object, which is probably as-set.
|
13
|
+
def parent
|
14
|
+
@_parent
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Set a parent (associated) Query object, which is probably as-set.
|
18
|
+
#
|
19
|
+
# parent - Parent Query object.
|
20
|
+
def parent=(parent)
|
21
|
+
@_parent = parent
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/irrc/version.rb
ADDED
data/lib/irrc/whoisd.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'irrc/whoisd/client'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'irrc/irr'
|
2
|
+
|
3
|
+
module Irrc
|
4
|
+
module Whoisd
|
5
|
+
module Api
|
6
|
+
private
|
7
|
+
|
8
|
+
def expand_set_command(as_set, sources, type)
|
9
|
+
"-k -r #{source_option(sources)} -T #{type} #{as_set}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_objects_from_set(result)
|
13
|
+
if result =~ error_code
|
14
|
+
raise $1
|
15
|
+
end
|
16
|
+
|
17
|
+
result.scan(Irrc::Irr.members_tag).flatten.map {|i| i.split /\s*,?\s+/ }.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def expand_route_set_command(route_set, sources)
|
21
|
+
if sources && !sources.empty?
|
22
|
+
"-k -r -s #{sources.join(',')} -T route-set #{route_set}"
|
23
|
+
else
|
24
|
+
"-k -r -a -T route-set #{route_set}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def expand_aut_num_command(autnum, sources)
|
29
|
+
"-k -r #{source_option(sources)} -K -i origin #{autnum}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_prefixes_from_aut_num(result, protocol)
|
33
|
+
result.scan(Irrc::Irr.route_tag(protocol)).flatten.uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
# See http://www.ripe.net/data-tools/support/documentation/ripe-database-query-reference-manual#a1--ripe-database-query-server-response-codes-and-messages for the error code
|
37
|
+
def error_code
|
38
|
+
/^%ERROR:(.*)$/
|
39
|
+
end
|
40
|
+
|
41
|
+
def return_code
|
42
|
+
/\n\n\n/
|
43
|
+
end
|
44
|
+
|
45
|
+
def source_option(sources)
|
46
|
+
if sources && !sources.empty?
|
47
|
+
"-s #{sources.join(',')}"
|
48
|
+
else
|
49
|
+
'-a'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'net/telnet'
|
2
|
+
|
3
|
+
require 'irrc/connecting'
|
4
|
+
require 'irrc/logging'
|
5
|
+
require 'irrc/parameter'
|
6
|
+
require 'irrc/prefix'
|
7
|
+
require 'irrc/runner'
|
8
|
+
require 'irrc/socket'
|
9
|
+
require 'irrc/whoisd/api'
|
10
|
+
|
11
|
+
module Irrc
|
12
|
+
module Whoisd
|
13
|
+
|
14
|
+
# Public: Whoisd client worker.
|
15
|
+
class Client
|
16
|
+
include Irrc::Connecting
|
17
|
+
include Irrc::Logging
|
18
|
+
include Irrc::Parameter
|
19
|
+
include Irrc::Prefix
|
20
|
+
include Irrc::Runner
|
21
|
+
include Irrc::Whoisd::Api
|
22
|
+
|
23
|
+
attr_reader :host, :queue
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def process(query)
|
29
|
+
case query.object_type
|
30
|
+
when 'as-set'
|
31
|
+
query.add_aut_num_result objects_from_set(query, 'as-set')
|
32
|
+
resolve_prefixes_from_aut_nums query
|
33
|
+
when 'route-set'
|
34
|
+
resolve_prefixes_from_route_set query
|
35
|
+
when 'aut-num'
|
36
|
+
query.add_aut_num_result query.object
|
37
|
+
resolve_prefixes_from_aut_nums query
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def objects_from_set(query, type)
|
42
|
+
command = expand_set_command(query.object, query.sources, type)
|
43
|
+
cache(query.object) {
|
44
|
+
result = execute(command)
|
45
|
+
parse_objects_from_set(result).map {|object|
|
46
|
+
expand_if_necessary(query.fork(object), type)
|
47
|
+
}.flatten.uniq.compact
|
48
|
+
}
|
49
|
+
rescue
|
50
|
+
raise "'#{command}' failed on '#{host}' (#{$!.message})."
|
51
|
+
end
|
52
|
+
|
53
|
+
def expand_if_necessary(query, type)
|
54
|
+
if query.object_type == type
|
55
|
+
objects_from_set(query, type)
|
56
|
+
else
|
57
|
+
query.object
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def resolve_prefixes_from_route_set(query)
|
62
|
+
prefixes = classify_by_protocol(objects_from_set(query, 'route-set'))
|
63
|
+
query.protocols.each do |protocol|
|
64
|
+
query.add_prefix_result prefixes[protocol], nil, protocol
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolve_prefixes_from_aut_nums(query)
|
69
|
+
unless query.protocols.empty?
|
70
|
+
# ipv4 and ipv6 should have the same result so far
|
71
|
+
(query.result[:ipv4] || query.result[:ipv6]).keys.each do |autnum|
|
72
|
+
command = expand_aut_num_command(autnum, query.sources)
|
73
|
+
result = execute(command)
|
74
|
+
|
75
|
+
query.protocols.each do |protocol|
|
76
|
+
query.add_prefix_result parse_prefixes_from_aut_num(result, protocol), autnum, protocol
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def cache(object, &block)
|
83
|
+
@_cache ||= {}
|
84
|
+
@_cache[object] ||= yield
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'IRR as-set resolution' do
|
4
|
+
include_context 'irr queries'
|
5
|
+
|
6
|
+
context 'When FQDN specified for IRR server' do
|
7
|
+
subject { send_query(irr_fqdn, 'AS-JPNIC') }
|
8
|
+
|
9
|
+
it 'returns ipv4 and ipv6 prefixes by default' do
|
10
|
+
expect(subject['AS-JPNIC'][:ipv4]['AS2515']).to include '192.41.192.0/24', '202.12.30.0/24',
|
11
|
+
'211.120.240.0/21', '211.120.248.0/24'
|
12
|
+
expect(subject['AS-JPNIC'][:ipv6]['AS2515']).to include '2001:dc2::/32', '2001:0fa0::/32'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'When a name specified for IRR server' do
|
17
|
+
subject { send_query(irr, as_set) }
|
18
|
+
|
19
|
+
it 'returns the same result as if the FQDN specified' do
|
20
|
+
expect(subject).to eq send_query(irr_fqdn, as_set)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'gently closes connections to IRR server' do
|
25
|
+
expect_any_instance_of(Irrc::Irrd::Client).to receive(:close)
|
26
|
+
send_query(irr_fqdn, as_set)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'Fintering by Authoritative IRR server' do
|
30
|
+
subject { send_query(:jpirr, 'AS-JPNIC', source: :apnic) }
|
31
|
+
|
32
|
+
it 'returns nothing' do
|
33
|
+
expect(subject).to eq({})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'When as-set resolution is done but something wrong while further processes' do
|
38
|
+
subject { send_query(irr, as_set) }
|
39
|
+
before do
|
40
|
+
allow_any_instance_of(Irrc::Irrd::Client).to receive(:resolve_prefixes_from_aut_nums){ raise }
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'ignores a halfway result' do
|
44
|
+
expect(subject).to eq({})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'When only ipv4 is specified for protocol' do
|
49
|
+
subject { send_query(irr, as_set, protocol: :ipv4) }
|
50
|
+
|
51
|
+
it 'returns nothing about ipv6' do
|
52
|
+
expect(subject[as_set][:ipv6]).to be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'When nil specified for protocol' do
|
57
|
+
subject { send_query(irr, as_set, protocol: nil) }
|
58
|
+
|
59
|
+
it 'returns nothing about the as-set' do
|
60
|
+
expect(subject[as_set]).to eq({})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'When blank protocol specified' do
|
65
|
+
subject { send_query(irr, as_set, protocol: []) }
|
66
|
+
|
67
|
+
it 'returns nothing about the as-set' do
|
68
|
+
expect(subject[as_set]).to eq({})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'When an invalid protocol specified' do
|
73
|
+
subject { send_query(irr, as_set, protocol: :invalid) }
|
74
|
+
|
75
|
+
it 'reports an ArgumentError' do
|
76
|
+
expect { subject }.to raise_error ArgumentError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'When a non-mirrored server specified for authoritative IRR server' do
|
81
|
+
subject { send_query(irr, as_set, source: :outsider) }
|
82
|
+
|
83
|
+
it 'returns nothing' do
|
84
|
+
expect(subject).to eq({})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'When nil specified for authoritative IRR server' do
|
89
|
+
subject { send_query(irr, as_set, source: nil) }
|
90
|
+
|
91
|
+
it 'returns a result without any filter of authoritative IRR server' do
|
92
|
+
expect(subject).to eq send_query(irr, as_set)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'When blank authoritative IRR server specified' do
|
97
|
+
subject { send_query(irr, as_set, source: []) }
|
98
|
+
|
99
|
+
it 'returns a result without any filter of authoritative IRR server' do
|
100
|
+
expect(subject).to eq send_query(irr, as_set)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'When non-existent IRR object specified' do
|
105
|
+
subject { send_query(irr, 'AS-NON-EXISTENT') }
|
106
|
+
|
107
|
+
it 'ignores the IRR error' do
|
108
|
+
expect(subject).to eq({})
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'When invalid IRR server name specified' do
|
113
|
+
subject { send_query(:invalid, as_set) }
|
114
|
+
|
115
|
+
it 'reports an error' do
|
116
|
+
expect { subject }.to raise_error
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'When non-resolvable IRR server fqdn specified' do
|
121
|
+
subject { send_query('non-resolvable.localdomain', as_set) }
|
122
|
+
|
123
|
+
it 'reports an error' do
|
124
|
+
expect { subject }.to raise_error
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'When unreachable IRR server specified' do
|
129
|
+
subject { send_query('192.0.2.1', as_set) }
|
130
|
+
|
131
|
+
it 'reports an error' do
|
132
|
+
expect { subject }.to raise_error
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'When specifing an IRR server out of service' do
|
137
|
+
subject { send_query('127.0.0.1', as_set) }
|
138
|
+
|
139
|
+
it 'reports an error' do
|
140
|
+
expect { subject }.to raise_error
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'NOTE: These may fail due to Whois database changes, not code. Check Whois database if fails.' do
|
145
|
+
describe 'route-set' do
|
146
|
+
subject { send_query(irr, 'RS-RC-26462') }
|
147
|
+
|
148
|
+
it 'returns the same result as Whois database' do
|
149
|
+
expect(subject['RS-RC-26462']).to eq(
|
150
|
+
{:ipv4=>{nil=>["137.238.0.0/16"]}, :ipv6=>{nil=>[]}}
|
151
|
+
)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'nested as-set' do
|
156
|
+
subject { send_query(irr, 'AS-PDOXUPLINKS', source: :apnic) }
|
157
|
+
|
158
|
+
it 'returns the same result as Whois database' do
|
159
|
+
expect(subject['AS-PDOXUPLINKS']).to eq(
|
160
|
+
{:ipv4=>
|
161
|
+
{"AS703"=>[],
|
162
|
+
"AS1221"=>["203.92.26.0/24", "155.143.128.0/17"],
|
163
|
+
"AS2764"=>[],
|
164
|
+
"AS7474"=>[],
|
165
|
+
"AS7657"=>[],
|
166
|
+
"AS4565"=>[],
|
167
|
+
"AS5650"=>[],
|
168
|
+
"AS6461"=>[]},
|
169
|
+
:ipv6=>
|
170
|
+
{"AS703"=>[],
|
171
|
+
"AS1221"=>[],
|
172
|
+
"AS2764"=>[],
|
173
|
+
"AS7474"=>[],
|
174
|
+
"AS7657"=>[],
|
175
|
+
"AS4565"=>[],
|
176
|
+
"AS5650"=>[],
|
177
|
+
"AS6461"=>[]}}
|
178
|
+
)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe 'nested route-set' do
|
183
|
+
subject { send_query(irr, 'RS-RR-COUDERSPORT') }
|
184
|
+
|
185
|
+
it 'returns the same result as Whois database' do
|
186
|
+
expect(subject['RS-RR-COUDERSPORT']).to eq(
|
187
|
+
{:ipv4=>{nil=>["107.14.160.0/20", "71.74.32.0/20", "75.180.128.0/19"]},
|
188
|
+
:ipv6=>{nil=>[]}}
|
189
|
+
)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'IRR Invalid object resolution' do
|
4
|
+
include_context 'irr queries'
|
5
|
+
|
6
|
+
describe 'Try as-jpnic with JPIRR' do
|
7
|
+
context 'When invalid object specified' do
|
8
|
+
subject { send_query(irr, 'INVALID') }
|
9
|
+
|
10
|
+
it "doesn't report an error" do
|
11
|
+
expect(subject['INVALID']).to eq({})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'When a blank String given for IRR object to resolve' do
|
16
|
+
subject { send_query(irr, '') }
|
17
|
+
|
18
|
+
it "doesn't report an error" do
|
19
|
+
expect(subject['']).to eq({})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'When nil given for IRR object to resolve' do
|
24
|
+
subject { send_query(irr, nil) }
|
25
|
+
|
26
|
+
it 'does nothing even reporting the error' do
|
27
|
+
expect_any_instance_of(Irrc::Irrd::Client).not_to receive(:connect)
|
28
|
+
expect(subject).to eq({})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|