This is a DNS client over the UDP extension - 1Nov2011 CMcC
It provides a full and a simple DNS client class.
# dns query using Udp extension # Colin McCormack lappend auto_path [pwd]/Udp package require Udp package require TclOO package provide Dns 1.1 oo::class create DNS { method decodeName {varname} { upvar 1 $varname text set line {} set suffix "" while {$text ne ""} { binary scan $text ca* len text if {$len < 0} { # compression binary scan $text ca* offset text set offset [expr {($len & 0x3F)<<8 | ($offset & 0xFF)}] variable response set reference [string range $response $offset end] set suffix .[my decodeName reference] break } elseif {$len} { lappend line [string range $text 0 $len-1] set text [string range $text $len end] } else { break } } return [join $line .]$suffix } method decodeQD {varname qdcount} { upvar 1 $varname text set result {} while {$qdcount} { incr qdcount -1 set name [my decodeName text] binary scan $text SSa* qtype qclass text lappend result [list $name $qtype $qclass] } return $result } method decodeAddress {address} { catch { binary scan $address cccc a1 a2 a3 a4 foreach v {a1 a2 a3 a4} { set $v [expr { [set $v] & 0xff }] } set result [join [list $a1 $a2 $a3 $a4] .] } e eo return $result } method rdA {rd} { # a host address return [my decodeAddress $rd] } method rdNS {rd} { # an authoritative name server return [my decodeName rd] } method rdMD {rd} { # a mail destination (Obsolete - use MX) return [my decodeName rd] } method rdMF {rd} { # a mail forwarder (Obsolete - use MX) return [my decodeName rd] } method rdCNAME {rd} { # the canonical name for an alias return [my decodeName rd] } method rdSOA {rd} { # marks the start of a zone of authority set mname [my decodeName rd] set rname [my decodeName rd] binary scan $rd IIIII serial refresh retry expire minimum return [list mname $mname rname $rname serial $serial refresh $refresh retry $retry expire $expire minimum $minimum] } method rdMB {rd} { # a mailbox domain name (EXPERIMENTAL) return [my decodeName rd] } method rdMG {rd} { # a mail group member (EXPERIMENTAL) return [my decodeName rd] } method rdMR {rd} { # a mail rename domain name (EXPERIMENTAL) return [my decodeName rd] } method rdNULL {rd} { # a null RR (EXPERIMENTAL) return $rd } method rdWKS {rd} { # a well known service description binary scan $rd Ica* address protocol rd set address [my decodeAddress $rd] return [list address $address protocol $protocol bitmap $rd] } method rdPTR {rd} { # a domain name pointer return [my decodeName rd] } method rdHINFO {rd} { # host information set cpu [my decodeName rd] set os [my decodeName rd] return [list cpu $cpu os $os] } method rdMINFO {rd} { # mailbox or mail list information set rmailbx [my decodeName rd] set emailbx [my decodeName rd] return [list rmailbx $rmailbx emailbx $emailbx] } method rdMX {rd} { # mail exchange binary scan $rd S preference rd return [list preference $preference [my decodeName rd]] } method rdTXT {rd} { # text strings set result {} while {$rd ne ""} { lappend result [my decodeName rd] } return $result } method rr {varname count} { upvar 1 $varname text set result {} while {$count && $text ne ""} { incr count -1 set name [my decodeName text] binary scan $text SSISa* type class ttl rdlength text set type [string trimleft $type 0] variable qtypes; catch {set type [dict get $qtypes $type]} set class [string trimleft $class 0] variable qclasses; catch {set class [dict get $qclasses $class]} set rdata [string range $text 0 $rdlength-1] catch {set rdata [my rd$type $rdata]} set text [string range $text $rdlength end] lappend result [list name $name type $type class $class ttl $ttl rdata $rdata] } return $result } method decode {payload from port} { variable response $payload binary scan $payload SB8B8SSSS id h1 h2 qdcount ancount nscount arcount lassign [split $h1 ""] qr . . . . aa tc rd set rcode 0b0[string trimleft [string range $h2 4 end] 0] set rcode [expr {$rcode + 0}] set ra [string index $h2 0] set id [string trimleft $id 0] set result [list id $id qr $qr aa $aa tc $tc rd $rd ra $ra rcode $rcode qdcount $qdcount ancount $ancount nscount $nscount arcount $arcount] if {$rcode} { variable rcodes dict set result error [dict get $rcodes $rcode] } set content [string range $payload 12 end] dict set result qd [my decodeQD content $qdcount] dict set result an [my rr content $ancount] dict set result ns [my rr content $nscount] dict set result ar [my rr content $arcount] if {$content ne ""} { dict set result remainder $content } #puts stderr "RESPONSE: $result" return $result } # response - get result and invoke callback method response {payload from port chan} { if {[catch { my decode $payload $from $port } result eo]} { puts stderr "$result ($eo)" } else { # got a complete response - invoke callback for it. variable callbacks set id [dict get $result id] if {[dict exists $callbacks $id]} { set callback [dict get $callbacks $id] dict unset callbacks $id {*}$callback $result } } } method qhead {id {opcode QUERY} {recurse 1}} { if {$recurse} { set h 0x0100 } else { set h 0 } switch $opcode { QUERY {} IQUERY { set h [expr {$h | 0x10}] } STATUS { set h [expr {$h | 0x100}] } default { error "opcode must be one of QUERY, IQUERY, STATUS" } } return [binary format SS $id $h] } method dquery {name {qtype A} {qclass IN}} { variable qtypes; variable qclasses set query "" foreach label [split $name .] { set len [string length $label] if {$len > 255} { error "name component too long: '$label'" } append query [binary format ca$len $len $label] } append query \0 append query [binary format SS [dict get $qtypes $qtype] [dict get $qclasses $qclass]] return $query } method dqueries {id args} { set count 0 set opcode QUERY set qtype A set qclass * set recurse 1 set query "" set skip 0 foreach arg $args { if {$skip} { set $var $arg set skip 0 continue } switch $arg { -qtype - -qclass - -opcode - -callback - -recurse { incr skip set var [string trim $arg -] } default { append query [my dquery $arg $qtype $qclass] incr count } } } if {[info exists callback]} { variable callbacks; dict set callbacks $id $callback } set query "[my qhead $id $opcode $recurse][binary format SSSS $count 0 0 0]$query" return $query } method query {dns args} { variable udp; variable domain variable qcount; incr qcount set query [my dqueries $qcount {*}$args] udp::send $udp $dns $domain $query } destructor { variable udp catch {chan close $udp} } constructor {args} { variable domain 53 variable {*}$args variable qcount 1 variable udp [::udp create 0 [list [self] response]] variable callbacks {} variable rcodes { 0 {No error condition} 1 {Format error - The name server was unable to interpret the query.} 2 {Server failure - The name server was unable to process this query due to a problem with the name server.} 3 {Name Error - Meaningful only for responses from an authoritative name server, this code signifies that the domain name referenced in the query does not exist.} 4 {Not Implemented - The name server does not support the requested kind of query.} 5 {Refused - The name server refuses to perform the specified operation for policy reasons. For example, a name server may not wish to provide the information to the particular requester, r a name server may not wish to perform a particular operation (e.g., zone transfer) for particular data.} } variable qtypes foreach {n v d} { A 1 {a host address} NS 2 {an authoritative name server} MD 3 {a mail destination (Obsolete - use MX)} MF 4 {a mail forwarder (Obsolete - use MX)} CNAME 5 {the canonical name for an alias} SOA 6 {marks the start of a zone of authority} MB 7 {a mailbox domain name (EXPERIMENTAL)} MG 8 {a mail group member (EXPERIMENTAL)} MR 9 {a mail rename domain name (EXPERIMENTAL)} NULL 10 {a null RR (EXPERIMENTAL)} WKS 11 {a well known service description} PTR 12 {a domain name pointer} HINFO 13 {host information} MINFO 14 {mailbox or mail list information} MX 15 {mail exchange} TXT 16 {text strings} AXFR 252 {A request for a transfer of an entire zone} MAILB 253 {A request for mailbox-related records (MB, MG or MR)} MAILA 254 {A request for mail agent RRs (Obsolete - see MX)} * 255 {A request for all records} } { dict set qtypes $n $v dict set qtypes $v $n } variable qclasses foreach {n v d} { IN 1 {the Internet} CS 2 {the CSNET class (Obsolete - used only for examples in some obsolete RFCs)} CH 3 {the CHAOS class} HS 4 {Hesiod [Dyer 87]} * 255 {any class} } { dict set qclasses $n $v dict set qclasses $v $n } } } oo::class create DNSsimple { superclass DNS method response {payload from port} { if {[catch { my decode $payload $from $port } result eo]} { puts stderr "$result ($eo)" } else { # got a complete response - invoke callback for it. variable callbacks set id [dict get $result id] if {[dict exists $callbacks $id]} { if {[dict exists $result error]} { # got an error set lookup [list [dict get $result error]] } else { # post-process the result to just give simplified answers set lookup {} foreach {v} [dict get $result an] { dict lappend lookup [string trimleft [dict get $v name] .] [dict get $v rdata] } } set callback [dict get $callbacks $id] dict unset callbacks $id {*}$callback {*}$lookup } } } method reverse {dns args} { set skip 0 set rquery {-qtype PTR} foreach arg $args { if {$skip} { set skip 0 } elseif {[string match -* $arg]} { if {$arg eq "-qtype"} { error "reverse queries cannot specify -qtype" } set skip 1 } else { # got to be an IP address set arg [join [lreverse [split $arg .]] .].in-addr.arpa } lappend rquery $arg } my query $dns {*}$rquery } constructor {args} { next {*}$args } } if {[info exists argv0] && ($argv0 eq [info script])} { set dns 192.168.178.2 ;# your dns server proc putss {where args} { puts $where DNS:$args } # simple DNS query processing DNSsimple create dnss dnss query $dns -callback {putss stderr} google.com ;# make a simple query dnss reverse $dns -callback {putss stderr} 74.125.237.48 dnss query $dns -callback {putss stderr} localhost dnss query $dns -callback {putss stderr} google.com localhost # full DNS query processing DNS create dns dns query $dns -callback {puts stderr} thighbone ;# make a query dns query $dns -qtype A -callback {puts stderr} google.com ;# make a query vwait forever }