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.
@@ -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