dnstraverse 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dnstraverse +11 -2
- data/lib/dnstraverse/decoded_query.rb +3 -2
- data/lib/dnstraverse/referral.rb +45 -25
- data/lib/dnstraverse/response.rb +3 -1
- data/lib/dnstraverse/response_loop.rb +54 -0
- data/lib/dnstraverse/traverser.rb +31 -34
- data/lib/dnstraverse/version.rb +1 -1
- metadata +5 -4
data/bin/dnstraverse
CHANGED
@@ -113,7 +113,7 @@ options[:domainname] = nil
|
|
113
113
|
options[:follow_aaaa] = false
|
114
114
|
options[:root_aaaa] = false
|
115
115
|
options[:always_tcp] = false
|
116
|
-
options[:allow_tcp] =
|
116
|
+
options[:allow_tcp] = true
|
117
117
|
options[:allstats] = false
|
118
118
|
options[:saveobjects] = false
|
119
119
|
options[:fast] = true
|
@@ -121,6 +121,7 @@ options[:udpsize] = 2048
|
|
121
121
|
options[:maxdepth] = 24
|
122
122
|
options[:retries] = 2
|
123
123
|
options[:results] = true
|
124
|
+
options[:quiet] = false
|
124
125
|
|
125
126
|
opts = OptionParser.new
|
126
127
|
opts.banner = "Usage: #{File.basename($0)} [options] DOMAIN"
|
@@ -146,6 +147,7 @@ opts.on("--[no-]show-results", "Display the results (default true)") { |o| optio
|
|
146
147
|
opts.on("--save-objects") { |o| options[:saveobjects] = o }
|
147
148
|
opts.on("--[no-]fast", "Fast mode (default true) turn off to be more accurate" ) { |o| options[:fast] = o }
|
148
149
|
opts.on_tail("-h", "--help", "Show full help") { puts opts; exit }
|
150
|
+
opts.on_tail("-q", "--quiet", "Supress supplementary information") { options[:quiet] = true }
|
149
151
|
opts.on_tail("-V", "--version", "Show version") { puts DNSTraverse::Version::STRING; exit }
|
150
152
|
begin
|
151
153
|
opts.parse!
|
@@ -169,8 +171,15 @@ args[:loglevel] = options[:debug] >= 1 ? Logger::DEBUG : Logger::UNKNOWN
|
|
169
171
|
args[:libloglevel] = options[:debug] >= 2 ? Logger::DEBUG : Logger::UNKNOWN
|
170
172
|
args[:always_tcp] = true if options[:always_tcp]
|
171
173
|
args[:allow_tcp] = true if options[:allow_tcp]
|
172
|
-
args[:fast] =
|
174
|
+
args[:fast] = options[:fast]
|
173
175
|
begin
|
176
|
+
unless options[:quiet] then
|
177
|
+
puts "# Using fast mode" if options[:fast]
|
178
|
+
puts "# Limiting traverse to one root" unless options[:allroots]
|
179
|
+
puts "# UDP size #{options[:udpsize]} (EDNS0 is #{options[:udpsize == 512] ? "off" : "on"})"
|
180
|
+
puts "# Retries #{options[:retries]}, max depth #{options[:maxdepth]}"
|
181
|
+
puts "# Allow TCP is #{options[:allow_tcp]}, always TCP is #{options[:always_tcp]}"
|
182
|
+
end
|
174
183
|
traverser = DNSTraverse::Traverser.new(args)
|
175
184
|
if options[:root] then
|
176
185
|
root = options[:root]
|
@@ -51,6 +51,7 @@ module DNSTraverse
|
|
51
51
|
@warnings = nil # Warnings if there are any (array)
|
52
52
|
query unless @message
|
53
53
|
process
|
54
|
+
Log.debug { "Query to #{@ip} for #{@qname}/#{@qclass}/#{@qtype} decoded status=#{@status}" }
|
54
55
|
return self
|
55
56
|
end
|
56
57
|
|
@@ -155,9 +156,9 @@ module DNSTraverse
|
|
155
156
|
@error_message = "Server failure (SERVFAIL)"
|
156
157
|
when Dnsruby::RCode::NXDOMAIN
|
157
158
|
@error_message = "No such domain (NXDOMAIN)"
|
158
|
-
when NOTIMP
|
159
|
+
when Dnsruby::RCode.NOTIMP
|
159
160
|
@error_message = "Not implemented (NOTIMP)"
|
160
|
-
when REFUSED
|
161
|
+
when Dnsruby::RCode.REFUSED
|
161
162
|
@error_message = "Refused"
|
162
163
|
else
|
163
164
|
@error_message = @message.rcode.to_s
|
data/lib/dnstraverse/referral.rb
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
|
16
16
|
require 'dnstraverse/response'
|
17
17
|
require 'dnstraverse/response_noglue'
|
18
|
+
require 'dnstraverse/response_loop'
|
18
19
|
require 'dnstraverse/info_cache'
|
19
20
|
require 'dnstraverse/decoded_query_cache'
|
20
21
|
require 'dnstraverse/summary_stats'
|
@@ -164,6 +165,26 @@ module DNSTraverse
|
|
164
165
|
return true
|
165
166
|
end
|
166
167
|
|
168
|
+
# look out for endless loops
|
169
|
+
# e.g. while looking for a.b we get b NS c.d
|
170
|
+
# and while looking for c.d we get d NS a.b
|
171
|
+
# which would take us back to b NS c.d
|
172
|
+
def loop?
|
173
|
+
return false if @serverips
|
174
|
+
parent = @parent
|
175
|
+
until parent.nil? do
|
176
|
+
if parent.qname.to_s == @qname.to_s and
|
177
|
+
parent.qclass.to_s == @qclass.to_s and
|
178
|
+
parent.qtype.to_s == @qtype.to_s and
|
179
|
+
parent.server == @server and
|
180
|
+
parent.serverips.nil?
|
181
|
+
return true
|
182
|
+
end
|
183
|
+
parent = parent.parent
|
184
|
+
end
|
185
|
+
return false
|
186
|
+
end
|
187
|
+
|
167
188
|
# resolve server to serverips, return list of Referral objects to process
|
168
189
|
def resolve(*args)
|
169
190
|
raise "This Referral object has already been resolved" if resolved?
|
@@ -173,6 +194,11 @@ module DNSTraverse
|
|
173
194
|
" of #{bailiwick} - no glue record provided" }
|
174
195
|
return Array.new
|
175
196
|
end
|
197
|
+
if loop? then
|
198
|
+
# b IN NS c.d, d IN NS a.b
|
199
|
+
Log.debug { "Loop reached at server #{server}" }
|
200
|
+
return Array.new
|
201
|
+
end
|
176
202
|
child_refid = 1
|
177
203
|
starters, newbailiwick = @infocache.get_startservers(@server)
|
178
204
|
Log.debug { "Resolving #{@server} type #{@nsatype} " }
|
@@ -205,6 +231,15 @@ module DNSTraverse
|
|
205
231
|
:bailiwick => @bailiwick)
|
206
232
|
@stats_resolve[r.stats_key] = { :prob => 1.0, :response => r,
|
207
233
|
:referral => self }
|
234
|
+
elsif loop? then # endless loop, e.g. b. NS c.d, d NS a.b
|
235
|
+
r = DNSTraverse::Response::Loop.new(:qname => @qname,
|
236
|
+
:qclass => @qclass,
|
237
|
+
:qtype => @qtype,
|
238
|
+
:server => @server,
|
239
|
+
:ip => @parent_ip,
|
240
|
+
:bailiwick => @bailiwick)
|
241
|
+
@stats_resolve[r.stats_key] = { :prob => 1.0, :response => r,
|
242
|
+
:referral => self }
|
208
243
|
else
|
209
244
|
# normal resolve - combine children's statistics in to @stats_resolve
|
210
245
|
stats_calculate_children(@stats_resolve, @resolves, 1.0)
|
@@ -339,31 +374,8 @@ module DNSTraverse
|
|
339
374
|
end
|
340
375
|
end
|
341
376
|
|
342
|
-
def check_loop?(args) # :ip, :qtype, :qname, :qclass
|
343
|
-
parent = @parent
|
344
|
-
until parent.nil? do
|
345
|
-
if parent.qname.to_s == args[:qname].to_s and
|
346
|
-
parent.qclass.to_s == args[:qclass].to_s and
|
347
|
-
parent.qtype.to_s == args[:qtype].to_s and
|
348
|
-
parent.serverips and parent.serverips.include?(args[:ip]) then
|
349
|
-
exit 1 # XXX fix me
|
350
|
-
return RuntimeError.new("Loop detected")
|
351
|
-
end
|
352
|
-
parent = parent.parent
|
353
|
-
end
|
354
|
-
return nil
|
355
|
-
end
|
356
|
-
|
357
377
|
def process_normal(args)
|
358
378
|
Log.debug { "process " + self.to_s }
|
359
|
-
# if l = check_loop?(:ip => ip, :qname => @qname,
|
360
|
-
# :qtype => @qtype, :qclass => @qclass) then
|
361
|
-
# for ip in @serverips do
|
362
|
-
# @responses[ip] = l
|
363
|
-
# done
|
364
|
-
# return
|
365
|
-
# end
|
366
|
-
# end
|
367
379
|
for ip in @serverips do
|
368
380
|
Log.debug { "Process normal #{ip}" }
|
369
381
|
next if ip =~ /^key:/ # resolve failed on something
|
@@ -376,6 +388,7 @@ module DNSTraverse
|
|
376
388
|
:qclass => @qclass, :qtype => @qtype,
|
377
389
|
:bailiwick => @bailiwick,
|
378
390
|
:infocache => @infocache, :ip => ip,
|
391
|
+
:server => @server,
|
379
392
|
:decoded_query_cache => @decoded_query_cache)
|
380
393
|
Log.debug { "Process normal #{ip} - done making response" }
|
381
394
|
@responses[ip] = r
|
@@ -404,7 +417,7 @@ module DNSTraverse
|
|
404
417
|
:server => starter[:name],
|
405
418
|
:serverips => starter[:ips],
|
406
419
|
:refid => "#{@refid}.#{child_refid}",
|
407
|
-
|
420
|
+
:refkey => "#{@refkey}.#{starters.count}"
|
408
421
|
}.merge(args)
|
409
422
|
children.push make_referral(refargs)
|
410
423
|
child_refid+= 1
|
@@ -427,6 +440,9 @@ module DNSTraverse
|
|
427
440
|
@children.each_key do | ip |
|
428
441
|
@children[ip].map! { |c| c.equal?(before) ? after : c }
|
429
442
|
end
|
443
|
+
if @resolves then
|
444
|
+
@resolves.map! { |c| c.equal?(before) ? after : c }
|
445
|
+
end
|
430
446
|
end
|
431
447
|
|
432
448
|
def stats_display(args)
|
@@ -448,7 +464,11 @@ module DNSTraverse
|
|
448
464
|
puts "#{response.exception_message} at #{where}"
|
449
465
|
when :noglue
|
450
466
|
puts "No glue at #{referral.parent.server} " +
|
451
|
-
"(#{response.ip}) for #{
|
467
|
+
"(#{response.ip}) for #{referral.server}"
|
468
|
+
when :referral_lame
|
469
|
+
puts "Lame referral from #{where}"
|
470
|
+
when :loop
|
471
|
+
puts "Loop encountered at #{response.server} "
|
452
472
|
when :error
|
453
473
|
puts "#{response.error_message} at #{where}"
|
454
474
|
when :nodata
|
data/lib/dnstraverse/response.rb
CHANGED
@@ -26,6 +26,7 @@ module DNSTraverse
|
|
26
26
|
attr_reader :status # our status, expanding on DecodedQuery status
|
27
27
|
attr_reader :starters, :starters_bailiwick # :referral/:restart only
|
28
28
|
attr_reader :stats_key
|
29
|
+
attr_reader :server
|
29
30
|
|
30
31
|
# :qname, :qclass, :qtype, :ip, :bailiwick, optional :message
|
31
32
|
def initialize(args)
|
@@ -33,6 +34,7 @@ module DNSTraverse
|
|
33
34
|
:qtype => args[:qtype], :ip => args[:ip],
|
34
35
|
:bailiwick => args[:bailiwick], :message => args[:message] }
|
35
36
|
@decoded_query = args[:decoded_query_cache].query(dqc_args)
|
37
|
+
@server = args[:server]
|
36
38
|
@infocache = InfoCache.new(args[:infocache]) # our infocache
|
37
39
|
@starters = nil # initial servers for :referral/:restart
|
38
40
|
@starters_bailiwick = nil # for initial servers for :referral/:restart
|
@@ -50,7 +52,7 @@ module DNSTraverse
|
|
50
52
|
|
51
53
|
def update_stats_key
|
52
54
|
r = @decoded_query
|
53
|
-
@stats_key = "key:#{r.ip}:#{@status}:#{r.qname}:#{r.qclass}:#{r.qtype}"
|
55
|
+
@stats_key = "key:#{r.ip}:#{@server}:#{@status}:#{r.qname}:#{r.qclass}:#{r.qtype}"
|
54
56
|
end
|
55
57
|
|
56
58
|
# clean up the workings
|
@@ -0,0 +1,54 @@
|
|
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
|
+
|
18
|
+
class Response::Loop < Response
|
19
|
+
|
20
|
+
attr_reader :qname, :qclass, :qtype, :ip, :bailiwick, :server
|
21
|
+
|
22
|
+
def initialize(args)
|
23
|
+
# we queried @ip about @qname/@qclass/@qtype and received @server as a
|
24
|
+
# referral in bailiwick @bailiwick but without any glue
|
25
|
+
@qname = args[:qname]
|
26
|
+
@qclass = args[:qclass]
|
27
|
+
@qtype = args[:qtype]
|
28
|
+
@bailiwick = args[:bailiwick]
|
29
|
+
@ip = args[:ip]
|
30
|
+
@server = args[:server]
|
31
|
+
@decoded_query = nil
|
32
|
+
@infocache = nil
|
33
|
+
@starters = nil
|
34
|
+
@starters_bailiwick = nil
|
35
|
+
@status = :loop
|
36
|
+
update_stats_key
|
37
|
+
return self
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(key, *args, &block)
|
41
|
+
super # there are no missing methods, we answer directly
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_stats_key
|
45
|
+
@stats_key = "key:#{@ip}:#{@status}:#{@qname}:#{@qclass}:#{@qtype}:#{@server}:#{@bailiwick}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
return "Loop encountered resolving #{@server}"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -158,8 +158,7 @@ module DNSTraverse
|
|
158
158
|
r.cleanup(cleanup)
|
159
159
|
if @fast then
|
160
160
|
# store away in @answered hash so we can lookup later
|
161
|
-
key = "#{r.qname}:#{r.qclass}:#{r.qtype}:#{r.server}:#{r.txt_ips_verbose}"
|
162
|
-
key.downcase!
|
161
|
+
key = "#{r.qname}:#{r.qclass}:#{r.qtype}:#{r.server}:#{r.txt_ips_verbose}".downcase!
|
163
162
|
Log.debug { "Fast mode cache store: #{key}" }
|
164
163
|
@answered[key] = r
|
165
164
|
end
|
@@ -169,48 +168,46 @@ module DNSTraverse
|
|
169
168
|
@seen[r.server.downcase].uniq!
|
170
169
|
end
|
171
170
|
next
|
172
|
-
else
|
173
|
-
report_progress r, :stage => :start
|
174
171
|
end
|
172
|
+
# ok time to process a new item
|
173
|
+
if @fast then
|
174
|
+
Log.debug { "Checking #{r} for already completed earlier" }
|
175
|
+
key = "#{r.qname}:#{r.qclass}:#{r.qtype}:#{r.server}:#{r.txt_ips_verbose}".downcase!
|
176
|
+
Log.debug { "Fast mode cache lookup: #{key}" }
|
177
|
+
# check for previously stored answer
|
178
|
+
# special case noglue situation, don't use previous answer
|
179
|
+
# because attributes are complicated for stats collection and
|
180
|
+
# we don't want to merge them together - creating the noglue
|
181
|
+
# response object is fast anyway
|
182
|
+
if @answered.key?(key) and (not r.noglue?) then
|
183
|
+
Log.debug { "Fast method - completed #{r}" }
|
184
|
+
r.parent.replace_child(r, @answered[key])
|
185
|
+
report_progress r, :stage => :answer_fast
|
186
|
+
next
|
187
|
+
end
|
188
|
+
end
|
189
|
+
report_progress r, :stage => :start
|
175
190
|
unless r.resolved? then
|
176
191
|
# get resolve Referral objects, place on stack with placeholder
|
177
192
|
stack << r << :calc_resolve
|
178
193
|
referrals = r.resolve({})
|
179
194
|
referrals.each { |c| report_progress c, :stage => :new }
|
180
|
-
stack.push(*referrals.reverse)
|
195
|
+
stack.push(*referrals.reverse)
|
181
196
|
next
|
182
197
|
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
if @fast
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
key.downcase!
|
193
|
-
Log.debug { "Fast mode cache lookup: #{key}" }
|
194
|
-
# check for previously stored answer
|
195
|
-
# special case noglue situation, don't use previous answer
|
196
|
-
# because attributes are complicated for stats collection and
|
197
|
-
# we don't want to merge them together - creating the noglue
|
198
|
-
# response object is fast anyway
|
199
|
-
if @answered.key?(key) and (not c.noglue?) then
|
200
|
-
Log.debug { "Fast method - completed #{c}" }
|
201
|
-
r.replace_child(c, @answered[key])
|
202
|
-
report_progress c, :stage => :answer_fast
|
203
|
-
else
|
204
|
-
newchildren << c
|
205
|
-
end
|
206
|
-
end
|
207
|
-
children = newchildren
|
198
|
+
# get Referral objects, place on stack with placeholder
|
199
|
+
stack << r << :calc_answer
|
200
|
+
children = r.process({})
|
201
|
+
children.each do |c|
|
202
|
+
if @fast
|
203
|
+
key = "#{c.qname}:#{c.qclass}:#{c.qtype}:#{c.server}:#{c.txt_ips_verbose}".downcase!
|
204
|
+
stage = @answered.key?(key) ? :new_fast : :new
|
205
|
+
else
|
206
|
+
stage = :new
|
208
207
|
end
|
209
|
-
|
210
|
-
stack.push(*children.reverse)
|
211
|
-
next
|
208
|
+
report_progress c, :stage => stage
|
212
209
|
end
|
213
|
-
|
210
|
+
stack.push(*children.reverse)
|
214
211
|
end
|
215
212
|
end
|
216
213
|
|
data/lib/dnstraverse/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dnstraverse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- James Ponder
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-10 00:00:00 +01:00
|
19
19
|
default_executable: dnstraverse
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- lib/dnstraverse/message_utility.rb
|
107
107
|
- lib/dnstraverse/referral.rb
|
108
108
|
- lib/dnstraverse/response.rb
|
109
|
+
- lib/dnstraverse/response_loop.rb
|
109
110
|
- lib/dnstraverse/response_noglue.rb
|
110
111
|
- lib/dnstraverse/summary_stats.rb
|
111
112
|
- lib/dnstraverse/traverser.rb
|