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,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
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ require 'logger'
2
+
3
+ module Irrc
4
+ module Logging
5
+ def logger=(logger)
6
+ @logger = logger
7
+ end
8
+
9
+ def logger
10
+ @logger ||= Logger.new(STDERR).tap {|l| l.level = Logger::WARN }
11
+ end
12
+ end
13
+ end
@@ -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
@@ -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
+
@@ -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