dns.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 package require Tcl 8.2;
00035 package require logger;
00036 package require uri;
00037 package require uri::urn;
00038 package require ip;
00039
00040 namespace ::dns {
00041 variable version 1.3.2
00042 variable rcsid {$Id: dns.tcl,v 1.35 2007/08/26 00:44:34 patthoyts Exp $}
00043
00044 namespace export configure resolve name address cname \
00045 status re wait = cleanup errorcode
00046
00047 variable options
00048 if {![info exists options]} {
00049 array options = {
00050 port 53
00051 timeout 30000
00052 protocol tcp
00053 search {}
00054 nameserver {localhost}
00055 loglevel warn
00056 }
00057 variable log [logger::init dns]
00058 ${log}::level = $options(loglevel)
00059 }
00060
00061
00062 if {![catch {package require udp 1.0.4} msg]} { ;
00063
00064 options = (protocol) udp
00065 } else {
00066 if {![catch {package require ceptcl} msg]} {
00067 options = (protocol) udp
00068 }
00069 }
00070
00071 variable types
00072 array types = {
00073 A 1 NS 2 MD 3 MF 4 CNAME 5 SOA 6 MB 7 MG 8 MR 9
00074 NULL 10 WKS 11 PTR 12 HINFO 13 MINFO 14 MX 15 TXT 16
00075 SPF 16 AAAA 28 SRV 33 IXFR 251 AXFR 252 MAILB 253 MAILA 254
00076 ANY 255 * 255
00077 }
00078
00079 variable classes
00080 array classes = { IN 1 CS 2 CH 3 HS 4 * 255}
00081
00082 variable uid
00083 if {![info exists uid]} {
00084 uid = 0
00085 }
00086 }
00087
00088
00089
00090
00091
00092
00093
00094 ret ::dns::configure (type args) {
00095 variable options
00096 variable log
00097
00098 if {[llength $args] < 1} {
00099 set r {}
00100 foreach opt [lsort [array names options]] {
00101 lappend r -$opt $options($opt)
00102 }
00103 return $r
00104 }
00105
00106 set cget 0
00107 if {[llength $args] == 1} {
00108 set cget 1
00109 }
00110
00111 while {[string match -* [lindex $args 0]]} {
00112 switch -glob -- [lindex $args 0] {
00113 -n* -
00114 -ser* {
00115 if {$cget} {
00116 return $options(nameserver)
00117 } else {
00118 set options(nameserver) [Pop args 1]
00119 }
00120 }
00121 -po* {
00122 if {$cget} {
00123 return $options(port)
00124 } else {
00125 set options(port) [Pop args 1]
00126 }
00127 }
00128 -ti* {
00129 if {$cget} {
00130 return $options(timeout)
00131 } else {
00132 set options(timeout) [Pop args 1]
00133 }
00134 }
00135 -pr* {
00136 if {$cget} {
00137 return $options(protocol)
00138 } else {
00139 set proto [string tolower [Pop args 1]]
00140 if {[string compare udp $proto] == 0 \
00141 && [string compare tcp $proto] == 0} {
00142 return -code error "invalid protocol \"$proto\":\
00143 protocol must be either \"udp\" or \"tcp\""
00144 }
00145 set options(protocol) $proto
00146 }
00147 }
00148 -sea* {
00149 if {$cget} {
00150 return $options(search)
00151 } else {
00152 set options(search) [Pop args 1]
00153 }
00154 }
00155 -log* {
00156 if {$cget} {
00157 return $options(loglevel)
00158 } else {
00159 set options(loglevel) [Pop args 1]
00160 ${log}::setlevel $options(loglevel)
00161 }
00162 }
00163 -- { Pop args ; break }
00164 default {
00165 set opts [join [lsort [array names options]] ", -"]
00166 return -code error "bad option [lindex $args 0]:\
00167 must be one of -$opts"
00168 }
00169 }
00170 Pop args
00171 }
00172
00173 return
00174 }
00175
00176
00177
00178
00179
00180
00181
00182 ret ::dns::resolve (type query , type args) {
00183 variable uid
00184 variable options
00185 variable log
00186
00187 # get a guaranteed unique and non-present token id.
00188 set id [incr uid]
00189 while {[info exists [set token [namespace current]::$id]]} {
00190 set id [incr uid]
00191 }
00192 # FRINK: nocheck
00193 variable $token
00194 upvar 0 $token state
00195
00196 # Setup token/state defaults.
00197 set state(id) $id
00198 set state(query) $query
00199 set state(qdata) ""
00200 set state(opcode) 0; # 0 = query, 1 = inverse query.
00201 set state(-type) A; # DNS record type (A address)
00202 set state(-class) IN; # IN (internet address space)
00203 set state(-recurse) 1; # Recursion Desired
00204 set state(-command) {}; # asynchronous handler
00205 set state(-timeout) $options(timeout); # connection timeout default.
00206 set state(-nameserver) $options(nameserver);# default nameserver
00207 set state(-port) $options(port); # default namerservers port
00208 set state(-search) $options(search); # domain search list
00209 set state(-protocol) $options(protocol); # which protocol udp/tcp
00210
00211 # Handle DNS URL's
00212 if {[string match "dns:*" $query]} {
00213 array set URI [uri::split $query]
00214 foreach {opt value} [uri::split $query] {
00215 if {$value != {} && [info exists state(-$opt)]} {
00216 set state(-$opt) $value
00217 }
00218 }
00219 set state(query) $URI(query)
00220 ${log}::debug "parsed query: $query"
00221 }
00222
00223 while {[string match -* [lindex $args 0]]} {
00224 switch -glob -- [lindex $args 0] {
00225 -n* - ns -
00226 -ser* { set state(-nameserver) [Pop args 1] }
00227 -po* { set state(-port) [Pop args 1] }
00228 -ti* { set state(-timeout) [Pop args 1] }
00229 -co* { set state(-command) [Pop args 1] }
00230 -cl* { set state(-class) [Pop args 1] }
00231 -ty* { set state(-type) [Pop args 1] }
00232 -pr* { set state(-protocol) [Pop args 1] }
00233 -sea* { set state(-search) [Pop args 1] }
00234 -re* { set state(-recurse) [Pop args 1] }
00235 -inv* { set state(opcode) 1 }
00236 -status {set state(opcode) 2}
00237 -data { set state(qdata) [Pop args 1] }
00238 default {
00239 set opts [join [lsort [array names state -*]] ", "]
00240 return -code error "bad option [lindex $args 0]: \
00241 must be $opts"
00242 }
00243 }
00244 Pop args
00245 }
00246
00247 if {$state(-nameserver) == {}} {
00248 return -code error "no nameserver specified"
00249 }
00250
00251 if {$state(-protocol) == "udp"} {
00252 if {[llength [package provide ceptcl]] == 0 \
00253 && [llength [package provide udp]] == 0} {
00254 return -code error "udp support is not available,\
00255 get ceptcl or tcludp"
00256 }
00257 }
00258
00259 # Check for reverse lookups
00260 if {[regexp {^(?:\d{0,3}\.){3}\d{0,3}$} $state(query)]} {
00261 set addr [lreverse [split $state(query) .]]
00262 lappend addr in-addr arpa
00263 set state(query) [join $addr .]
00264 set state(-type) PTR
00265 }
00266
00267 BuildMessage $token
00268
00269 if {$state(-protocol) == "tcp"} {
00270 TcpTransmit $token
00271 if {$state(-command) == {}} {
00272 wait $token
00273 }
00274 } else {
00275 UdpTransmit $token
00276 }
00277
00278 return $token
00279 }
00280
00281
00282
00283
00284
00285
00286 ret ::dns::name (type token) {
00287 set r {}
00288 Flags $token flags
00289 array set reply [Decode $token]
00290
00291 switch -exact -- $flags(opcode) {
00292 0 {
00293 # QUERY
00294 foreach answer $reply(AN) {
00295 array set AN $answer
00296 if {![info exists AN(type)]} {set AN(type) {}}
00297 switch -exact -- $AN(type) {
00298 MX - NS - PTR {
00299 if {[info exists AN(rdata)]} {lappend r $AN(rdata)}
00300 }
00301 default {
00302 if {[info exists AN(name)]} {
00303 lappend r $AN(name)
00304 }
00305 }
00306 }
00307 }
00308 }
00309
00310 1 {
00311 # IQUERY
00312 foreach answer $reply(QD) {
00313 array set QD $answer
00314 lappend r $QD(name)
00315 }
00316 }
00317 default {
00318 return -code error "not supported for this query type"
00319 }
00320 }
00321 return $r
00322 }
00323
00324
00325
00326
00327 ret ::dns::address (type token) {
00328 set r {}
00329 array set reply [Decode $token]
00330 foreach answer $reply(AN) {
00331 array set AN $answer
00332
00333 if {[info exists AN(type)]} {
00334 switch -exact -- $AN(type) {
00335 "A" {
00336 lappend r $AN(rdata)
00337 }
00338 "AAAA" {
00339 lappend r $AN(rdata)
00340 }
00341 }
00342 }
00343 }
00344 return $r
00345 }
00346
00347
00348
00349
00350 ret ::dns::cname (type token) {
00351 set r {}
00352 array set reply [Decode $token]
00353 foreach answer $reply(AN) {
00354 array set AN $answer
00355
00356 if {[info exists AN(type)]} {
00357 if {$AN(type) == "CNAME"} {
00358 lappend r $AN(rdata)
00359 }
00360 }
00361 }
00362 return $r
00363 }
00364
00365
00366
00367
00368 ret ::dns::result (type token , type args) {
00369 array set reply [eval [linsert $args 0 Decode $token]]
00370 return $reply(AN)
00371 }
00372
00373
00374
00375
00376
00377
00378 ret ::dns::status (type token) {
00379 upvar #0 $token state
00380 return $state(status)
00381 }
00382
00383
00384
00385
00386 ret ::dns::error (type token) {
00387 upvar #0 $token state
00388 if {[info exists state(error)]} {
00389 return $state(error)
00390 }
00391 return ""
00392 }
00393
00394
00395
00396
00397 ret ::dns::errorcode (type token) {
00398 upvar #0 $token state
00399 set flags [Flags $token]
00400 set ndx [lsearch -exact $flags errorcode]
00401 incr ndx
00402 return [lindex $flags $ndx]
00403 }
00404
00405
00406
00407
00408 ret ::dns::reset (type token , optional why =reset , optional errormsg ={)} {
00409 upvar #0 $token state
00410 set state(status) $why
00411 if {[string length $errormsg] > 0 && ![info exists state(error)]} {
00412 state = (error) $errormsg
00413 }
00414 catch {fileevent $state(sock) readable {}}
00415 Finish $token
00416 }
00417
00418
00419
00420
00421 ret ::dns::wait (type token) {
00422 upvar #0 $token state
00423
00424 if {$state(status) == "connect"} {
00425 vwait [subst $token](status)
00426 }
00427
00428 return $state(status)
00429 }
00430
00431
00432
00433
00434 ret ::dns::cleanup (type token) {
00435 upvar #0 $token state
00436 if {[info exists state]} {
00437 catch {close $state(sock)}
00438 catch {after cancel $state(after)}
00439 unset state
00440 }
00441 }
00442
00443
00444
00445
00446
00447
00448 ret ::dns::dump (type args) {
00449 if {[llength $args] == 1} {
00450 set type -reply
00451 set token [lindex $args 0]
00452 } elseif { [llength $args] == 2 } {
00453 set type [lindex $args 0]
00454 set token [lindex $args 1]
00455 } else {
00456 return -code error "wrong # args:\
00457 should be \"dump ?option? methodName\""
00458 }
00459
00460 # FRINK: nocheck
00461 variable $token
00462 upvar 0 $token state
00463
00464 set result {}
00465 switch -glob -- $type {
00466 -qu* -
00467 -req* {
00468 set result [DumpMessage $state(request)]
00469 }
00470 -rep* {
00471 set result [DumpMessage $state(reply)]
00472 }
00473 default {
00474 error "unrecognised option: must be one of \
00475 \"-query\", \"-request\" or \"-reply\""
00476 }
00477 }
00478
00479 return $result
00480 }
00481
00482
00483
00484
00485 ret ::dns::DumpMessage (type data) {
00486 set result {}
00487 binary scan $data c* r
00488 foreach c $r {
00489 append result [format "%02x " [expr {$c & 0xff}]]
00490 }
00491 return $result
00492 }
00493
00494
00495
00496
00497
00498
00499 ret ::dns::BuildMessage (type token) {
00500 # FRINK: nocheck
00501 variable $token
00502 upvar 0 $token state
00503 variable types
00504 variable classes
00505 variable options
00506
00507 if {! [info exists types($state(-type))] } {
00508 return -code error "invalid DNS query type"
00509 }
00510
00511 if {! [info exists classes($state(-class))] } {
00512 return -code error "invalid DNS query class"
00513 }
00514
00515 set qdcount 0
00516 set qsection {}
00517 set nscount 0
00518 set nsdata {}
00519
00520 # In theory we can send multiple queries. In practice, named doesn't
00521 # appear to like that much. If it did work we'd do this:
00522 # foreach domain [linsert $options(search) 0 {}] ...
00523
00524
00525 # Pack the query: QNAME QTYPE QCLASS
00526 set qsection [PackName $state(query)]
00527 append qsection [binary format SS \
00528 $types($state(-type))\
00529 $classes($state(-class))]
00530 incr qdcount
00531
00532 if {[string length $state(qdata)] > 0} {
00533 set nsdata [eval [linsert $state(qdata) 0 PackRecord]]
00534 incr nscount
00535 }
00536
00537 switch -exact -- $state(opcode) {
00538 0 {
00539 # QUERY
00540 set state(request) [binary format SSSSSS $state(id) \
00541 [expr {($state(opcode) << 11) | ($state(-recurse) << 8)}] \
00542 $qdcount 0 $nscount 0]
00543 append state(request) $qsection $nsdata
00544 }
00545 1 {
00546 # IQUERY
00547 set state(request) [binary format SSSSSS $state(id) \
00548 [expr {($state(opcode) << 11) | ($state(-recurse) << 8)}] \
00549 0 $qdcount 0 0 0]
00550 append state(request) \
00551 [binary format cSSI 0 \
00552 $types($state(-type)) $classes($state(-class)) 0]
00553 switch -exact -- $state(-type) {
00554 A {
00555 append state(request) \
00556 [binary format Sc4 4 [split $state(query) .]]
00557 }
00558 PTR {
00559 append state(request) \
00560 [binary format Sc4 4 [split $state(query) .]]
00561 }
00562 default {
00563 return -code error "inverse query not supported for this type"
00564 }
00565 }
00566 }
00567 default {
00568 return -code error "operation not supported"
00569 }
00570 }
00571
00572 return
00573 }
00574
00575
00576 ret ::dns::PackName (type name) {
00577 set data ""
00578 foreach part [split [string trim $name .] .] {
00579 set len [string length $part]
00580 append data [binary format ca$len $len $part]
00581 }
00582 append data \x00
00583 return $data
00584 }
00585
00586
00587 ret ::dns::PackString (type text) {
00588 set len [string length $text]
00589 set data [binary format ca$len $len $text]
00590 return $data
00591 }
00592
00593
00594
00595
00596
00597 ret ::dns::PackRecord (type args) {
00598 variable types
00599 variable classes
00600 array set rr {name "" type A class IN ttl 0 rdlength 0 rdata ""}
00601 array set rr $args
00602 set data [PackName $rr(name)]
00603
00604 switch -exact -- $rr(type) {
00605 CNAME - MB - MD - MF - MG - MR - NS - PTR {
00606 set rr(rdata) [PackName $rr(rdata)]
00607 }
00608 HINFO {
00609 array set r {CPU {} OS {}}
00610 array set r $rr(rdata)
00611 set rr(rdata) [PackString $r(CPU)]
00612 append rr(rdata) [PackString $r(OS)]
00613 }
00614 MINFO {
00615 array set r {RMAILBX {} EMAILBX {}}
00616 array set r $rr(rdata)
00617 set rr(rdata) [PackString $r(RMAILBX)]
00618 append rr(rdata) [PackString $r(EMAILBX)]
00619 }
00620 MX {
00621 foreach {pref exch} $rr(rdata) break
00622 set rr(rdata) [binary format S $pref]
00623 append rr(rdata) [PackName $exch]
00624 }
00625 TXT {
00626 set str $rr(rdata)
00627 set len [string length [set str $rr(rdata)]]
00628 set rr(rdata) ""
00629 for {set n 0} {$n < $len} {incr n} {
00630 set s [string range $str $n [incr n 253]]
00631 append rr(rdata) [PackString $s]
00632 }
00633 }
00634 NULL {}
00635 SOA {
00636 array set r {MNAME {} RNAME {}
00637 SERIAL 0 REFRESH 0 RETRY 0 EXPIRE 0 MINIMUM 0}
00638 array set r $rr(rdata)
00639 set rr(rdata) [PackName $r(MNAME)]
00640 append rr(rdata) [PackName $r(RNAME)]
00641 append rr(rdata) [binary format IIIII $r(SERIAL) \
00642 $r(REFRESH) $r(RETRY) $r(EXPIRE) $r(MINIMUM)]
00643 }
00644 }
00645
00646 # append the root label and the type flag and query class.
00647 append data [binary format SSIS $types($rr(type)) \
00648 $classes($rr(class)) $rr(ttl) [string length $rr(rdata)]]
00649 append data $rr(rdata)
00650 return $data
00651 }
00652
00653
00654
00655
00656
00657
00658 ret ::dns::TcpTransmit (type token) {
00659 # FRINK: nocheck
00660 variable $token
00661 upvar 0 $token state
00662
00663 # setup the timeout
00664 if {$state(-timeout) > 0} {
00665 set state(after) [after $state(-timeout) \
00666 [list [namespace origin reset] \
00667 $token timeout\
00668 "operation timed out"]]
00669 }
00670
00671 # Sometimes DNS servers drop TCP requests. So it's better to
00672 # use asynchronous connect
00673 set s [socket -async $state(-nameserver) $state(-port)]
00674 fileevent $s writable [list [namespace origin TcpConnected] $token $s]
00675 set state(sock) $s
00676 set state(status) connect
00677
00678 return $token
00679 }
00680
00681 ret ::dns::TcpConnected (type token , type s) {
00682 variable $token
00683 upvar 0 $token state
00684
00685 fileevent $s writable {}
00686 if {[catch {fconfigure $s -peername}]} {
00687 # TCP connection failed
00688 Finish $token "can't connect to server"
00689 return
00690 }
00691
00692 fconfigure $s -blocking 0 -translation binary -buffering none
00693
00694 # For TCP the message must be prefixed with a 16bit length field.
00695 set req [binary format S [string length $state(request)]]
00696 append req $state(request)
00697
00698 puts -nonewline $s $req
00699
00700 fileevent $s readable [list [namespace current]::TcpEvent $token]
00701 }
00702
00703
00704
00705
00706
00707
00708
00709
00710
00711
00712 ret ::dns::UdpTransmit (type token) {
00713 # FRINK: nocheck
00714 variable $token
00715 upvar 0 $token state
00716
00717 # setup the timeout
00718 if {$state(-timeout) > 0} {
00719 set state(after) [after $state(-timeout) \
00720 [list [namespace origin reset] \
00721 $token timeout\
00722 "operation timed out"]]
00723 }
00724
00725 if {[llength [package provide ceptcl]] > 0} {
00726 # using ceptcl
00727 set state(sock) [cep -type datagram $state(-nameserver) $state(-port)]
00728 fconfigure $state(sock) -blocking 0
00729 } else {
00730 # using tcludp
00731 set state(sock) [udp_open]
00732 udp_conf $state(sock) $state(-nameserver) $state(-port)
00733 }
00734 fconfigure $state(sock) -translation binary -buffering none
00735 set state(status) connect
00736 puts -nonewline $state(sock) $state(request)
00737
00738 fileevent $state(sock) readable [list [namespace current]::UdpEvent $token]
00739
00740 return $token
00741 }
00742
00743
00744
00745
00746
00747
00748 ret ::dns::Finish (type token , optional errormsg ="") {
00749 # FRINK: nocheck
00750 variable $token
00751 upvar 0 $token state
00752 global errorInfo errorCode
00753
00754 if {[string length $errormsg] != 0} {
00755 set state(error) $errormsg
00756 set state(status) error
00757 }
00758 catch {close $state(sock)}
00759 catch {after cancel $state(after)}
00760 if {[info exists state(-command)] && $state(-command) != {}} {
00761 if {[catch {eval $state(-command) {$token}} err]} {
00762 if {[string length $errormsg] == 0} {
00763 set state(error) [list $err $errorInfo $errorCode]
00764 set state(status) error
00765 }
00766 }
00767 if {[info exists state(-command)]} {
00768 unset state(-command)
00769 }
00770 }
00771 }
00772
00773
00774
00775
00776
00777
00778 ret ::dns::Eof (type token) {
00779 # FRINK: nocheck
00780 variable $token
00781 upvar 0 $token state
00782 set state(status) eof
00783 Finish $token
00784 }
00785
00786
00787
00788
00789
00790
00791 ret ::dns::Receive (type token) {
00792 # FRINK: nocheck
00793 variable $token
00794 upvar 0 $token state
00795
00796 binary scan $state(reply) SS id flags
00797 set status [expr {$flags & 0x000F}]
00798
00799 switch -- $status {
00800 0 {
00801 set state(status) ok
00802 Finish $token
00803 }
00804 1 { Finish $token "Format error - unable to interpret the query." }
00805 2 { Finish $token "Server failure - internal server error." }
00806 3 { Finish $token "Name Error - domain does not exist" }
00807 4 { Finish $token "Not implemented - the query type is not available." }
00808 5 { Finish $token "Refused - your request has been refused by the server." }
00809 default {
00810 Finish $token "unrecognised error code: $err"
00811 }
00812 }
00813 }
00814
00815
00816
00817
00818
00819
00820 ret ::dns::TcpEvent (type token) {
00821 variable log
00822 # FRINK: nocheck
00823 variable $token
00824 upvar 0 $token state
00825 set s $state(sock)
00826
00827 if {[eof $s]} {
00828 Eof $token
00829 return
00830 }
00831
00832 set status [catch {read $state(sock)} result]
00833 if {$status != 0} {
00834 ${log}::debug "Event error: $result"
00835 Finish $token "error reading data: $result"
00836 } elseif { [string length $result] >= 0 } {
00837 if {[catch {
00838 # Handle incomplete reads - check the size and keep reading.
00839 if {![info exists state(size)]} {
00840 binary scan $result S state(size)
00841 set result [string range $result 2 end]
00842 }
00843 append state(reply) $result
00844
00845 # check the length and flags and chop off the tcp length prefix.
00846 if {[string length $state(reply)] >= $state(size)} {
00847 binary scan $result S id
00848 set id [expr {$id & 0xFFFF}]
00849 if {$id != [expr {$state(id) & 0xFFFF}]} {
00850 ${log}::error "received packed with incorrect id"
00851 }
00852 # bug #1158037 - doing this causes problems > 65535 requests!
00853 #Receive [namespace current]::$id
00854 Receive $token
00855 } else {
00856 ${log}::debug "Incomplete tcp read:\
00857 [string length $state(reply)] should be $state(size)"
00858 }
00859 } err]} {
00860 Finish $token "Event error: $err"
00861 }
00862 } elseif { [eof $state(sock)] } {
00863 Eof $token
00864 } elseif { [fblocked $state(sock)] } {
00865 ${log}::debug "Event blocked"
00866 } else {
00867 ${log}::critical "Event error: this can't happen!"
00868 Finish $token "Event error: this can't happen!"
00869 }
00870 }
00871
00872
00873
00874
00875
00876 ret ::dns::UdpEvent (type token) {
00877 # FRINK: nocheck
00878 variable $token
00879 upvar 0 $token state
00880 set s $state(sock)
00881
00882 set payload [read $state(sock)]
00883 append state(reply) $payload
00884
00885 binary scan $payload S id
00886 set id [expr {$id & 0xFFFF}]
00887 if {$id != [expr {$state(id) & 0xFFFF}]} {
00888 ${log}::error "received packed with incorrect id"
00889 }
00890 # bug #1158037 - doing this causes problems > 65535 requests!
00891 #Receive [namespace current]::$id
00892 Receive $token
00893 }
00894
00895
00896
00897 ret ::dns::Flags (type token , optional varname ={)} {
00898 # FRINK: nocheck
00899 variable $token
00900 upvar 0 $token state
00901
00902 if {$varname != {}} {
00903 upvar $varname flags
00904 }
00905
00906 array flags = {query 0 opcode 0 authoritative 0 errorcode 0
00907 truncated 0 recursion_desired 0 recursion_allowed 0}
00908
00909 binary scan $state(reply) SSSSSS mid hdr nQD nAN nNS nAR
00910
00911 flags = (response) [expr {($hdr & 0x8000) >> 15}]
00912 flags = (opcode) [expr {($hdr & 0x7800) >> 11}]
00913 flags = (authoritative) [expr {($hdr & 0x0400) >> 10}]
00914 flags = (truncated) [expr {($hdr & 0x0200) >> 9}]
00915 flags = (recursion_desired) [expr {($hdr & 0x0100) >> 8}]
00916 flafs = (recursion_allowed) [expr {($hdr & 0x0080) >> 7}]
00917 flags = (errorcode) [expr {($hdr & 0x000F)}]
00918
00919 return [array get flags]
00920 }
00921
00922
00923
00924
00925
00926
00927 ret ::dns::Decode (type token , type args) {
00928 variable log
00929 # FRINK: nocheck
00930 variable $token
00931 upvar 0 $token state
00932
00933 array set opts {-rdata 0 -query 0}
00934 while {[string match -* [set option [lindex $args 0]]]} {
00935 switch -exact -- $option {
00936 -rdata { set opts(-rdata) 1 }
00937 -query { set opts(-query) 1 }
00938 default {
00939 return -code error "bad option \"$option\":\
00940 must be -rdata"
00941 }
00942 }
00943 Pop args
00944 }
00945
00946 if {$opts(-query)} {
00947 binary scan $state(request) SSSSSSc* mid hdr nQD nAN nNS nAR data
00948 } else {
00949 binary scan $state(reply) SSSSSSc* mid hdr nQD nAN nNS nAR data
00950 }
00951
00952 set fResponse [expr {($hdr & 0x8000) >> 15}]
00953 set fOpcode [expr {($hdr & 0x7800) >> 11}]
00954 set fAuthoritative [expr {($hdr & 0x0400) >> 10}]
00955 set fTrunc [expr {($hdr & 0x0200) >> 9}]
00956 set fRecurse [expr {($hdr & 0x0100) >> 8}]
00957 set fCanRecurse [expr {($hdr & 0x0080) >> 7}]
00958 set fRCode [expr {($hdr & 0x000F)}]
00959 set flags ""
00960
00961 if {$fResponse} {set flags "QR"} else {set flags "Q"}
00962 set opcodes [list QUERY IQUERY STATUS]
00963 lappend flags [lindex $opcodes $fOpcode]
00964 if {$fAuthoritative} {lappend flags "AA"}
00965 if {$fTrunc} {lappend flags "TC"}
00966 if {$fRecurse} {lappend flags "RD"}
00967 if {$fCanRecurse} {lappend flags "RA"}
00968
00969 set info "ID: $mid\
00970 Fl: [format 0x%02X [expr {$hdr & 0xFFFF}]] ($flags)\
00971 NQ: $nQD\
00972 NA: $nAN\
00973 NS: $nNS\
00974 AR: $nAR"
00975 ${log}::debug $info
00976
00977 set ndx 12
00978 set r {}
00979 set QD [ReadQuestion $nQD $state(reply) ndx]
00980 lappend r QD $QD
00981 set AN [ReadAnswer $nAN $state(reply) ndx $opts(-rdata)]
00982 lappend r AN $AN
00983 set NS [ReadAnswer $nNS $state(reply) ndx $opts(-rdata)]
00984 lappend r NS $NS
00985 set AR [ReadAnswer $nAR $state(reply) ndx $opts(-rdata)]
00986 lappend r AR $AR
00987 return $r
00988 }
00989
00990
00991
00992 ret ::dns::Expand (type data) {
00993 set r {}
00994 binary scan $data c* d
00995 foreach c $d {
00996 lappend r [expr {$c & 0xFF}]
00997 }
00998 return $r
00999 }
01000
01001
01002
01003
01004
01005
01006 ret ::dns::Pop (type varname , optional nth =0) {
01007 upvar $varname args
01008 set r [lindex $args $nth]
01009 set args [lreplace $args $nth $nth]
01010 return $r
01011 }
01012
01013
01014
01015
01016
01017 ret ::dns::lreverse (type lst) {
01018 set res {}
01019 set i [llength $lst]
01020 while {$i} {lappend res [lindex $lst [incr i -1]]}
01021 return $res
01022 }
01023
01024
01025
01026 ret ::dns::KeyOf (type arrayname , type value , optional default ={)} {
01027 upvar $arrayname array
01028 set lst [array get array]
01029 set ndx [lsearch -exact $lst $value]
01030 if {$ndx != -1} {
01031 incr ndx -1
01032 r = [lindex $lst $ndx]
01033 } else {
01034 r = $default
01035 }
01036 return $r
01037 }
01038
01039
01040
01041
01042
01043
01044 ret ::dns::ReadQuestion (type nitems , type data , type indexvar) {
01045 variable types
01046 variable classes
01047 upvar $indexvar index
01048 set result {}
01049
01050 for {set cn 0} {$cn < $nitems} {incr cn} {
01051 set r {}
01052 lappend r name [ReadName data $index offset]
01053 incr index $offset
01054
01055 # Read off QTYPE and QCLASS for this query.
01056 set ndx $index
01057 incr index 3
01058 binary scan [string range $data $ndx $index] SS qtype qclass
01059 set qtype [expr {$qtype & 0xFFFF}]
01060 set qclass [expr {$qclass & 0xFFFF}]
01061 incr index
01062 lappend r type [KeyOf types $qtype $qtype] \
01063 class [KeyOf classes $qclass $qclass]
01064 lappend result $r
01065 }
01066 return $result
01067 }
01068
01069
01070
01071
01072
01073 ret ::dns::ReadAnswer (type nitems , type data , type indexvar , optional raw =0) {
01074 variable types
01075 variable classes
01076 upvar $indexvar index
01077 set result {}
01078
01079 for {set cn 0} {$cn < $nitems} {incr cn} {
01080 set r {}
01081 lappend r name [ReadName data $index offset]
01082 incr index $offset
01083
01084 # Read off TYPE, CLASS, TTL and RDLENGTH
01085 binary scan [string range $data $index end] SSIS type class ttl rdlength
01086
01087 set type [expr {$type & 0xFFFF}]
01088 set type [KeyOf types $type $type]
01089
01090 set class [expr {$class & 0xFFFF}]
01091 set class [KeyOf classes $class $class]
01092
01093 set ttl [expr {$ttl & 0xFFFFFFFF}]
01094 set rdlength [expr {$rdlength & 0xFFFF}]
01095 incr index 10
01096 set rdata [string range $data $index [expr {$index + $rdlength - 1}]]
01097
01098 if {! $raw} {
01099 switch -- $type {
01100 A {
01101 set rdata [join [Expand $rdata] .]
01102 }
01103 AAAA {
01104 set rdata [ip::contract [ip::ToString $rdata]]
01105 }
01106 NS - CNAME - PTR {
01107 set rdata [ReadName data $index off]
01108 }
01109 MX {
01110 binary scan $rdata S preference
01111 set exchange [ReadName data [expr {$index + 2}] off]
01112 set rdata [list $preference $exchange]
01113 }
01114 SRV {
01115 set x $index
01116 set rdata [list priority [ReadUShort data $x off]]
01117 incr x $off
01118 lappend rdata weight [ReadUShort data $x off]
01119 incr x $off
01120 lappend rdata port [ReadUShort data $x off]
01121 incr x $off
01122 lappend rdata target [ReadName data $x off]
01123 incr x $off
01124 }
01125 TXT {
01126 set rdata [ReadString data $index $rdlength]
01127 }
01128 SOA {
01129 set x $index
01130 set rdata [list MNAME [ReadName data $x off]]
01131 incr x $off
01132 lappend rdata RNAME [ReadName data $x off]
01133 incr x $off
01134 lappend rdata SERIAL [ReadULong data $x off]
01135 incr x $off
01136 lappend rdata REFRESH [ReadLong data $x off]
01137 incr x $off
01138 lappend rdata RETRY [ReadLong data $x off]
01139 incr x $off
01140 lappend rdata EXPIRE [ReadLong data $x off]
01141 incr x $off
01142 lappend rdata MINIMUM [ReadULong data $x off]
01143 incr x $off
01144 }
01145 }
01146 }
01147
01148 incr index $rdlength
01149 lappend r type $type class $class ttl $ttl rdlength $rdlength rdata $rdata
01150 lappend result $r
01151 }
01152 return $result
01153 }
01154
01155
01156
01157
01158
01159
01160 ret ::dns::ReadLong (type datavar , type index , type usedvar) {
01161 upvar $datavar data
01162 upvar $usedvar used
01163 set r {}
01164 set used 0
01165 if {[binary scan $data @${index}I r]} {
01166 set used 4
01167 }
01168 return $r
01169 }
01170
01171 ret ::dns::ReadULong (type datavar , type index , type usedvar) {
01172 upvar $datavar data
01173 upvar $usedvar used
01174 set r {}
01175 set used 0
01176 if {[binary scan $data @${index}cccc b1 b2 b3 b4]} {
01177 set used 4
01178 # This gets us an unsigned value.
01179 set r [expr {($b4 & 0xFF) + (($b3 & 0xFF) << 8)
01180 + (($b2 & 0xFF) << 16) + ($b1 << 24)}]
01181 }
01182 return $r
01183 }
01184
01185 ret ::dns::ReadUShort (type datavar , type index , type usedvar) {
01186 upvar $datavar data
01187 upvar $usedvar used
01188 set r {}
01189 set used 0
01190 if {[binary scan [string range $data $index end] cc b1 b2]} {
01191 set used 2
01192 # This gets us an unsigned value.
01193 set r [expr {(($b2 & 0xff) + (($b1 & 0xff) << 8)) & 0xffff}]
01194 }
01195 return $r
01196 }
01197
01198
01199
01200
01201
01202 ret ::dns::ReadName (type datavar , type index , type usedvar) {
01203 upvar $datavar data
01204 upvar $usedvar used
01205 set startindex $index
01206
01207 set r {}
01208 set len 1
01209 set max [string length $data]
01210
01211 while {$len != 0 && $index < $max} {
01212 # Read the label length (and preread the pointer offset)
01213 binary scan [string range $data $index end] cc len lenb
01214 set len [expr {$len & 0xFF}]
01215 incr index
01216
01217 if {$len != 0} {
01218 if {[expr {$len & 0xc0}]} {
01219 binary scan [binary format cc [expr {$len & 0x3f}] [expr {$lenb & 0xff}]] S offset
01220 incr index
01221 lappend r [ReadName data $offset junk]
01222 set len 0
01223 } else {
01224 lappend r [string range $data $index [expr {$index + $len - 1}]]
01225 incr index $len
01226 }
01227 }
01228 }
01229 set used [expr {$index - $startindex}]
01230 return [join $r .]
01231 }
01232
01233 ret ::dns::ReadString (type datavar , type index , type length) {
01234 upvar $datavar data
01235 set startindex $index
01236
01237 set r {}
01238 set max [expr {$index + $length}]
01239
01240 while {$index < $max} {
01241 binary scan [string range $data $index end] c len
01242 set len [expr {$len & 0xFF}]
01243 incr index
01244
01245 if {$len != 0} {
01246 append r [string range $data $index [expr {$index + $len - 1}]]
01247 incr index $len
01248 }
01249 }
01250 return $r
01251 }
01252
01253
01254
01255
01256
01257
01258
01259
01260
01261 switch -exact $::tcl_platform(platform) {
01262 windows {
01263 ret ::dns::nameservers () {
01264 package require registry
01265 set base {HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services}
01266 set param "$base\\Tcpip\\Parameters"
01267 set interfaces "$param\\Interfaces"
01268 set nameservers {}
01269 if {[string equal $::tcl_platform(os) "Windows NT"]} {
01270 AppendRegistryValue $param NameServer nameservers
01271 AppendRegistryValue $param DhcpNameServer nameservers
01272 foreach i [registry keys $interfaces] {
01273 AppendRegistryValue "$interfaces\\$i" NameServer nameservers
01274 AppendRegistryValue "$interfaces\\$i" DhcpNameServer nameservers
01275 }
01276 } else {
01277 set param "$base\\VxD\\MSTCP"
01278 AppendRegistryValue $param NameServer nameservers
01279 }
01280 return $nameservers
01281 }
01282 ret ::dns::AppendRegistryValue (type key , type val , type listName) {
01283 upvar $listName lst
01284 if {![catch {registry get $key $val} v]} {
01285 foreach ns [split $v ", "] {
01286 if {[lsearch -exact $lst $ns] == -1} {
01287 lappend lst $ns
01288 }
01289 }
01290 }
01291 }
01292 }
01293 unix {
01294 ret ::dns::nameservers () {
01295 set nameservers {}
01296 if {[file readable /etc/resolv.conf]} {
01297 set f [open /etc/resolv.conf r]
01298 while {![eof $f]} {
01299 gets $f line
01300 if {[regexp {^\s*nameserver\s+(.*)$} $line -> ns]} {
01301 lappend nameservers $ns
01302 }
01303 }
01304 close $f
01305 }
01306 if {[llength $nameservers] < 1} {
01307 lappend nameservers 127.0.0.1
01308 }
01309 return $nameservers
01310 }
01311 }
01312 default {
01313 ret ::dns::nameservers () {
01314 return -code error "command not supported for this platform."
01315 }
01316 }
01317 }
01318
01319
01320
01321
01322
01323
01324
01325
01326
01327
01328 catch {
01329 uri::register {dns} {
01330 escape = [ [namespace = parent [namespace current]]::basic::escape]
01331 host = [ [namespace = parent [namespace current]]::basic::host]
01332 hostOrPort = [ [namespace = parent [namespace current]]::basic::hostOrPort]
01333
01334 class = [string map {* \\\\*} \
01335 "class=([join [array names ::dns::classes] {|}])"]
01336 type = [string map {* \\\\*} \
01337 "type=([join [array names ::dns::types] {|}])"]
01338 classOrType = "(?:${class}|${type})"
01339 classOrTypeSpec = "(?:${class}|${type})(?:;(?:${class}|${type}))?"
01340
01341 query = "${host}(${classOrTypeSpec})?"
01342 variable schemepart "(
01343 variable url "dns:$schemepart"
01344 }
01345 }
01346
01347 namespace ::uri {} ;
01348
01349 ret ::uri::SplitDns (type uri) {
01350 upvar \#0 [namespace current]::dns::schemepart schemepart
01351 upvar \#0 [namespace current]::dns::class classOrType
01352 upvar \#0 [namespace current]::dns::class classRE
01353 upvar \#0 [namespace current]::dns::type typeRE
01354 upvar \#0 [namespace current]::dns::classOrTypeSpec classOrTypeSpec
01355
01356 array set parts {nameserver {} query {} class {} type {} port {}}
01357
01358 # validate the uri
01359 if {[regexp -- $dns::schemepart $uri r] == 1} {
01360
01361 # deal with the optional class and type specifiers
01362 if {[regexp -indices -- "${classOrTypeSpec}$" $uri range]} {
01363 set spec [string range $uri [lindex $range 0] [lindex $range 1]]
01364 set uri [string range $uri 0 [expr {[lindex $range 0] - 2}]]
01365
01366 if {[regexp -- "$classRE" $spec -> class]} {
01367 set parts(class) $class
01368 }
01369 if {[regexp -- "$typeRE" $spec -> type]} {
01370 set parts(type) $type
01371 }
01372 }
01373
01374 # Handle the nameserver specification
01375 if {[string match "//*" $uri]} {
01376 set uri [string range $uri 2 end]
01377 array set tmp [GetHostPort uri]
01378 set parts(nameserver) $tmp(host)
01379 set parts(port) $tmp(port)
01380 }
01381
01382 # what's left is the query domain name.
01383 set parts(query) [string trimleft $uri /]
01384 }
01385
01386 return [array get parts]
01387 }
01388
01389 ret ::uri::JoinDns (type args) {
01390 array set parts {nameserver {} port {} query {} class {} type {}}
01391 array set parts $args
01392 set query [::uri::urn::quote $parts(query)]
01393 if {$parts(type) != {}} {
01394 append query "?type=$parts(type)"
01395 }
01396 if {$parts(class) != {}} {
01397 if {$parts(type) == {}} {
01398 append query "?class=$parts(class)"
01399 } else {
01400 append query ";class=$parts(class)"
01401 }
01402 }
01403 if {$parts(nameserver) != {}} {
01404 set ns "$parts(nameserver)"
01405 if {$parts(port) != {}} {
01406 append ns ":$parts(port)"
01407 }
01408 set query "//${ns}/${query}"
01409 }
01410 return "dns:$query"
01411 }
01412
01413
01414
01415 catch {dns::configure -nameserver [lindex [dns::nameservers] 0]}
01416
01417 package provide dns $dns::version
01418
01419
01420
01421
01422
01423