dnstraverse 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. data/LICENSE +674 -0
  2. data/README +4 -0
  3. data/Rakefile +19 -0
  4. data/a +44 -0
  5. data/bin/dnstraverse +300 -0
  6. data/doc.tar +0 -0
  7. data/doc/classes/DNSTraverse.html +198 -0
  8. data/doc/classes/DNSTraverse/CachingResolver.html +172 -0
  9. data/doc/classes/DNSTraverse/CachingResolver.src/M000055.html +21 -0
  10. data/doc/classes/DNSTraverse/CachingResolver.src/M000056.html +42 -0
  11. data/doc/classes/DNSTraverse/DecodedQuery.html +475 -0
  12. data/doc/classes/DNSTraverse/DecodedQuery.src/M000020.html +34 -0
  13. data/doc/classes/DNSTraverse/DecodedQuery.src/M000021.html +23 -0
  14. data/doc/classes/DNSTraverse/DecodedQuery.src/M000022.html +19 -0
  15. data/doc/classes/DNSTraverse/DecodedQuery.src/M000023.html +33 -0
  16. data/doc/classes/DNSTraverse/DecodedQuery.src/M000024.html +26 -0
  17. data/doc/classes/DNSTraverse/DecodedQuery.src/M000025.html +24 -0
  18. data/doc/classes/DNSTraverse/DecodedQuery.src/M000026.html +29 -0
  19. data/doc/classes/DNSTraverse/DecodedQuery.src/M000027.html +19 -0
  20. data/doc/classes/DNSTraverse/DecodedQuery.src/M000028.html +18 -0
  21. data/doc/classes/DNSTraverse/DecodedQuery.src/M000029.html +32 -0
  22. data/doc/classes/DNSTraverse/DecodedQuery.src/M000030.html +19 -0
  23. data/doc/classes/DNSTraverse/DecodedQuery.src/M000031.html +19 -0
  24. data/doc/classes/DNSTraverse/DecodedQuery.src/M000032.html +20 -0
  25. data/doc/classes/DNSTraverse/DecodedQuery.src/M000033.html +31 -0
  26. data/doc/classes/DNSTraverse/DecodedQueryCache.html +182 -0
  27. data/doc/classes/DNSTraverse/DecodedQueryCache.src/M000063.html +21 -0
  28. data/doc/classes/DNSTraverse/DecodedQueryCache.src/M000064.html +33 -0
  29. data/doc/classes/DNSTraverse/Fingerprint.html +277 -0
  30. data/doc/classes/DNSTraverse/Fingerprint.src/M000047.html +29 -0
  31. data/doc/classes/DNSTraverse/Fingerprint.src/M000048.html +18 -0
  32. data/doc/classes/DNSTraverse/Fingerprint.src/M000049.html +57 -0
  33. data/doc/classes/DNSTraverse/Fingerprint.src/M000050.html +28 -0
  34. data/doc/classes/DNSTraverse/Fingerprint.src/M000051.html +27 -0
  35. data/doc/classes/DNSTraverse/Fingerprint.src/M000052.html +35 -0
  36. data/doc/classes/DNSTraverse/Fingerprint.src/M000053.html +24 -0
  37. data/doc/classes/DNSTraverse/Fingerprint.src/M000054.html +29 -0
  38. data/doc/classes/DNSTraverse/InfoCache.html +235 -0
  39. data/doc/classes/DNSTraverse/InfoCache.src/M000034.html +20 -0
  40. data/doc/classes/DNSTraverse/InfoCache.src/M000035.html +23 -0
  41. data/doc/classes/DNSTraverse/InfoCache.src/M000036.html +28 -0
  42. data/doc/classes/DNSTraverse/InfoCache.src/M000037.html +25 -0
  43. data/doc/classes/DNSTraverse/InfoCache.src/M000038.html +27 -0
  44. data/doc/classes/DNSTraverse/InfoCache.src/M000039.html +33 -0
  45. data/doc/classes/DNSTraverse/MessageUtility.html +275 -0
  46. data/doc/classes/DNSTraverse/MessageUtility.src/M000011.html +35 -0
  47. data/doc/classes/DNSTraverse/MessageUtility.src/M000012.html +37 -0
  48. data/doc/classes/DNSTraverse/MessageUtility.src/M000013.html +28 -0
  49. data/doc/classes/DNSTraverse/MessageUtility.src/M000014.html +27 -0
  50. data/doc/classes/DNSTraverse/MessageUtility.src/M000015.html +30 -0
  51. data/doc/classes/DNSTraverse/MessageUtility.src/M000016.html +32 -0
  52. data/doc/classes/DNSTraverse/MessageUtility.src/M000017.html +34 -0
  53. data/doc/classes/DNSTraverse/MessageUtility.src/M000018.html +23 -0
  54. data/doc/classes/DNSTraverse/MessageUtility.src/M000019.html +36 -0
  55. data/doc/classes/DNSTraverse/Referral.html +641 -0
  56. data/doc/classes/DNSTraverse/Referral.src/M000065.html +22 -0
  57. data/doc/classes/DNSTraverse/Referral.src/M000066.html +21 -0
  58. data/doc/classes/DNSTraverse/Referral.src/M000067.html +23 -0
  59. data/doc/classes/DNSTraverse/Referral.src/M000068.html +19 -0
  60. data/doc/classes/DNSTraverse/Referral.src/M000069.html +18 -0
  61. data/doc/classes/DNSTraverse/Referral.src/M000070.html +54 -0
  62. data/doc/classes/DNSTraverse/Referral.src/M000071.html +25 -0
  63. data/doc/classes/DNSTraverse/Referral.src/M000072.html +27 -0
  64. data/doc/classes/DNSTraverse/Referral.src/M000073.html +23 -0
  65. data/doc/classes/DNSTraverse/Referral.src/M000074.html +20 -0
  66. data/doc/classes/DNSTraverse/Referral.src/M000075.html +40 -0
  67. data/doc/classes/DNSTraverse/Referral.src/M000076.html +52 -0
  68. data/doc/classes/DNSTraverse/Referral.src/M000077.html +29 -0
  69. data/doc/classes/DNSTraverse/Referral.src/M000078.html +54 -0
  70. data/doc/classes/DNSTraverse/Referral.src/M000079.html +18 -0
  71. data/doc/classes/DNSTraverse/Referral.src/M000080.html +22 -0
  72. data/doc/classes/DNSTraverse/Referral.src/M000081.html +20 -0
  73. data/doc/classes/DNSTraverse/Referral.src/M000082.html +29 -0
  74. data/doc/classes/DNSTraverse/Referral.src/M000083.html +28 -0
  75. data/doc/classes/DNSTraverse/Referral.src/M000084.html +29 -0
  76. data/doc/classes/DNSTraverse/Referral.src/M000085.html +55 -0
  77. data/doc/classes/DNSTraverse/Referral.src/M000086.html +30 -0
  78. data/doc/classes/DNSTraverse/Referral.src/M000087.html +24 -0
  79. data/doc/classes/DNSTraverse/Referral.src/M000088.html +20 -0
  80. data/doc/classes/DNSTraverse/Referral.src/M000089.html +58 -0
  81. data/doc/classes/DNSTraverse/ResolveError.html +111 -0
  82. data/doc/classes/DNSTraverse/Response.html +271 -0
  83. data/doc/classes/DNSTraverse/Response.src/M000057.html +27 -0
  84. data/doc/classes/DNSTraverse/Response.src/M000058.html +21 -0
  85. data/doc/classes/DNSTraverse/Response.src/M000059.html +19 -0
  86. data/doc/classes/DNSTraverse/Response.src/M000060.html +21 -0
  87. data/doc/classes/DNSTraverse/Response.src/M000061.html +31 -0
  88. data/doc/classes/DNSTraverse/Response.src/M000062.html +23 -0
  89. data/doc/classes/DNSTraverse/Response/NoGlue.html +222 -0
  90. data/doc/classes/DNSTraverse/Response/NoGlue.src/M000007.html +32 -0
  91. data/doc/classes/DNSTraverse/Response/NoGlue.src/M000008.html +18 -0
  92. data/doc/classes/DNSTraverse/Response/NoGlue.src/M000009.html +18 -0
  93. data/doc/classes/DNSTraverse/Response/NoGlue.src/M000010.html +18 -0
  94. data/doc/classes/DNSTraverse/Traverser.html +247 -0
  95. data/doc/classes/DNSTraverse/Traverser.src/M000040.html +17 -0
  96. data/doc/classes/DNSTraverse/Traverser.src/M000041.html +52 -0
  97. data/doc/classes/DNSTraverse/Traverser.src/M000042.html +61 -0
  98. data/doc/classes/DNSTraverse/Traverser.src/M000043.html +110 -0
  99. data/doc/classes/DNSTraverse/Traverser.src/M000044.html +63 -0
  100. data/doc/classes/DNSTraverse/Traverser.src/M000045.html +28 -0
  101. data/doc/classes/DNSTraverse/Traverser.src/M000046.html +18 -0
  102. data/doc/classes/FingerprintRules.html +175 -0
  103. data/doc/classes/Log.html +174 -0
  104. data/doc/classes/Log.src/M000002.html +18 -0
  105. data/doc/classes/Log.src/M000003.html +18 -0
  106. data/doc/classes/Log.src/M000004.html +18 -0
  107. data/doc/classes/Log/Formatter.html +165 -0
  108. data/doc/classes/Log/Formatter.src/M000005.html +22 -0
  109. data/doc/classes/Log/Formatter.src/M000006.html +24 -0
  110. data/doc/classes/TestFingerprint.html +137 -0
  111. data/doc/classes/TestFingerprint.src/M000001.html +22 -0
  112. data/doc/created.rid +1 -0
  113. data/doc/files/lib/dnstraverse/caching_resolver_rb.html +132 -0
  114. data/doc/files/lib/dnstraverse/decoded_query_cache_rb.html +132 -0
  115. data/doc/files/lib/dnstraverse/decoded_query_rb.html +132 -0
  116. data/doc/files/lib/dnstraverse/fingerprint_rb.html +134 -0
  117. data/doc/files/lib/dnstraverse/fingerprint_rules_rb.html +143 -0
  118. data/doc/files/lib/dnstraverse/info_cache_rb.html +131 -0
  119. data/doc/files/lib/dnstraverse/log_rb.html +131 -0
  120. data/doc/files/lib/dnstraverse/message_utility_rb.html +124 -0
  121. data/doc/files/lib/dnstraverse/referral_rb.html +134 -0
  122. data/doc/files/lib/dnstraverse/response_noglue_rb.html +124 -0
  123. data/doc/files/lib/dnstraverse/response_rb.html +132 -0
  124. data/doc/files/lib/dnstraverse/traverser_rb.html +137 -0
  125. data/doc/files/test/test_fingerprint_rb.html +133 -0
  126. data/doc/fr_class_index.html +43 -0
  127. data/doc/fr_file_index.html +39 -0
  128. data/doc/fr_method_index.html +115 -0
  129. data/doc/index.html +24 -0
  130. data/doc/rdoc-style.css +208 -0
  131. data/lib/dnstraverse/caching_resolver.rb +63 -0
  132. data/lib/dnstraverse/decoded_query.rb +199 -0
  133. data/lib/dnstraverse/decoded_query_cache.rb +53 -0
  134. data/lib/dnstraverse/fingerprint.rb +166 -0
  135. data/lib/dnstraverse/fingerprint_rules.rb +389 -0
  136. data/lib/dnstraverse/info_cache.rb +108 -0
  137. data/lib/dnstraverse/log.rb +55 -0
  138. data/lib/dnstraverse/message_utility.rb +199 -0
  139. data/lib/dnstraverse/referral.rb +463 -0
  140. data/lib/dnstraverse/response.rb +92 -0
  141. data/lib/dnstraverse/response_noglue.rb +54 -0
  142. data/lib/dnstraverse/traverser.rb +291 -0
  143. data/test/test_fingerprint.rb +29 -0
  144. 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