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 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] = false
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] = true if options[: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
@@ -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
- :refkey => "#{@refkey}.#{starters.count}"
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 #{response.server}"
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
@@ -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) # XXX
195
+ stack.push(*referrals.reverse)
181
196
  next
182
197
  end
183
- unless r.processed? then
184
- # get Referral objects, place on stack with placeholder
185
- stack << r << :calc_answer
186
- children = r.process({})
187
- if @fast then
188
- Log.debug { "Checking #{r} for already completed children" }
189
- newchildren = []
190
- for c in children do
191
- key = "#{c.qname}:#{c.qclass}:#{c.qtype}:#{c.server}:#{c.txt_ips_verbose}"
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
- children.each { |c| report_progress c, :stage => :new }
210
- stack.push(*children.reverse)
211
- next
208
+ report_progress c, :stage => stage
212
209
  end
213
- raise "Fatal stack error at #{r} - size still #{stack.size}"
210
+ stack.push(*children.reverse)
214
211
  end
215
212
  end
216
213
 
@@ -2,7 +2,7 @@ module DNSTraverse
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- PATCH = 0
5
+ PATCH = 1
6
6
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
  end
8
8
  end
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
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-03 00:00:00 +01:00
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