dnstraverse 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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