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.
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