irrc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ module Irrc
2
+ module QueryStatus
3
+ def fail
4
+ @failed = true
5
+ end
6
+
7
+ def success
8
+ @failed = false
9
+ end
10
+
11
+ def failed?
12
+ @failed
13
+ end
14
+
15
+ def succeeded?
16
+ @failed == false
17
+ end
18
+ end
19
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'socket'
2
+
3
+ class Socket
4
+ TCP_INFO ||= 11
5
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Irrc
2
+ VERSION = '0.1.0'
3
+ end
@@ -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