DNS Library Revisited

The current version of the DNS code that ships with the Tcl library has two major problems when it comes:

  • It does not offer any way to dynamically look for the default DNS setup on your machine.
  • It does not offer any cache, which is not a very good idea, even though the DNS server is usually "close" to you.

To palliate both problems, I have written a little "super" package. This has been tested on Windows and with Tcl 8.3 and 8.4. It should work on other architectures, as long as they support the nslookup executable.

Well, enough talking, here goes the code. Enjoy. EF


[PT] 26Feb03: I have sucked this into tcllib as resolv package alongside the dns package. Some improvements to the resolution algorithm - like trying a search list of domain suffixes and ensuring that the dns request has completed using [dns::wait $token].


 ##################
 ## Module Name     --  resolv.tcl
 ## Original Author --  Emmanuel Frecon - [email protected]
 ## Description:
 ##
 ##   A super module on top of the dns module for host name resolution.
 ##   There are two services provided on top of the regular Tcl library:
 ##   Firstly, this module attempts to automatically discover the default
 ##   DNS server that is setup on the machine that it is run on.  This
 ##   server will be used in all further host resolutions.  Secondly, this
 ##   module offers a rudimentary cache.  The cache is rudimentary since it
 ##   has no expiration on host name resolutions, but this is probably
 ##   enough for short lived applications.
 ##
 ##################
 
 package provide resolv 1.0
 
 package require dns
 
 namespace eval ::resolv {
     namespace export resolve init ignore
 
     array set R {
         initdone   0
         dns        ""
         dnsdefault ""
     }
 }
 
 
 
 # Command Name     --  ignore
 # Original Author  --  Emmanuel Frecon - [email protected]
 #
 # Remove a host name resolution from the cache, if present, so that the
 # next resolution will query the DNS server again.
 #
 # Arguments:
 #    hostname        - Name of host to remove from the cache.
 proc ::resolv::ignore { hostname } {
     global ::resolv::Cache
 
     catch "unset ::resolv::Cache($hostname)"
 }
 
 
 # Command Name     --  init
 # Original Author  --  Emmanuel Frecon - [email protected]
 #
 # Initialise this module with a known host name.  This host (not mandatory)
 # will become the default if the library was not able to find a DNS server.
 # This command can be called several times, its effect is double: actively
 # looking for the default DNS server setup on the running machine; and 
 # emptying the host name resolution cache.
 #
 # Arguments:
 #    defaultdns        - Default DNS server
 proc ::resolv::init { { defaultdns "" } } {
     global ::resolv::R ::resolv::Cache
 
     catch "unset ::resolv::Cache"
     
     set ::resolv::R(dnsdefault) $defaultdns
 
     set res [catch "exec nslookup 127.0.0.1" lkup]
     if { $res == 0 } {
         set l [split $lkup]
         set nl ""
         foreach e $l {
             if { [string length $e] > 0 } {
                 lappend nl $e
             }
         }
         
         set hostname ""
         set len [llength $nl]
         for { set i 0 } { $i < $len } { incr i } {
             set e [lindex $nl $i]
             if { [string match -nocase "*server*" $e] } {
                 set hostname [lindex $nl [expr $i + 1]]
                 break
             }
         }
         
         if { $hostname != "" } {
             set ::resolv::R(dns) $hostname
         } else {
             set ::resolv::R(dns) $::resolv::R(dnsdefault)
         }
     }
 
     set ::resolv::R(initdone) 1
 
     return $::resolv::R(dns)
 }
 
 
 # Command Name     --  resolve
 # Original Author  --  Emmanuel Frecon - [email protected]
 #
 # Resolve a host name to an IP address.  This is a wrapping procedure around
 # the basic services of the dns library.
 #
 # Arguments:
 #    hostname        - Name of host
 proc ::resolv::resolve { hostname } {
     global ::resolv::R
     global ::resolv::Cache
 
     # Initialise if not already done. Auto initialisation cannot take
     # any known DNS server (known to the caller)
     if { ! $::resolv::R(initdone) } {
         ::resolv::init
     }
 
     # Check whether this is not simply a raw IP address. What about
     # IPv6 ??
     if { [regexp "\\d+\\.\\d+\\.\\d+\\.\\d+" $hostname] } {
         return $hostname
     }
 
     # Look for hostname in the cache, if found return.
     if { [array names ::resolv::Cache $hostname] != "" } {
         return $::resolv::Cache($hostname)
     }
 
     # Scream if we don't have any DNS server setup, since we cannot do
     # anything in that case.
     if { $::resolv::R(dns) == "" } {
         error "No dns server provided"
     }
 
     # Now resolve in a clean way
     set t [::dns::resolve $hostname -server $::resolv::R(dns)]
     set ip [lindex [::dns::address $t] 0]
     ::dns::cleanup $t
 
     # And store the result of resolution in our cache for further use.
     set ::resolv::Cache($hostname) $ip
 
     return $ip
 }