irrc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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