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
@@ -0,0 +1,39 @@
|
|
1
|
+
module Irrc
|
2
|
+
module Connecting
|
3
|
+
private
|
4
|
+
|
5
|
+
def connection
|
6
|
+
@connection
|
7
|
+
end
|
8
|
+
|
9
|
+
def connection=(connection)
|
10
|
+
@connection = connection
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
@connection ||= logger.info("Connecting to #{@host}") &&
|
15
|
+
Net::Telnet.new('Host' => @host,
|
16
|
+
'Port' => 43,
|
17
|
+
'Telnetmode' => false,
|
18
|
+
'Prompt' => return_code)
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
if established?
|
23
|
+
logger.info "Closing a connection to #{@host}"
|
24
|
+
@connection.close
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def established?
|
29
|
+
@connection && !@connection.sock.closed?
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(command)
|
33
|
+
return if command.nil? || command == ''
|
34
|
+
|
35
|
+
logger.debug "Executing: #{command}"
|
36
|
+
@connection.cmd(command).tap {|result| logger.debug "Returned: #{result}" }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/irrc/irr.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
module Irrc
|
2
|
+
module Irr
|
3
|
+
class << self
|
4
|
+
def host(name)
|
5
|
+
irr_list[irr_name(name)]
|
6
|
+
end
|
7
|
+
|
8
|
+
def irr?(name)
|
9
|
+
irr_list.keys.include?(irr_name(name))
|
10
|
+
end
|
11
|
+
|
12
|
+
def type(name)
|
13
|
+
type_list[irr_name(name)] || type_list[fqdn(name)]
|
14
|
+
end
|
15
|
+
|
16
|
+
# See RFC2622 / RFC4012 for details
|
17
|
+
def members_tag
|
18
|
+
/^(?:mp-)?members:\s*(.*)$/
|
19
|
+
end
|
20
|
+
|
21
|
+
# See RFC2622 / RFC4012 for details
|
22
|
+
def route_tag(protocol)
|
23
|
+
case protocol
|
24
|
+
when :ipv4, 'ipv4'
|
25
|
+
/^route:\s*(\S+)$/
|
26
|
+
when :ipv6, 'ipv6'
|
27
|
+
/^route6:\s*(\S+)$/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def irr_list
|
34
|
+
@_irr_list ||= Hash[LIST.map {|i| [i[0], i[1]] }]
|
35
|
+
end
|
36
|
+
|
37
|
+
def type_list
|
38
|
+
@_type_list ||= Hash[LIST.map {|i| [[i[0], i[2]], [i[1], i[2]]] }.flatten(1)]
|
39
|
+
end
|
40
|
+
|
41
|
+
def irr_name(name)
|
42
|
+
name.to_s.upcase
|
43
|
+
end
|
44
|
+
|
45
|
+
def fqdn(fqdn)
|
46
|
+
fqdn.to_s.downcase
|
47
|
+
end
|
48
|
+
|
49
|
+
# See http://www.irr.net/docs/list.html
|
50
|
+
LIST = [
|
51
|
+
['ALTDB', 'whois.altdb.net', 'irrd'],
|
52
|
+
['AOLTW', 'whois.aoltw.net', 'irrd'],
|
53
|
+
['APNIC', 'whois.apnic.net', 'whoisd'],
|
54
|
+
['ARIN', 'rr.arin.net', 'whoisd'],
|
55
|
+
# ['BCNET', 'whois.bc.net', nil],
|
56
|
+
['BELL', 'whois.in.bell.ca', 'irrd'],
|
57
|
+
['BBOI', 'irr.bboi.net', 'irrd'],
|
58
|
+
['CANARIE', 'whois.canarie.ca', 'whoisd'],
|
59
|
+
['D', 'whois.depository.net', 'whoisd'],
|
60
|
+
# ['DERU', 'whois.deru.net', nil],
|
61
|
+
# ['DIGITALREALM', 'rr.digitalrealm.net', nil],
|
62
|
+
['EASYNET', 'whois.noc.easynet.net', 'whoisd'],
|
63
|
+
# ['EBIT', 'whois.ebit.ca', nil],
|
64
|
+
['EPOCH', 'whois.epoch.net', 'irrd'],
|
65
|
+
['GT', 'rr.gt.ca', 'irrd'],
|
66
|
+
# ['GW', 'whois.gw.net', nil],
|
67
|
+
['HOST', 'rr.host.net', 'irrd'],
|
68
|
+
['JPIRR', 'jpirr.nic.ad.jp', 'irrd'],
|
69
|
+
['LEVEL3', 'rr.level3.net', 'whoisd'],
|
70
|
+
# ['MTO', 'rr.mtotelecom.com', nil],
|
71
|
+
['NESTEGG', 'whois.nestegg.net', 'irrd'],
|
72
|
+
['NTTCOM', 'rr.ntt.net', 'irrd'],
|
73
|
+
['OPENFACE', 'whois.openface.ca', 'irrd'],
|
74
|
+
['OTTIX', 'whois.ottix.net', 'irrd'],
|
75
|
+
['PANIX', 'rrdb.access.net', 'irrd'],
|
76
|
+
['RADB', 'whois.radb.net', 'irrd'],
|
77
|
+
['REACH', 'rr.net.reach.com', 'irrd'],
|
78
|
+
['RGNET', 'whois.rg.net', 'irrd'],
|
79
|
+
['RIPE', 'whois.ripe.net', 'whoisd'],
|
80
|
+
['RISQ', 'rr.risq.net', 'irrd'],
|
81
|
+
['ROGERS', 'whois.rogerstelecom.net', 'irrd'],
|
82
|
+
['SAVVIS', 'rr.savvis.net', 'whoisd'],
|
83
|
+
['TC', 'bgp.net.br', 'irrd']
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def object
|
89
|
+
@_object
|
90
|
+
end
|
91
|
+
|
92
|
+
def object=(object)
|
93
|
+
@_object = object
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Returns the object type to query.
|
97
|
+
# See RFC2622 for details.
|
98
|
+
#
|
99
|
+
# Returns: A String. ('as-set', 'route-set' or 'aut-num')
|
100
|
+
def object_type
|
101
|
+
case @_object
|
102
|
+
when /^AS-[\w-]+$|:AS-[\w-]+$/i
|
103
|
+
'as-set'
|
104
|
+
when /^RS-[\w-]+$|:RS-[\w-]+$/i
|
105
|
+
'route-set'
|
106
|
+
when /^AS\d+$|:AS\d+$/i
|
107
|
+
'aut-num'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def as_set?
|
112
|
+
object_type == 'as-set'
|
113
|
+
end
|
114
|
+
|
115
|
+
def route_set?
|
116
|
+
object_type == 'route-set'
|
117
|
+
end
|
118
|
+
|
119
|
+
def aut_num?
|
120
|
+
object_type == 'aut-num'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/irrc/irrd.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'irrc/irrd/client'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'irrc/irr'
|
2
|
+
|
3
|
+
module Irrc
|
4
|
+
module Irrd
|
5
|
+
module Api
|
6
|
+
private
|
7
|
+
|
8
|
+
def persist_command
|
9
|
+
'!!'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: Returns a IRR command to specify authoritative IRR source.
|
13
|
+
#
|
14
|
+
# sources - Array object containing IRR source names.
|
15
|
+
def set_source_command(sources)
|
16
|
+
if sources && !sources.empty?
|
17
|
+
"!s#{sources.join(',')}"
|
18
|
+
else
|
19
|
+
'!s-*'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def expand_set_command(as_set)
|
24
|
+
"!i#{as_set},1" if as_set && as_set != ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_aut_nums_from_as_set(result)
|
28
|
+
case result
|
29
|
+
when success_code
|
30
|
+
result.gsub(/^#{$1}$/, '').strip.split.grep(/^AS/).uniq
|
31
|
+
when error_code
|
32
|
+
raise $1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_prefixes_from_route_set(result)
|
37
|
+
case result
|
38
|
+
when success_code
|
39
|
+
result.gsub(/^#{$1}$/, '').strip.split.reject {|p| p =~ /^A\d+$/ }.uniq
|
40
|
+
when error_code
|
41
|
+
raise $1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def expand_aut_num_command(autnum)
|
46
|
+
"-K -r -i origin #{autnum}" if autnum && autnum != ''
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_prefixes_from_aut_num(result, protocol)
|
50
|
+
result.scan(Irrc::Irr.route_tag(protocol)).flatten.uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
# See http://www.irrd.net/irrd-user.pdf for return codes
|
54
|
+
def success_code
|
55
|
+
/^(C.*)\n$/
|
56
|
+
end
|
57
|
+
|
58
|
+
def error_code
|
59
|
+
/^(D|E|F.*)\n$/
|
60
|
+
end
|
61
|
+
|
62
|
+
def return_code
|
63
|
+
# Query with ripe option commands like "-i origin AS2515" returns doubled blank lines.
|
64
|
+
# And we can't easily tell whether it succeeded or not.
|
65
|
+
Regexp.union(success_code, error_code, /^\n\n$/)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,90 @@
|
|
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/irrd/api'
|
10
|
+
|
11
|
+
module Irrc
|
12
|
+
module Irrd
|
13
|
+
|
14
|
+
# Public: IRRd 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::Irrd::Api
|
22
|
+
|
23
|
+
attr_reader :host, :queue
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def connect
|
29
|
+
super
|
30
|
+
connection.puts persist_command
|
31
|
+
end
|
32
|
+
|
33
|
+
def process(query)
|
34
|
+
set_source query
|
35
|
+
|
36
|
+
case query.object_type
|
37
|
+
when 'as-set'
|
38
|
+
resolve_aut_nums_from_as_set query
|
39
|
+
resolve_prefixes_from_aut_nums query
|
40
|
+
when 'route-set'
|
41
|
+
resolve_prefixes_from_route_set query
|
42
|
+
when 'aut-num'
|
43
|
+
query.add_aut_num_result query.object
|
44
|
+
resolve_prefixes_from_aut_nums query
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_source(query)
|
49
|
+
command = set_source_command(query.sources)
|
50
|
+
if execute(command) =~ error_code
|
51
|
+
raise "'#{command}' failed on '#{host}' (#{$1})."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def resolve_aut_nums_from_as_set(query)
|
56
|
+
command = expand_set_command(query.object)
|
57
|
+
result = execute(command)
|
58
|
+
query.add_aut_num_result parse_aut_nums_from_as_set(result)
|
59
|
+
rescue
|
60
|
+
raise "'#{command}' failed on '#{host}' (#{$!.message})."
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolve_prefixes_from_route_set(query)
|
64
|
+
command = expand_set_command(query.object)
|
65
|
+
result = execute(command)
|
66
|
+
prefixes = classify_by_protocol(parse_prefixes_from_route_set(result))
|
67
|
+
|
68
|
+
query.protocols.each do |protocol|
|
69
|
+
query.add_prefix_result prefixes[protocol], nil, protocol
|
70
|
+
end
|
71
|
+
rescue
|
72
|
+
raise "'#{command}' failed on '#{host}' (#{$!.message})."
|
73
|
+
end
|
74
|
+
|
75
|
+
def resolve_prefixes_from_aut_nums(query)
|
76
|
+
unless query.protocols.empty?
|
77
|
+
# ipv4 and ipv6 should have the same result so far
|
78
|
+
(query.result[:ipv4] || query.result[:ipv6]).keys.each do |autnum|
|
79
|
+
command = expand_aut_num_command(autnum)
|
80
|
+
result = execute(command)
|
81
|
+
|
82
|
+
query.protocols.each do |protocol|
|
83
|
+
query.add_prefix_result parse_prefixes_from_aut_num(result, protocol), autnum, protocol
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/irrc/logging.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Irrc
|
2
|
+
module Parameter
|
3
|
+
|
4
|
+
# Public: Create a new IRRd / Whoisd client worker.
|
5
|
+
# You can customize the logger by specifying a block.
|
6
|
+
# The default logger is STDERR printer of more severe messages than INFO.
|
7
|
+
#
|
8
|
+
# host - FQDN of IRR / Whois. IRR / Whois name is also accespted.
|
9
|
+
# queue - Queue object having query jobs.
|
10
|
+
# IRR / Whois name is also accespted.
|
11
|
+
# block - An optional block that can be used to customize the logger.
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# Irrc::Irrd::Client.new('jpirr.nic.ad.jp', queue) {|c|
|
16
|
+
# c.logger = Logger.new('irrc.log')
|
17
|
+
# }
|
18
|
+
def initialize(host, queue, &block)
|
19
|
+
self.host = host
|
20
|
+
self.queue = queue
|
21
|
+
instance_eval(&block) if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def host=(host)
|
28
|
+
raise ArgumentError, "Missing argument." unless host
|
29
|
+
@host = Irrc::Irr.host(host) || host
|
30
|
+
end
|
31
|
+
|
32
|
+
def queue=(queue)
|
33
|
+
queue.is_a?(Queue) or raise ArgumentError, "Missing argument."
|
34
|
+
@queue = queue
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/irrc/prefix.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Irrc
|
4
|
+
module Prefix
|
5
|
+
private
|
6
|
+
|
7
|
+
def classify_by_protocol(prefixes)
|
8
|
+
prefixes.each_with_object(Struct.new(:ipv4, :ipv6).new([], [])) {|prefix, result|
|
9
|
+
addr = IPAddr.new(prefix)
|
10
|
+
if addr.ipv4?
|
11
|
+
result.ipv4 << prefix
|
12
|
+
elsif addr.ipv6?
|
13
|
+
result.ipv6 << prefix
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
data/lib/irrc/query.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'irrc/irr'
|
2
|
+
require 'irrc/query_status'
|
3
|
+
require 'irrc/subquery'
|
4
|
+
|
5
|
+
module Irrc
|
6
|
+
|
7
|
+
# Public: IRR / Whois query and result container.
|
8
|
+
class Query
|
9
|
+
include Irrc::Irr
|
10
|
+
include Irrc::QueryStatus
|
11
|
+
include Irrc::Subquery
|
12
|
+
|
13
|
+
attr_reader :sources, :protocols
|
14
|
+
|
15
|
+
# Public: Create a new Query object.
|
16
|
+
#
|
17
|
+
# object - IRR object to extract. (eg: as-set, route-set, aut-num object)
|
18
|
+
# options - The Hash options to pass to IRR. (default: {procotol: [:ipv4, :ipv6]})
|
19
|
+
# :source - Specify authoritative IRR source names.
|
20
|
+
# If not given, any source will be accepted. (optional)
|
21
|
+
# :protocol - :ipv4, :ipv6 or [:ipv4, :ipv6]
|
22
|
+
# A String or Symbol of protcol name is acceptable. (optional)
|
23
|
+
#
|
24
|
+
# Examples
|
25
|
+
#
|
26
|
+
# Irrc::Query.new('AS-JPNIC', source: :jpirr, protocol: :ipv4)
|
27
|
+
# Irrc::Query.new('AS-JPNIC', source: [:jpirr, :radb])
|
28
|
+
def initialize(object, options={})
|
29
|
+
options = {protocol: [:ipv4, :ipv6]}.merge(options)
|
30
|
+
self.sources = options[:source]
|
31
|
+
self.protocols = options[:protocol]
|
32
|
+
self.object = object.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def result
|
36
|
+
@result ||= Struct.new(:ipv4, :ipv6).new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Register aut-num object(s) as a result.
|
40
|
+
#
|
41
|
+
# autnums - aut-num object(s) in String. Array form is also acceptable for multiple objects.
|
42
|
+
def add_aut_num_result(autnums)
|
43
|
+
@protocols.each do |protocol|
|
44
|
+
result[protocol] ||= {}
|
45
|
+
|
46
|
+
Array(autnums).each do |autnum|
|
47
|
+
result[protocol][autnum] ||= []
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Register route object(s) as a result.
|
53
|
+
#
|
54
|
+
# prefixes - route object(s) in String. Array form is also acceptable for multiple objects.
|
55
|
+
# autnum - Which aut-num has the route object(s).
|
56
|
+
# protocol - Which protocol the route object(s) is for. :ipv4 or :ipv6.
|
57
|
+
# A String or Symbol of protcol name is acceptable.
|
58
|
+
def add_prefix_result(prefixes, autnum, protocol)
|
59
|
+
result[protocol] ||= {}
|
60
|
+
result[protocol][autnum] ||= []
|
61
|
+
result[protocol][autnum] |= Array(prefixes)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def sources=(sources)
|
68
|
+
@sources = Array(sources).compact.map(&:to_s).flatten.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
def protocols=(protocols)
|
72
|
+
protocols = Array(protocols).compact.map(&:to_s).flatten.uniq
|
73
|
+
invalid = protocols - ['ipv4', 'ipv6']
|
74
|
+
raise ArgumentError, "Invalid protocol: #{invalid.join(', ')}" unless invalid.empty?
|
75
|
+
@protocols = protocols
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|