dnstraverse 0.0.1
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.
- data/LICENSE +674 -0
- data/README +4 -0
- data/Rakefile +19 -0
- data/a +44 -0
- data/bin/dnstraverse +300 -0
- data/doc.tar +0 -0
- data/doc/classes/DNSTraverse.html +198 -0
- data/doc/classes/DNSTraverse/CachingResolver.html +172 -0
- data/doc/classes/DNSTraverse/CachingResolver.src/M000055.html +21 -0
- data/doc/classes/DNSTraverse/CachingResolver.src/M000056.html +42 -0
- data/doc/classes/DNSTraverse/DecodedQuery.html +475 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000020.html +34 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000021.html +23 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000022.html +19 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000023.html +33 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000024.html +26 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000025.html +24 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000026.html +29 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000027.html +19 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000028.html +18 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000029.html +32 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000030.html +19 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000031.html +19 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000032.html +20 -0
- data/doc/classes/DNSTraverse/DecodedQuery.src/M000033.html +31 -0
- data/doc/classes/DNSTraverse/DecodedQueryCache.html +182 -0
- data/doc/classes/DNSTraverse/DecodedQueryCache.src/M000063.html +21 -0
- data/doc/classes/DNSTraverse/DecodedQueryCache.src/M000064.html +33 -0
- data/doc/classes/DNSTraverse/Fingerprint.html +277 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000047.html +29 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000048.html +18 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000049.html +57 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000050.html +28 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000051.html +27 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000052.html +35 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000053.html +24 -0
- data/doc/classes/DNSTraverse/Fingerprint.src/M000054.html +29 -0
- data/doc/classes/DNSTraverse/InfoCache.html +235 -0
- data/doc/classes/DNSTraverse/InfoCache.src/M000034.html +20 -0
- data/doc/classes/DNSTraverse/InfoCache.src/M000035.html +23 -0
- data/doc/classes/DNSTraverse/InfoCache.src/M000036.html +28 -0
- data/doc/classes/DNSTraverse/InfoCache.src/M000037.html +25 -0
- data/doc/classes/DNSTraverse/InfoCache.src/M000038.html +27 -0
- data/doc/classes/DNSTraverse/InfoCache.src/M000039.html +33 -0
- data/doc/classes/DNSTraverse/MessageUtility.html +275 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000011.html +35 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000012.html +37 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000013.html +28 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000014.html +27 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000015.html +30 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000016.html +32 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000017.html +34 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000018.html +23 -0
- data/doc/classes/DNSTraverse/MessageUtility.src/M000019.html +36 -0
- data/doc/classes/DNSTraverse/Referral.html +641 -0
- data/doc/classes/DNSTraverse/Referral.src/M000065.html +22 -0
- data/doc/classes/DNSTraverse/Referral.src/M000066.html +21 -0
- data/doc/classes/DNSTraverse/Referral.src/M000067.html +23 -0
- data/doc/classes/DNSTraverse/Referral.src/M000068.html +19 -0
- data/doc/classes/DNSTraverse/Referral.src/M000069.html +18 -0
- data/doc/classes/DNSTraverse/Referral.src/M000070.html +54 -0
- data/doc/classes/DNSTraverse/Referral.src/M000071.html +25 -0
- data/doc/classes/DNSTraverse/Referral.src/M000072.html +27 -0
- data/doc/classes/DNSTraverse/Referral.src/M000073.html +23 -0
- data/doc/classes/DNSTraverse/Referral.src/M000074.html +20 -0
- data/doc/classes/DNSTraverse/Referral.src/M000075.html +40 -0
- data/doc/classes/DNSTraverse/Referral.src/M000076.html +52 -0
- data/doc/classes/DNSTraverse/Referral.src/M000077.html +29 -0
- data/doc/classes/DNSTraverse/Referral.src/M000078.html +54 -0
- data/doc/classes/DNSTraverse/Referral.src/M000079.html +18 -0
- data/doc/classes/DNSTraverse/Referral.src/M000080.html +22 -0
- data/doc/classes/DNSTraverse/Referral.src/M000081.html +20 -0
- data/doc/classes/DNSTraverse/Referral.src/M000082.html +29 -0
- data/doc/classes/DNSTraverse/Referral.src/M000083.html +28 -0
- data/doc/classes/DNSTraverse/Referral.src/M000084.html +29 -0
- data/doc/classes/DNSTraverse/Referral.src/M000085.html +55 -0
- data/doc/classes/DNSTraverse/Referral.src/M000086.html +30 -0
- data/doc/classes/DNSTraverse/Referral.src/M000087.html +24 -0
- data/doc/classes/DNSTraverse/Referral.src/M000088.html +20 -0
- data/doc/classes/DNSTraverse/Referral.src/M000089.html +58 -0
- data/doc/classes/DNSTraverse/ResolveError.html +111 -0
- data/doc/classes/DNSTraverse/Response.html +271 -0
- data/doc/classes/DNSTraverse/Response.src/M000057.html +27 -0
- data/doc/classes/DNSTraverse/Response.src/M000058.html +21 -0
- data/doc/classes/DNSTraverse/Response.src/M000059.html +19 -0
- data/doc/classes/DNSTraverse/Response.src/M000060.html +21 -0
- data/doc/classes/DNSTraverse/Response.src/M000061.html +31 -0
- data/doc/classes/DNSTraverse/Response.src/M000062.html +23 -0
- data/doc/classes/DNSTraverse/Response/NoGlue.html +222 -0
- data/doc/classes/DNSTraverse/Response/NoGlue.src/M000007.html +32 -0
- data/doc/classes/DNSTraverse/Response/NoGlue.src/M000008.html +18 -0
- data/doc/classes/DNSTraverse/Response/NoGlue.src/M000009.html +18 -0
- data/doc/classes/DNSTraverse/Response/NoGlue.src/M000010.html +18 -0
- data/doc/classes/DNSTraverse/Traverser.html +247 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000040.html +17 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000041.html +52 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000042.html +61 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000043.html +110 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000044.html +63 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000045.html +28 -0
- data/doc/classes/DNSTraverse/Traverser.src/M000046.html +18 -0
- data/doc/classes/FingerprintRules.html +175 -0
- data/doc/classes/Log.html +174 -0
- data/doc/classes/Log.src/M000002.html +18 -0
- data/doc/classes/Log.src/M000003.html +18 -0
- data/doc/classes/Log.src/M000004.html +18 -0
- data/doc/classes/Log/Formatter.html +165 -0
- data/doc/classes/Log/Formatter.src/M000005.html +22 -0
- data/doc/classes/Log/Formatter.src/M000006.html +24 -0
- data/doc/classes/TestFingerprint.html +137 -0
- data/doc/classes/TestFingerprint.src/M000001.html +22 -0
- data/doc/created.rid +1 -0
- data/doc/files/lib/dnstraverse/caching_resolver_rb.html +132 -0
- data/doc/files/lib/dnstraverse/decoded_query_cache_rb.html +132 -0
- data/doc/files/lib/dnstraverse/decoded_query_rb.html +132 -0
- data/doc/files/lib/dnstraverse/fingerprint_rb.html +134 -0
- data/doc/files/lib/dnstraverse/fingerprint_rules_rb.html +143 -0
- data/doc/files/lib/dnstraverse/info_cache_rb.html +131 -0
- data/doc/files/lib/dnstraverse/log_rb.html +131 -0
- data/doc/files/lib/dnstraverse/message_utility_rb.html +124 -0
- data/doc/files/lib/dnstraverse/referral_rb.html +134 -0
- data/doc/files/lib/dnstraverse/response_noglue_rb.html +124 -0
- data/doc/files/lib/dnstraverse/response_rb.html +132 -0
- data/doc/files/lib/dnstraverse/traverser_rb.html +137 -0
- data/doc/files/test/test_fingerprint_rb.html +133 -0
- data/doc/fr_class_index.html +43 -0
- data/doc/fr_file_index.html +39 -0
- data/doc/fr_method_index.html +115 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/dnstraverse/caching_resolver.rb +63 -0
- data/lib/dnstraverse/decoded_query.rb +199 -0
- data/lib/dnstraverse/decoded_query_cache.rb +53 -0
- data/lib/dnstraverse/fingerprint.rb +166 -0
- data/lib/dnstraverse/fingerprint_rules.rb +389 -0
- data/lib/dnstraverse/info_cache.rb +108 -0
- data/lib/dnstraverse/log.rb +55 -0
- data/lib/dnstraverse/message_utility.rb +199 -0
- data/lib/dnstraverse/referral.rb +463 -0
- data/lib/dnstraverse/response.rb +92 -0
- data/lib/dnstraverse/response_noglue.rb +54 -0
- data/lib/dnstraverse/traverser.rb +291 -0
- data/test/test_fingerprint.rb +29 -0
- metadata +231 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# DNSTraverse traverses the DNS to show statistics and information
|
|
2
|
+
# Copyright (C) 2008 James Ponder
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
require 'dnstraverse/log'
|
|
17
|
+
|
|
18
|
+
module DNSTraverse
|
|
19
|
+
class InfoCache
|
|
20
|
+
|
|
21
|
+
attr_reader :parent
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def key(rr)
|
|
26
|
+
return "#{rr.name}:#{rr.klass}:#{rr.type}".downcase
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
public
|
|
30
|
+
|
|
31
|
+
def initialize(parent = nil)
|
|
32
|
+
@parent = parent
|
|
33
|
+
@data = Hash.new
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# adds the resource records, clearing out any existing entries with the
|
|
38
|
+
# same details
|
|
39
|
+
def add(rrs)
|
|
40
|
+
rrs.each {|rr| @data[key(rr)] = Array.new } # clear out
|
|
41
|
+
for rr in rrs do
|
|
42
|
+
@data[key(rr)].push rr
|
|
43
|
+
Log.debug { "Adding to infocache: #{rr}" }
|
|
44
|
+
end
|
|
45
|
+
return nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# array of hashes containing :name (server name) and :ips (array of strings)
|
|
49
|
+
# set domain to '' for setting root hints
|
|
50
|
+
def add_hints(domain, ns)
|
|
51
|
+
rrs = Array.new
|
|
52
|
+
for server in ns do
|
|
53
|
+
rrs.push Dnsruby::RR.create(:name => domain, :ttl => 0,
|
|
54
|
+
:type => 'NS', :domainname => server[:name])
|
|
55
|
+
for ip in server[:ips] do
|
|
56
|
+
type = (ip.to_s =~ /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/) ? 'A' : 'AAAA'
|
|
57
|
+
rrs.push Dnsruby::RR.create(:type => type, :ttl => 0,
|
|
58
|
+
:name => server[:name], :address => ip)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
return add(rrs)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get?(args)
|
|
65
|
+
qclass = args[:qclass] || 'IN'
|
|
66
|
+
gkey = "#{args[:qname]}:#{qclass}:#{args[:qtype]}".downcase
|
|
67
|
+
if @data.has_key?(gkey) then
|
|
68
|
+
Log.debug { "Infocache recall: " + @data[gkey].join(', ')}
|
|
69
|
+
return @data[gkey] # returns Array
|
|
70
|
+
end
|
|
71
|
+
return nil unless parent
|
|
72
|
+
return parent.get?(args)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_ns?(domain) # get an appropriate ns based on domain
|
|
76
|
+
domain = domain.to_s
|
|
77
|
+
while true do
|
|
78
|
+
Log.debug { "Infocache get_ns? checking NS records for '#{domain}'" }
|
|
79
|
+
rrs = get?(:qname => domain, :qtype => 'NS')
|
|
80
|
+
return rrs if rrs
|
|
81
|
+
if domain == '' then
|
|
82
|
+
raise "No nameservers available for #{domain} -- no root hints set??"
|
|
83
|
+
end
|
|
84
|
+
domain = (i = domain.index('.')) ? domain[i+1..-1] : ''
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def get_startservers(domain, nsatype = 'A')
|
|
89
|
+
Log.debug { "Getting startservers for #{domain}/#{nsatype}" }
|
|
90
|
+
newbailiwick = nil
|
|
91
|
+
# search for best NS records in authority cache based on this domain name
|
|
92
|
+
ns = get_ns?(domain)
|
|
93
|
+
starters = Array.new
|
|
94
|
+
# look up in additional cache corresponding IP addresses if we know them
|
|
95
|
+
for rr in ns do
|
|
96
|
+
nameserver = rr.domainname.to_s
|
|
97
|
+
iprrs = get?(:qname => nameserver, :qtype => nsatype)
|
|
98
|
+
ips = iprrs ? iprrs.map {|iprr| iprr.address.to_s } : nil
|
|
99
|
+
starters.push({ :name => nameserver, :ips => ips })
|
|
100
|
+
end
|
|
101
|
+
newbailiwick = ns[0].name.to_s
|
|
102
|
+
Log.debug { "For domain #{domain} using start servers: " +
|
|
103
|
+
starters.map { |x| x[:name] }.join(', ') }
|
|
104
|
+
return starters, newbailiwick
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# DNSTraverse traverses the DNS to show statistics and information
|
|
2
|
+
# Copyright (C) 2008 James Ponder
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
require 'logger'
|
|
17
|
+
|
|
18
|
+
module Log
|
|
19
|
+
include Logger::Severity
|
|
20
|
+
|
|
21
|
+
class Formatter
|
|
22
|
+
Format = "%s, [%s #%d] %5s -- %s: %s\n"
|
|
23
|
+
|
|
24
|
+
def call(severity, time, progname, msg)
|
|
25
|
+
t = time.strftime("%Y-%m-%d %H:%M:%S.") << "%06d" % time.usec
|
|
26
|
+
#t = ""
|
|
27
|
+
msg2str(msg).split(/\n/).map do |m|
|
|
28
|
+
Format % [severity[0..0], t, $$, severity, progname, m]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def msg2str(msg)
|
|
33
|
+
case msg
|
|
34
|
+
when ::Exception
|
|
35
|
+
"#{ msg.message } (#{ msg.class })\n" <<
|
|
36
|
+
(msg.backtrace || []).join("\n")
|
|
37
|
+
else
|
|
38
|
+
msg.to_s
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.level=(l)
|
|
44
|
+
@@logger.level = l
|
|
45
|
+
end
|
|
46
|
+
def self.logger=(logger)
|
|
47
|
+
@@logger = logger
|
|
48
|
+
end
|
|
49
|
+
def self.method_missing(key, *args, &b)
|
|
50
|
+
@@logger.send(key, *args, &b)
|
|
51
|
+
end
|
|
52
|
+
@@logger = Logger.new(STDERR)
|
|
53
|
+
@@logger.formatter = Formatter.new
|
|
54
|
+
@@logger.level = Logger::FATAL
|
|
55
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# DNSTraverse traverses the DNS to show statistics and information
|
|
2
|
+
# Copyright (C) 2008 James Ponder
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
module DNSTraverse
|
|
17
|
+
class ResolveError < RuntimeError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module MessageUtility
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def msg_comment(msg, args)
|
|
24
|
+
warnings = Array.new
|
|
25
|
+
if args[:want_recursion] then
|
|
26
|
+
if not msg.header.ra then
|
|
27
|
+
warnings.push "#{msg.answerfrom} doesn't allow recursion"
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
if msg.header.ra then
|
|
31
|
+
warnings.push "#{msg.answerfrom} allows recursion"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
if msg.header.tc then
|
|
35
|
+
warnings.push "#{msg.answerfrom} sent truncated packet"
|
|
36
|
+
end
|
|
37
|
+
for warn in warnings do
|
|
38
|
+
Log.warn { warn }
|
|
39
|
+
end
|
|
40
|
+
Log.debug { "#{msg.answerfrom} code #{msg.rcode}" }
|
|
41
|
+
return warnings
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def msg_validate(msg, args)
|
|
45
|
+
a = args.dup
|
|
46
|
+
a[:qclass]||= 'IN'
|
|
47
|
+
return true if msg.rcode != Dnsruby::RCode.NOERROR
|
|
48
|
+
begin
|
|
49
|
+
if msg.question.size != 1 then
|
|
50
|
+
raise ResolveError, "#{msg.answerfrom} returned unexpected " +
|
|
51
|
+
"question size #{msg.question.size}"
|
|
52
|
+
end
|
|
53
|
+
for c in [:qname, :qclass, :qtype] do
|
|
54
|
+
if a[c] and
|
|
55
|
+
a[c].to_s.downcase != msg.question[0].send(c).to_s.downcase then
|
|
56
|
+
raise ResolveError, "#{msg.answerfrom} returned mismatched #{c} " +
|
|
57
|
+
"#{msg.question[0].send(c)} instead of expected #{a[c]}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
rescue => e
|
|
61
|
+
Log.debug { "Failed message was: " + msg.to_s }
|
|
62
|
+
raise e
|
|
63
|
+
end
|
|
64
|
+
return true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def msg_answers?(msg, args)
|
|
68
|
+
qname = args[:qname].to_s
|
|
69
|
+
qclass = (args[:qclass] || 'IN').to_s
|
|
70
|
+
qtype = args[:qtype].to_s
|
|
71
|
+
any = qtype.casecmp('ANY') == 0 ? true : false
|
|
72
|
+
ans = msg.answer.select { |x|
|
|
73
|
+
x.name.to_s.casecmp(qname) == 0 &&
|
|
74
|
+
x.klass.to_s.casecmp(qclass) == 0 &&
|
|
75
|
+
(any || x.type.to_s.casecmp(qtype) == 0)
|
|
76
|
+
}
|
|
77
|
+
Log.debug { "Answers:" + ans.size.to_s}
|
|
78
|
+
return ans.size > 0 ? ans : nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def msg_additional?(msg, args)
|
|
82
|
+
qclass = args[:qclass] || 'IN'
|
|
83
|
+
Log.debug { "Looking for #{args[:qname]}/#{args[:qtype]} in additional" }
|
|
84
|
+
add = msg.additional.select { |x|
|
|
85
|
+
x.name.to_s.casecmp(args[:qname].to_s) == 0 &&
|
|
86
|
+
x.klass.to_s.casecmp(qclass.to_s) == 0 &&
|
|
87
|
+
x.type.to_s.casecmp(args[:qtype].to_s) == 0
|
|
88
|
+
}
|
|
89
|
+
Log.debug { add.size > 0 ? "Found #{add.size} additional records" \
|
|
90
|
+
: "No additional records for #{args[:qname]}/#{args[:qtype]}"}
|
|
91
|
+
return add.size > 0 ? add : nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def msg_additional_ips?(msg, args)
|
|
95
|
+
qclass = args[:qclass] || 'IN'
|
|
96
|
+
Log.debug { "Looking for #{args[:qname]}/#{args[:qtype]} in additional" }
|
|
97
|
+
if add = msg.additional.select { |x|
|
|
98
|
+
x.name.to_s.casecmp(args[:qname].to_s) == 0 &&
|
|
99
|
+
x.klass.to_s.casecmp(qclass.to_s) == 0 &&
|
|
100
|
+
x.type.to_s.casecmp(args[:qtype].to_s) == 0
|
|
101
|
+
} then
|
|
102
|
+
ips = add.map {|x| x.address.to_s }
|
|
103
|
+
Log.debug { "Found in additional #{args[:qname]} = " + ips.join(", ") }
|
|
104
|
+
return ips
|
|
105
|
+
end
|
|
106
|
+
Log.debug { "No additional records for #{args[:qname]}/#{args[:qtype]}" }
|
|
107
|
+
return nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# def msg_referrals(msg, args)
|
|
111
|
+
# r = msg.authority.select { |x|
|
|
112
|
+
# x.type.to_s.casecmp('NS') == 0 && x.klass.to_s.casecmp('IN') == 0
|
|
113
|
+
# }
|
|
114
|
+
# if args[:bailiwick] then
|
|
115
|
+
# b = args[:bailiwick]
|
|
116
|
+
# r = r.select { |x|
|
|
117
|
+
# zonename = x.name.to_s
|
|
118
|
+
# if cond = zonename !~ /#{@b}$/i then
|
|
119
|
+
# Log.debug { "Excluding lame referral #{b} to #{zonename}" }
|
|
120
|
+
# raise "lame"
|
|
121
|
+
# end
|
|
122
|
+
# cond
|
|
123
|
+
# }
|
|
124
|
+
# end
|
|
125
|
+
# Log.debug { "Referrals: " + r.map {|x| x.domainname.to_s }.join(", ") }
|
|
126
|
+
# return r
|
|
127
|
+
# end
|
|
128
|
+
|
|
129
|
+
def msg_authority(msg)
|
|
130
|
+
ns = []
|
|
131
|
+
soa = []
|
|
132
|
+
other = []
|
|
133
|
+
for rr in msg.authority do
|
|
134
|
+
type = rr.type.to_s
|
|
135
|
+
klass = rr.klass.to_s
|
|
136
|
+
if type.casecmp('NS') == 0 && klass.casecmp('IN') == 0
|
|
137
|
+
ns.push rr
|
|
138
|
+
elsif type.casecmp('SOA') == 0 && klass.casecmp('IN') == 0
|
|
139
|
+
soa.push rr
|
|
140
|
+
else
|
|
141
|
+
other.push rr
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
return ns, soa, other
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def msg_follow_cnames(msg, args)
|
|
148
|
+
name = args[:qname]
|
|
149
|
+
type = args[:qtype]
|
|
150
|
+
bw = args[:bailiwick].to_s
|
|
151
|
+
bwend = ".#{args[:bailiwick]}"
|
|
152
|
+
while true do
|
|
153
|
+
return name if msg_answers?(msg, :qname => name, :qtype => type)
|
|
154
|
+
if not ans = msg_answers?(msg, :qname => name, :qtype => 'CNAME') then
|
|
155
|
+
return name
|
|
156
|
+
end
|
|
157
|
+
target = ans[0].domainname.to_s
|
|
158
|
+
Log.debug { "CNAME encountered from #{name} to #{target}"}
|
|
159
|
+
if bw and (target.casecmp(bw) != 0 and name !~ /#{bwend}$/i) then
|
|
160
|
+
# target outside of bailiwick, don't follow any more CNAMEs.
|
|
161
|
+
return target
|
|
162
|
+
end
|
|
163
|
+
name = target
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def msg_nodata?(msg)
|
|
168
|
+
ns, soa, other = msg_authority(msg)
|
|
169
|
+
if soa.size > 0 or ns.size == 0 then
|
|
170
|
+
Log.debug { "NODATA: soa=#{soa.size} ns=#{ns.size}" }
|
|
171
|
+
return true
|
|
172
|
+
end
|
|
173
|
+
return false
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def msg_cacheable(msg, bailiwick, type = :both)
|
|
177
|
+
good, bad = Array.new, Array.new
|
|
178
|
+
bw = bailiwick.to_s
|
|
179
|
+
bwend = "." + bw
|
|
180
|
+
for section in [:additional, :authority] do
|
|
181
|
+
for rr in msg.send(section) do
|
|
182
|
+
name = rr.name.to_s
|
|
183
|
+
if bailiwick.nil? or name.casecmp(bw) == 0 or
|
|
184
|
+
name =~ /#{bwend}$/i then
|
|
185
|
+
good.push rr
|
|
186
|
+
else
|
|
187
|
+
bad.push rr
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
good.map {|x| Log.debug { "Records within bailiwick: " + x.to_s } }
|
|
192
|
+
bad.map {|x| Log.debug { "Records outside bailiwick: " + x.to_s } }
|
|
193
|
+
return good if type == :good
|
|
194
|
+
return bad if type == :bad
|
|
195
|
+
return good, bad
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# DNSTraverse traverses the DNS to show statistics and information
|
|
2
|
+
# Copyright (C) 2008 James Ponder
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
require 'dnstraverse/response'
|
|
17
|
+
require 'dnstraverse/response_noglue'
|
|
18
|
+
require 'dnstraverse/info_cache'
|
|
19
|
+
require 'dnstraverse/decoded_query_cache'
|
|
20
|
+
|
|
21
|
+
module DNSTraverse
|
|
22
|
+
|
|
23
|
+
class Referral
|
|
24
|
+
include MessageUtility
|
|
25
|
+
|
|
26
|
+
attr_reader :server, :serverips, :qname, :qclass, :qtype, :nsatype
|
|
27
|
+
attr_reader :refid, :message, :infocache, :parent, :bailiwick, :stats
|
|
28
|
+
attr_reader :warnings, :children, :parent_ip
|
|
29
|
+
attr_reader :decoded_query_cache
|
|
30
|
+
|
|
31
|
+
EMPTY_ARRAY = [].freeze
|
|
32
|
+
|
|
33
|
+
def txt_ips_verbose
|
|
34
|
+
return '' unless @serverips
|
|
35
|
+
a = @serverips.map do |ip|
|
|
36
|
+
sprintf("%.1f%%=", 100 * @serverweights[ip]).concat(ip =~ /^key:([^:]+(:[^:]*)?)/ ? $1 : ip)
|
|
37
|
+
end
|
|
38
|
+
a.sort.join(',')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def txt_ips
|
|
42
|
+
return '' unless @serverips
|
|
43
|
+
@serverips.map { |ip|
|
|
44
|
+
ip =~ /^key:/ ? @stats_resolve[ip][:response].to_s : ip
|
|
45
|
+
}.sort.join(',')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ips_as_array will return any IP addresses we know for this referral server
|
|
49
|
+
def ips_as_array
|
|
50
|
+
return EMPTY_ARRAY unless @serverips
|
|
51
|
+
my_ips = []
|
|
52
|
+
for ip in @serverips do
|
|
53
|
+
my_ips << ip unless ip =~ /^key:/
|
|
54
|
+
end
|
|
55
|
+
return my_ips
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_s
|
|
59
|
+
return "#{@refid} [#{@qname}/#{@qclass}/#{@qtype}] server=#{@server} " +
|
|
60
|
+
"server_ips=#{txt_ips()} bailiwick=#{@bailiwick}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def referral_resolution?
|
|
64
|
+
return @referral_resolution ? true : false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Referral object represents a particular referral to a specified server
|
|
68
|
+
# with given qname, qclass and qtype.
|
|
69
|
+
#
|
|
70
|
+
# roots can be passed in, which will be used to populate root hints in to
|
|
71
|
+
# the infocache, which if not passed in will be automatically created
|
|
72
|
+
#
|
|
73
|
+
# server can be nil which is a special case and causes all the roots
|
|
74
|
+
# to be added as child referrals (uses infocache to lookup roots)
|
|
75
|
+
#
|
|
76
|
+
# if the server's IP address(es) are known, they are passed in as serverips
|
|
77
|
+
# otherwise, we will resolve the serverips
|
|
78
|
+
#
|
|
79
|
+
# referral_resolution should be set to false. children that are a result
|
|
80
|
+
# of a resolution of a referral that didn't have glue records will have
|
|
81
|
+
# this set to true so that you can distringuish this detail
|
|
82
|
+
def initialize(args)
|
|
83
|
+
@resolver = args[:resolver] # Dnsruby::Resolver object
|
|
84
|
+
@qname = args[:qname]
|
|
85
|
+
@qclass = args[:qclass] || :IN
|
|
86
|
+
@qtype = args[:qtype] || :A
|
|
87
|
+
@nsatype = args[:nsatype] || :A
|
|
88
|
+
@infocache = args[:infocache] || DNSTraverse::InfoCache.new
|
|
89
|
+
@roots = args[:roots]
|
|
90
|
+
@resolves = nil # Array of referral objects for resolving phase
|
|
91
|
+
@refid = args[:refid] || ''
|
|
92
|
+
@server = args[:server] || nil # nil for the root-root server
|
|
93
|
+
@serverips = args[:serverips] || nil
|
|
94
|
+
@responses = Hash.new # responses/exception for each IP in @serverips
|
|
95
|
+
@children = Hash.new # Array of child Referrer objects keyed by IP
|
|
96
|
+
@bailiwick = args[:bailiwick] || nil
|
|
97
|
+
@secure = args[:secure] || true # ensure bailiwick checks
|
|
98
|
+
@parent = args[:parent] || nil # Parent Referral
|
|
99
|
+
@parent_ip = args[:parent_ip] || nil # Parent Referral IP if applicable
|
|
100
|
+
@maxdepth = args[:maxdepth] || 10 # maximum depth before error
|
|
101
|
+
@decoded_query_cache = args[:decoded_query_cache]
|
|
102
|
+
@referral_resolution = args[:referral_resolution] || false # flag
|
|
103
|
+
@stats = nil # will contain statistics for answers
|
|
104
|
+
@stats_resolve = nil # will contain statistics for our resolve (if applic)
|
|
105
|
+
@serverweights = Hash.new # key is IP
|
|
106
|
+
@warnings = Array.new # warnings will be placed here
|
|
107
|
+
@processed = false # flag for processed? method
|
|
108
|
+
raise "Must pass Resolver" unless @resolver
|
|
109
|
+
@infocache.add_hints('', args[:roots]) if args[:roots] # add root hints
|
|
110
|
+
unless @decoded_query_cache then
|
|
111
|
+
dcq_args = { :resolver => @resolver}
|
|
112
|
+
@decoded_query_cache = DNSTraverse::DecodedQueryCache.new(dcq_args)
|
|
113
|
+
end
|
|
114
|
+
if serverips then # we know the server weights - we're not resolving
|
|
115
|
+
for ip in serverips do
|
|
116
|
+
@serverweights[ip] = 1.0 / @serverips.length
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
Log.debug { "New resolver object created: " + self.to_s }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def showstats
|
|
123
|
+
s = Hash.new
|
|
124
|
+
ObjectSpace.each_object do |o|
|
|
125
|
+
s[o.class]||= 0
|
|
126
|
+
s[o.class]= s[o.class] + 1
|
|
127
|
+
end
|
|
128
|
+
s.sort {|a,b| a[1] <=> b[1]}.each do | c |
|
|
129
|
+
puts "#{c[1]} #{c[0]}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# clean up the workings
|
|
134
|
+
def cleanup(args = nil)
|
|
135
|
+
Log.debug { "cleaning: #{self}" }
|
|
136
|
+
@infocache = nil unless args and args[:infocache]
|
|
137
|
+
@cacheable_good = @cacheable_bad = nil unless args and args[:cacheable]
|
|
138
|
+
@starters = @starters_bailiwick = nil unless args and args[:starters]
|
|
139
|
+
@auth_ns = @auth_soa = @auth_other = nil unless args and args[:auth]
|
|
140
|
+
@children = nil unless args and args[:children]
|
|
141
|
+
@resolves = nil unless args and args[:resolves]
|
|
142
|
+
@responses = nil unless args and args[:responses]
|
|
143
|
+
@decoded_query_cache = nil unless args and args[:decoded_query_cache]
|
|
144
|
+
@resolver = nil unless args and args[:resolver]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def inside_bailiwick?(name)
|
|
148
|
+
return true if @bailiwick.nil?
|
|
149
|
+
bwend = ".#{@bailiwick}"
|
|
150
|
+
namestr = name.to_s
|
|
151
|
+
return true if namestr.casecmp(@bailiwick) == 0
|
|
152
|
+
return true if namestr =~ /#{bwend}$/i
|
|
153
|
+
return false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def noglue?
|
|
157
|
+
return false if @serverips
|
|
158
|
+
return false unless inside_bailiwick?(@server)
|
|
159
|
+
return true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# resolve server to serverips, return list of Referral objects to process
|
|
163
|
+
def resolve(*args)
|
|
164
|
+
raise "This Referral object has already been resolved" if resolved?
|
|
165
|
+
if noglue? then
|
|
166
|
+
# foo.net IN NS ns.foo.net - no IP cached & no glue = failure
|
|
167
|
+
Log.debug { "Attempt to resolve #{@server} with a bailiwick referral " +
|
|
168
|
+
" of #{bailiwick} - no glue record provided" }
|
|
169
|
+
return Array.new
|
|
170
|
+
end
|
|
171
|
+
refid = "#{@refid}.0"
|
|
172
|
+
child_refid = 1
|
|
173
|
+
starters, newbailiwick = @infocache.get_startservers(@server)
|
|
174
|
+
Log.debug { "Resolving #{@server} type #{@nsatype} " }
|
|
175
|
+
for starter in starters do
|
|
176
|
+
r = make_referral(:server => starter[:name],
|
|
177
|
+
:serverips => starter[:ips],
|
|
178
|
+
:referral_resolution => true,
|
|
179
|
+
:qname => @server, :qclass => 'IN',
|
|
180
|
+
:qtype => @nsatype, :bailiwick => newbailiwick,
|
|
181
|
+
:refid => "#{refid}.#{child_refid}")
|
|
182
|
+
(@resolves||= []) << r
|
|
183
|
+
child_refid+= 1
|
|
184
|
+
end
|
|
185
|
+
# return a set of Referral objects that need to be processed
|
|
186
|
+
return @resolves
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def resolve_calculate
|
|
190
|
+
Log.debug { "Calculating resolution: #{self}" }
|
|
191
|
+
# create stats_resolve containing all the statistics of the resolution
|
|
192
|
+
@stats_resolve = Hash.new
|
|
193
|
+
if noglue? then # in-bailiwick referral without glue
|
|
194
|
+
r = DNSTraverse::Response::NoGlue.new(:qname => @qname,
|
|
195
|
+
:qclass => @qclass,
|
|
196
|
+
:qtype => @qtype,
|
|
197
|
+
:server => @server,
|
|
198
|
+
:ip => @parent_ip,
|
|
199
|
+
:bailiwick => @bailiwick)
|
|
200
|
+
@stats_resolve[r.stats_key] = { :prob => 1.0, :response => r,
|
|
201
|
+
:referral => self }
|
|
202
|
+
else
|
|
203
|
+
# normal resolve - combine children's statistics in to @stats_resolve
|
|
204
|
+
stats_calculate_children(@stats_resolve, @resolves, 1.0)
|
|
205
|
+
end
|
|
206
|
+
# now use this data to work out %age of each IP address returned
|
|
207
|
+
@serverweights = Hash.new
|
|
208
|
+
@stats_resolve.each_pair do |key, data|
|
|
209
|
+
# key = IP or key:blah, data is hash containing :prob, etc.
|
|
210
|
+
if data[:response].status == :answered then # RR records
|
|
211
|
+
# there were some answers - so add the probabilities in
|
|
212
|
+
answers = data[:response].answers # weight RRs evenly
|
|
213
|
+
for rr in answers do
|
|
214
|
+
@serverweights[rr.address.to_s]||= 0
|
|
215
|
+
@serverweights[rr.address.to_s]+= data[:prob] / answers.length
|
|
216
|
+
end
|
|
217
|
+
else
|
|
218
|
+
# there were no answers - use the special key and record probabilities
|
|
219
|
+
@serverweights[key]||= 0
|
|
220
|
+
@serverweights[key]+= data[:prob]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
@serverips = @serverweights.keys
|
|
224
|
+
Log.debug { "Calculating resolution (answer): #{@serverips.join(',')}" }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def stats_calculate_children(stats, children, weight)
|
|
228
|
+
percent = (1.0 / children.length) * weight
|
|
229
|
+
for child in children do
|
|
230
|
+
child.stats.each_pair do |key, data|
|
|
231
|
+
if not stats[key] then
|
|
232
|
+
# just copy the child's statistics for this key
|
|
233
|
+
stats[key] = data.dup
|
|
234
|
+
stats[key][:prob]*= percent
|
|
235
|
+
else
|
|
236
|
+
stats[key][:prob]+= data[:prob] * percent
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def answer_calculate
|
|
243
|
+
Log.debug { "Calculating answer: #{self}" }
|
|
244
|
+
@stats = Hash.new
|
|
245
|
+
|
|
246
|
+
if not @server then
|
|
247
|
+
# special case - rootroot, no actual IPs, just root referrals
|
|
248
|
+
stats_calculate_children(@stats, @children[:rootroot], 1.0)
|
|
249
|
+
@stats.each_pair do |key, data|
|
|
250
|
+
Log.debug { sprintf "Answer: %.2f%% %s\n", data[:prob] * 100, key }
|
|
251
|
+
end
|
|
252
|
+
return
|
|
253
|
+
end
|
|
254
|
+
for ip in @serverips do
|
|
255
|
+
serverweight = @serverweights[ip] # set at initialize or at resolve
|
|
256
|
+
if ip =~ /^key:/ then # resolve failed for some reason
|
|
257
|
+
# pull out the statistics on the resolution and copy over
|
|
258
|
+
raise "duplicate key found" if @stats[ip] # assertion
|
|
259
|
+
if @stats_resolve[ip][:prob] != serverweight then # assertion
|
|
260
|
+
$stderr.puts "#{@stats_resolve[ip][:prob]} vs #{serverweight}"
|
|
261
|
+
@stats_resolve[ip].each_pair do |a,b|
|
|
262
|
+
$stderr.puts a
|
|
263
|
+
end
|
|
264
|
+
raise "unexpected probability"
|
|
265
|
+
end
|
|
266
|
+
@stats[ip] = @stats_resolve[ip].dup
|
|
267
|
+
next
|
|
268
|
+
end
|
|
269
|
+
if @children[ip] then
|
|
270
|
+
stats_calculate_children(@stats, @children[ip], serverweight)
|
|
271
|
+
else
|
|
272
|
+
response = @responses[ip]
|
|
273
|
+
@stats[response.stats_key] = { :prob => serverweight,
|
|
274
|
+
:response => response, :referral => self }
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
@stats.each_pair do |key, data|
|
|
278
|
+
Log.debug { sprintf "Answer: %.2f%% %s\n", data[:prob] * 100, key }
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def processed?
|
|
283
|
+
return @processed
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def resolved?
|
|
287
|
+
# root-root is always resolved, otherwise check we have IP addresses
|
|
288
|
+
return true if is_rootroot?
|
|
289
|
+
return true if @noglue
|
|
290
|
+
return false if @serverips.nil?
|
|
291
|
+
return true
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def is_rootroot?
|
|
295
|
+
# rootroot is the topmost object representing an automatic referral
|
|
296
|
+
# to all the root servers
|
|
297
|
+
@server.nil? ? true : false
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def process(args)
|
|
301
|
+
raise "This Referral object has already been processed" if processed?
|
|
302
|
+
raise "You need to resolve this Referral object" unless resolved?
|
|
303
|
+
if (server) then
|
|
304
|
+
process_normal(args)
|
|
305
|
+
else
|
|
306
|
+
# special case - no server means start from the top with the roots
|
|
307
|
+
process_add_roots(args)
|
|
308
|
+
end
|
|
309
|
+
# return a set of Referral objects that need to be processed
|
|
310
|
+
# XXX flatten really necessary?
|
|
311
|
+
@processed = true
|
|
312
|
+
return @children.values.flatten.select {|x| x.is_a? Referral}
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def process_add_roots(args)
|
|
316
|
+
Log.debug { "Special case processing, addding roots as referrals" }
|
|
317
|
+
refid_prefix = @refid == '' ? '' : "#{@refid}."
|
|
318
|
+
refid = 1
|
|
319
|
+
starters = (@infocache.get_startservers('', @nsatype))[0]
|
|
320
|
+
@children[:rootroot] = Array.new # use 'rootroot' instead of IP address
|
|
321
|
+
for root in starters do
|
|
322
|
+
r = make_referral(:server => root[:name], :serverips => root[:ips],
|
|
323
|
+
:refid => "#{refid_prefix}#{refid}")
|
|
324
|
+
@children[:rootroot].push r
|
|
325
|
+
refid+= 1
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def check_loop?(args) # :ip, :qtype, :qname, :qclass
|
|
330
|
+
parent = @parent
|
|
331
|
+
until parent.nil? do
|
|
332
|
+
if parent.qname.to_s == args[:qname].to_s and
|
|
333
|
+
parent.qclass.to_s == args[:qclass].to_s and
|
|
334
|
+
parent.qtype.to_s == args[:qtype].to_s and
|
|
335
|
+
parent.serverips and parent.serverips.include?(args[:ip]) then
|
|
336
|
+
exit 1 # XXX fix me
|
|
337
|
+
return RuntimeError.new("Loop detected")
|
|
338
|
+
end
|
|
339
|
+
parent = parent.parent
|
|
340
|
+
end
|
|
341
|
+
return nil
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def process_normal(args)
|
|
345
|
+
Log.debug { "process " + self.to_s }
|
|
346
|
+
# if l = check_loop?(:ip => ip, :qname => @qname,
|
|
347
|
+
# :qtype => @qtype, :qclass => @qclass) then
|
|
348
|
+
# for ip in @serverips do
|
|
349
|
+
# @responses[ip] = l
|
|
350
|
+
# done
|
|
351
|
+
# return
|
|
352
|
+
# end
|
|
353
|
+
# end
|
|
354
|
+
for ip in @serverips do
|
|
355
|
+
Log.debug { "Process normal #{ip}" }
|
|
356
|
+
next if ip =~ /^key:/ # resolve failed on something
|
|
357
|
+
m = nil
|
|
358
|
+
if @refid.scan(/\./).length >= @maxdepth.to_i then
|
|
359
|
+
m = RuntimeError.new "Maxdepth #{@maxdepth} exceeded"
|
|
360
|
+
end
|
|
361
|
+
Log.debug { "Process normal #{ip} - making response" }
|
|
362
|
+
r = DNSTraverse::Response.new(:message => m, :qname => @qname,
|
|
363
|
+
:qclass => @qclass, :qtype => @qtype,
|
|
364
|
+
:bailiwick => @bailiwick,
|
|
365
|
+
:infocache => @infocache, :ip => ip,
|
|
366
|
+
:decoded_query_cache => @decoded_query_cache)
|
|
367
|
+
Log.debug { "Process normal #{ip} - done making response" }
|
|
368
|
+
@responses[ip] = r
|
|
369
|
+
case r.status
|
|
370
|
+
when :restart, :referral then
|
|
371
|
+
Log.debug { "Process normal #{ip} - making referrals" }
|
|
372
|
+
@children[ip] = make_referrals(:qname => r.endname,
|
|
373
|
+
:starters => r.starters,
|
|
374
|
+
:bailiwick => r.starters_bailiwick,
|
|
375
|
+
:infocache => r.infocache,
|
|
376
|
+
:parent_ip => ip)
|
|
377
|
+
Log.debug { "Process normal #{ip} - done making referrals" }
|
|
378
|
+
# XXX shouldn't be any children unless referrals
|
|
379
|
+
#when :referral_lame then
|
|
380
|
+
#@children[ip] = RuntimeError.new "Improper or lame delegation"
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def make_referrals(args) # :starters can be @root or our own list
|
|
386
|
+
starters = args[:starters]
|
|
387
|
+
children = Array.new
|
|
388
|
+
child_refid = 1
|
|
389
|
+
for starter in starters do
|
|
390
|
+
refargs = {
|
|
391
|
+
:server => starter[:name],
|
|
392
|
+
:serverips => starter[:ips],
|
|
393
|
+
:refid => "#{refid}.#{child_refid}"
|
|
394
|
+
}.merge(args)
|
|
395
|
+
children.push make_referral(refargs)
|
|
396
|
+
child_refid+= 1
|
|
397
|
+
end
|
|
398
|
+
return children
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def make_referral(args)
|
|
402
|
+
raise "Must pass new refid" unless args[:refid]
|
|
403
|
+
refargs = { :qname => @qname, :qclass => @qclass,
|
|
404
|
+
:qtype => @qtype, :nsatype => @nsatype, :infocache => @infocache,
|
|
405
|
+
:referral_resolution => @referral_resolution,
|
|
406
|
+
:resolver => @resolver, :maxdepth => @maxdepth, :parent => self,
|
|
407
|
+
:decoded_query_cache => @decoded_query_cache }.merge(args)
|
|
408
|
+
return Referral.new(refargs)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def replace_child(before, after)
|
|
412
|
+
@children.each_key do | ip |
|
|
413
|
+
@children[ip].map! { |c| c.equal?(before) ? after : c }
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def stats_display(args)
|
|
418
|
+
spacing = args[:spacing] || false
|
|
419
|
+
results = args[:results] || true
|
|
420
|
+
prefix = args[:prefix] || ''
|
|
421
|
+
indent = args[:indent] || "#{prefix} "
|
|
422
|
+
first = true
|
|
423
|
+
@stats.keys.sort!.each do | key |
|
|
424
|
+
data = @stats[key]
|
|
425
|
+
puts if spacing and not first
|
|
426
|
+
first = false
|
|
427
|
+
printf "#{prefix}%5.1f%%: ", data[:prob] * 100
|
|
428
|
+
response = data[:response]
|
|
429
|
+
referral = data[:referral]
|
|
430
|
+
where = "#{referral.server} (#{response.ip})"
|
|
431
|
+
case response.status
|
|
432
|
+
when :exception
|
|
433
|
+
puts "#{response.exception_message} at #{where}"
|
|
434
|
+
when :noglue
|
|
435
|
+
puts "No glue at #{referral.parent.server} " +
|
|
436
|
+
"(#{response.ip}) for #{response.server}"
|
|
437
|
+
when :error
|
|
438
|
+
puts "#{response.error_message} at #{where}"
|
|
439
|
+
when :nodata
|
|
440
|
+
puts "NODATA (for this type) at #{where})"
|
|
441
|
+
when :answered
|
|
442
|
+
puts "Answer from #{where}"
|
|
443
|
+
if results then
|
|
444
|
+
for rr in data[:response].answers do
|
|
445
|
+
puts "#{indent}#{rr}"
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
else
|
|
449
|
+
puts "Stopped at #{where})"
|
|
450
|
+
puts "#{indent}#{key}"
|
|
451
|
+
end
|
|
452
|
+
if response.status != :answered and
|
|
453
|
+
((response.qname != @qname) or (response.qclass != @qclass) or
|
|
454
|
+
(response.qtype != @qtype)) then
|
|
455
|
+
puts "#{indent}While querying #{response.qname}/" +
|
|
456
|
+
"#{response.qclass}/#{response.qtype}"
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
end
|