Class Dnsruby::Recursor
In: lib/Dnsruby/Recursor.rb
Parent: Object
ResolvError EncodeError OtherResolvError ServFail FormErr DecodeError NXRRSet YXDomain NotImp NXDomain VerifyError NotAuth YXRRSet NotZone Refused TsigError Message Update CodeMapper Types MetaTypes QTypes Nsec3HashAlgorithms Algorithms OpCode Classes ExtendedRCode RCode Modes Comparable Name RRSet Resolver SingleResolver StandardError TimeoutError ResolvTimeout DNS Dnssec Hosts SelectThread\n[lib/Dnsruby/select_thread.rb\nlib/Dnsruby/select_thread.rb.michael.rb] Recursor IPv6 IPv4 ZoneTransfer MessageDecoder MessageEncoder Question Header TheLog RR\n[lib/Dnsruby/resource/A.rb\nlib/Dnsruby/resource/AAAA.rb\nlib/Dnsruby/resource/AFSDB.rb\nlib/Dnsruby/resource/CERT.rb\nlib/Dnsruby/resource/DLV.rb\nlib/Dnsruby/resource/DNSKEY.rb\nlib/Dnsruby/resource/DS.rb\nlib/Dnsruby/resource/HINFO.rb\nlib/Dnsruby/resource/IN.rb\nlib/Dnsruby/resource/ISDN.rb\nlib/Dnsruby/resource/LOC.rb\nlib/Dnsruby/resource/MINFO.rb\nlib/Dnsruby/resource/MX.rb\nlib/Dnsruby/resource/NAPTR.rb\nlib/Dnsruby/resource/NSAP.rb\nlib/Dnsruby/resource/NSEC.rb\nlib/Dnsruby/resource/NSEC3.rb\nlib/Dnsruby/resource/NSEC3PARAM.rb\nlib/Dnsruby/resource/OPT.rb\nlib/Dnsruby/resource/PX.rb\nlib/Dnsruby/resource/RP.rb\nlib/Dnsruby/resource/RRSIG.rb\nlib/Dnsruby/resource/RT.rb\nlib/Dnsruby/resource/SOA.rb\nlib/Dnsruby/resource/SPF.rb\nlib/Dnsruby/resource/SRV.rb\nlib/Dnsruby/resource/TKEY.rb\nlib/Dnsruby/resource/TSIG.rb\nlib/Dnsruby/resource/TXT.rb\nlib/Dnsruby/resource/X25.rb\nlib/Dnsruby/resource/domain_name.rb\nlib/Dnsruby/resource/generic.rb\nlib/Dnsruby/resource/resource.rb] ValidatorThread PacketSender ResolverRuby Config KeyCache Cache SingleVerifier Resolv Iana lib/Dnsruby/DNS.rb lib/Dnsruby/dnssec.rb lib/Dnsruby/Hosts.rb lib/Dnsruby/select_thread.rb.michael.rb lib/Dnsruby/Recursor.rb lib/Dnsruby/update.rb lib/Dnsruby/ipv6.rb lib/Dnsruby/ipv4.rb lib/Dnsruby/code_mapper.rb lib/Dnsruby/zone_transfer.rb lib/Dnsruby/message.rb lib/Dnsruby/TheLog.rb lib/Dnsruby/resource/resource.rb lib/Dnsruby/validator_thread.rb lib/Dnsruby/PacketSender.rb lib/Dnsruby/Resolver.rb lib/Dnsruby/Config.rb lib/Dnsruby/key_cache.rb lib/Dnsruby/Cache.rb lib/Dnsruby/single_verifier.rb lib/Dnsruby/SingleResolver.rb lib/Dnsruby/name.rb lib/dnsruby.rb lib/Dnsruby/resource/TKEY.rb lib/Dnsruby/iana_ports.rb Dnsruby dot/m_56_0.png

Dnsruby::Recursor - Perform recursive dns lookups

  require 'Dnsruby'
  rec = Dnsruby::Recursor.new()
  answer = rec.recurse("rob.com.au")

This module uses a Dnsruby::Resolver to perform recursive queries.

AUTHOR

Rob Brown, bbb@cpan.org Alex Dalitz, alexd@nominet.org.uk

SEE ALSO

Dnsruby::Resolver,

COPYRIGHT

Copyright (c) 2002, Rob Brown. All rights reserved. Portions Copyright (c) 2005, Olaf M Kolkman. Ruby version with caching and validation Copyright (c) 2008, AlexD (Nominet UK)

Example lookup process:

[root@box root]# dig +trace www.rob.com.au.

; <<>> DiG 9.2.0 <<>> +trace www.rob.com.au. ;; global options: printcmd . 507343 IN NS C.ROOT-SERVERS.NET. . 507343 IN NS D.ROOT-SERVERS.NET. . 507343 IN NS E.ROOT-SERVERS.NET. . 507343 IN NS F.ROOT-SERVERS.NET. . 507343 IN NS G.ROOT-SERVERS.NET. . 507343 IN NS H.ROOT-SERVERS.NET. . 507343 IN NS I.ROOT-SERVERS.NET. . 507343 IN NS J.ROOT-SERVERS.NET. . 507343 IN NS K.ROOT-SERVERS.NET. . 507343 IN NS L.ROOT-SERVERS.NET. . 507343 IN NS M.ROOT-SERVERS.NET. . 507343 IN NS A.ROOT-SERVERS.NET. . 507343 IN NS B.ROOT-SERVERS.NET. ;; Received 436 bytes from 127.0.0.1#53(127.0.0.1) in 9 ms

  ;;; But these should be hard coded as the hints

  ;;; Ask H.ROOT-SERVERS.NET gave:

au. 172800 IN NS NS2.BERKELEY.EDU. au. 172800 IN NS NS1.BERKELEY.EDU. au. 172800 IN NS NS.UU.NET. au. 172800 IN NS BOX2.AUNIC.NET. au. 172800 IN NS SEC1.APNIC.NET. au. 172800 IN NS SEC3.APNIC.NET. ;; Received 300 bytes from 128.63.2.53#53(H.ROOT-SERVERS.NET) in 322 ms

  ;;; A little closer than before

  ;;; Ask NS2.BERKELEY.EDU gave:

com.au. 259200 IN NS ns4.ausregistry.net. com.au. 259200 IN NS dns1.telstra.net. com.au. 259200 IN NS au2ld.CSIRO.au. com.au. 259200 IN NS audns01.syd.optus.net. com.au. 259200 IN NS ns.ripe.net. com.au. 259200 IN NS ns1.ausregistry.net. com.au. 259200 IN NS ns2.ausregistry.net. com.au. 259200 IN NS ns3.ausregistry.net. com.au. 259200 IN NS ns3.melbourneit.com. ;; Received 387 bytes from 128.32.206.12#53(NS2.BERKELEY.EDU) in 10312 ms

  ;;; A little closer than before

  ;;; Ask ns4.ausregistry.net gave:

com.au. 259200 IN NS ns1.ausregistry.net. com.au. 259200 IN NS ns2.ausregistry.net. com.au. 259200 IN NS ns3.ausregistry.net. com.au. 259200 IN NS ns4.ausregistry.net. com.au. 259200 IN NS ns3.melbourneit.com. com.au. 259200 IN NS dns1.telstra.net. com.au. 259200 IN NS au2ld.CSIRO.au. com.au. 259200 IN NS ns.ripe.net. com.au. 259200 IN NS audns01.syd.optus.net. ;; Received 259 bytes from 137.39.1.3#53(ns4.ausregistry.net) in 606 ms

  ;;; Uh... yeah... I already knew this
  ;;; from what NS2.BERKELEY.EDU told me.
  ;;; ns4.ausregistry.net must have brain damage

  ;;; Ask ns1.ausregistry.net gave:

rob.com.au. 86400 IN NS sy-dns02.tmns.net.au. rob.com.au. 86400 IN NS sy-dns01.tmns.net.au. ;; Received 87 bytes from 203.18.56.41#53(ns1.ausregistry.net) in 372 ms

  ;;; Ah, much better.  Something more useful.

  ;;; Ask sy-dns02.tmns.net.au gave:

www.rob.com.au. 7200 IN A 139.134.5.123 rob.com.au. 7200 IN NS sy-dns01.tmns.net.au. rob.com.au. 7200 IN NS sy-dns02.tmns.net.au. ;; Received 135 bytes from 139.134.2.18#53(sy-dns02.tmns.net.au) in 525 ms

  ;;; FINALLY, THE ANSWER!
 Now,DNSSEC validation is performed (unless disabled).

Methods

Attributes

callback  [RW] 
hints  [R] 
ipv6_ok  [RW] 
nameservers  [RW] 
recurse  [RW] 
resolver  [RW]  The resolver to use for the queries

Public Class methods

[Source]

     # File lib/Dnsruby/Recursor.rb, line 291
291:     def Recursor.clear_caches(resolver = Resolver.new)
292:           Recursor.set_hints(Hash.new, resolver)
293:           @@zones_cache = Hash.new # key zone_name, values Hash of servers and AddressCaches
294:           @@zones_cache["."] = @@hints
295:     end

[Source]

     # File lib/Dnsruby/Recursor.rb, line 166
166:     def initialize(res = Resolver.new)
167:       @resolver = res
168:       @ipv6_ok = false
169:     end

[Source]

     # File lib/Dnsruby/Recursor.rb, line 185
185:     def Recursor.set_hints(hints, resolver)
186:       TheLog.debug(";; hints(#{hints.inspect})\n")
187:       if (!hints && @@nameservers)
188:         @@hints=(@@nameservers)
189:       else
190:         @@nameservers=(hints)
191:       end
192:       TheLog.debug(";; verifying (root) zone...\n")
193:       # bind always asks one of the hint servers
194:       # for who it thinks is authoritative for
195:       # the (root) zone as a sanity check.
196:       # Nice idea.
197:           
198:       resolver.recurse=(1)
199:       packet=resolver.query(".", "NS", "IN")
200:       hints = Hash.new
201:       if (packet)
202:         if (ans = packet.answer)
203:           ans.each do |rr|
204:             if (rr.name.to_s =~ /^\.?$/ and
205:                   rr.type == Types::NS)
206:               # Found root authority
207:               server = rr.nsdname.to_s.downcase
208:               server.sub!(/\.$/,"")
209:               TheLog.debug(";; FOUND HINT: #{server}\n")
210:               hints[server] = AddressCache.new
211:             end
212:           end
213:           packet.additional.each do |rr|
214:             TheLog.debug(";; ADDITIONAL: "+rr.inspect+"\n")
215:             server = rr.name.to_s.downcase
216:             server.sub!(/\.$/,"")
217:             if (server)
218:               if ( rr.type == Types::A)
219:                 #print ";; ADDITIONAL HELP: $server -> [".$rr->rdatastr."]\n" if $self->{'debug'};
220:                 if (hints[server]!=nil)
221:                   TheLog.debug(";; STORING IP: #{server} IN A "+rr.address.to_s+"\n")
222:                   hints[server].push([rr.address.to_s, rr.ttl])
223:                 end
224:               end
225:               if ( rr.type == Types::AAAA)
226:                 #print ";; ADDITIONAL HELP: $server -> [".$rr->rdatastr."]\n" if $self->{'debug'};
227:                 if (hints[server])
228:                   TheLog.debug(";; STORING IP6: #{server} IN AAAA "+rr.address.to_s+"\n")
229:                   hints[server].push([rr.address.to_s, rr.ttl])
230:                 end
231:               end
232:                   
233:             end 
234:           end
235:         end
236:         #                      foreach my $server (keys %hints) {
237:         hints.keys.each do |server|
238:           if (!hints[server] || hints[server].length == 0)
239:             # Wipe the servers without lookups
240:             hints.delete(server)
241:           end
242:         end
243:         @@hints = hints
244:       else
245:         @@hints = {}
246:       end
247:       if (@@hints.size > 0)
248:         if (@debug)
249:           TheLog.info(";; USING THE FOLLOWING HINT IPS:\n")
250:           @@hints.values.each do |ips|
251:             ips.each do |server|
252:               TheLog.info(";;  #{server}\n")
253:             end
254:           end
255:         end
256:       else
257:         warn "Server ["+(@@nameservers)[0].to_s+"] did not give answers"
258:       end
259:           
260:       # Disable recursion flag.
261:       resolver.recurse=(0)
262:           
263:       #  return $self->nameservers( map { @{ $_ } } values %{ $self->{'hints'} } );
264:       @@nameservers = @@hints.values
265:       return @@nameservers
266:     end

Public Instance methods

Initialize the hint servers. Recursive queries need a starting name server to work off of. This method takes a list of IP addresses to use as the starting servers. These name servers should be authoritative for the root (.) zone.

  res.hints=(ips)

If no hints are passed, the default nameserver is asked for the hints. Normally these IPs can be obtained from the following location:

  ftp://ftp.internic.net/domain/named.root

[Source]

     # File lib/Dnsruby/Recursor.rb, line 182
182:     def hints=(hints)
183:       Recursor.set_hints(hints, @resolver)
184:     end

[Source]

     # File lib/Dnsruby/Recursor.rb, line 638
638:     def prune_rrsets_to_rfc5452(packet, zone)
639:       # Now prune the response of any unrelated rrsets (RFC5452 section6)
640:       # "One very simple way to achieve this is to only accept data if it is
641:       # part of the domain for which the query was intended."
642:       if (!packet.header.aa)
643:         return
644:       end
645:       if (!packet.question()[0])
646:         return
647:       end
648: 
649:       section_rrsets = packet.section_rrsets
650:       section_rrsets.keys.each {|section|
651:         section_rrsets[section].each {|rrset|
652:           n = Name.create(rrset.name)
653:           n.absolute = true
654:           if ((n.to_s == zone) || (n.to_s == Name.create(zone).to_s) ||
655:                 (n.subdomain_of?(Name.create(zone))) ||
656:                 (rrset.type == Types::OPT))
657: #            # @TODO@ Leave in the response if it is an SOA, NSEC or RRSIGfor the parent zone
658: ##          elsif ((query_name.subdomain_of?rrset.name) &&
659: #          elsif  ((rrset.type == Types.SOA) || (rrset.type == Types.NSEC) || (rrset.type == Types.NSEC3)) #)
660:           else
661:             TheLog.debug"Removing #{rrset.name}, #{rrset.type} from response from server for #{zone}"
662:             packet.send(section).remove_rrset(rrset.name, rrset.type)
663:           end
664:         }
665:       }
666:     end

This method is much like the normal query() method except it disables the recurse flag in the packet and explicitly performs the recursion.

  packet = res.query_dorecursion( "www.netscape.com.", "A")
  packet = res.query_dorecursion( "www.netscape.com.", "A", "IN", true) # no validation

The Recursor maintains a cache of known nameservers. DNSSEC validation is performed unless true is passed as the fourth parameter.

[Source]

     # File lib/Dnsruby/Recursor.rb, line 309
309:     def query(name, type=Types.A, klass=Classes.IN, no_validation = false)
310:       # @TODO@ PROVIDE AN ASYNCHRONOUS SEND WHICH RETURNS MESSAGE WITH ERROR!!!
311:           
312:       # Make sure the hint servers are initialized.
313:       @@mutex.synchronize {
314:         self.hints=(Hash.new) unless @@hints
315:       }
316:       @resolver.recurse=(0)
317:       # Make sure the authority cache is clean.
318:       # It is only used to store A and AAAA records of
319:       # the suposedly authoritative name servers.
320:       # TTLs are respected
321:       @@mutex.synchronize {
322:         if (!@@zones_cache)
323:           Recursor.clear_caches(@resolver)
324:         end
325:       }
326: 
327:       # So we have normal hashes, but the array of addresses at the end is now an AddressCache
328:       # which respects the ttls of the A/AAAA records
329: 
330:       # Now see if we already know the zone in question
331:       # Otherwise, see if we know any of its parents (will know at least ".")
332:       known_zone, known_authorities = get_closest_known_zone_authorities_for(name) # ".", @hints if nothing else
333: 
334:       # Seed name servers with the closest known authority
335:       #      ret =  _dorecursion( name, type, klass, ".", @hints, 0)
336:       ret =  _dorecursion( name, type, klass, known_zone, known_authorities, 0, no_validation)
337:       Dnssec.validate(ret) if !no_validation
338:       #      print "\n\nRESPONSE:\n#{ret}\n"
339:       return ret
340:     end

[Source]

     # File lib/Dnsruby/Recursor.rb, line 287
287:     def recursion_callback
288:       return @callback
289:     end

This method takes a code reference, which is then invoked each time a packet is received during the recursive lookup. For example to emulate dig‘s C<+trace> function:

 res.recursion_callback(Proc.new { |packet|
     print packet.additional.inspect

     print";; Received %d bytes from %s\n\n",
         packetanswersize,
         packet.answerfrom);
 })

[Source]

     # File lib/Dnsruby/Recursor.rb, line 281
281:     def recursion_callback=(sub)
282:       #          if (sub && UNIVERSAL::isa(sub, 'CODE'))
283:       @callback = sub
284:       #          end
285:     end

[Validate]