First stab at DNS client, based on the code from PLT.

Note it does at least one thing wrong (besides what's noted in PLT's
documentation):

> (dns-get-address "macon" "ftp.uni-tuebingen.de")
"8.115.111.102"

(The original version also does this.  I assume it has something to do
with recursive queries.)
This commit is contained in:
sperber 2002-01-25 16:50:23 +00:00
parent 49de023eb7
commit ebbbe7192f
2 changed files with 364 additions and 0 deletions

349
dns.scm Normal file
View File

@ -0,0 +1,349 @@
;; open string-table big-util(?) ascii format signals random
(define *nul* (ascii->char 0))
(define *on* (ascii->char 1))
(define types
'((a 1)
(ns 2)
(md 3)
(mf 4)
(cname 5)
(soa 6)
(mb 7)
(mg 8)
(mr 9)
(null 10)
(wks 11)
(ptr 12)
(hinfo 13)
(minfo 14)
(mx 15)
(txt 16)))
(define classes
'((in 1)
(cs 2)
(ch 3)
(hs 4)))
(define (cossa i l)
(cond
((null? l) #f)
((equal? (cadar l) i)
(car l))
(else (cossa i (cdr l)))))
(define (number->octet-pair n)
(list (ascii->char (arithmetic-shift n -8))
(ascii->char (modulo n 256))))
(define (octet-pair->number a b)
(+ (arithmetic-shift (char->ascii a) 8)
(char->ascii b)))
(define (octet-quad->number a b c d)
(+ (arithmetic-shift (char->ascii a) 24)
(arithmetic-shift (char->ascii b) 16)
(arithmetic-shift (char->ascii c) 8)
(char->ascii d)))
(define (name->octets s)
(define (encode-portion s)
(cons
(ascii->char (string-length s))
(string->list s)))
(let loop ((s s))
(cond
((string-match "^([^.]*)\\.(.*)" s)
=> (lambda (match)
(append
(encode-portion (match:substring match 1))
(loop (match:substring match 2)))))
(else
(append
(encode-portion s)
(list *nul*))))))
(define (make-std-query-header id question-count)
(append
(number->octet-pair id)
(list *on* *nul*) ; Opcode & flags (recusive flag set)
(number->octet-pair question-count)
(number->octet-pair 0)
(number->octet-pair 0)
(number->octet-pair 0)))
(define (make-query id name type class)
(append
(make-std-query-header id 1)
(name->octets name)
(number->octet-pair (cadr (assoc type types)))
(number->octet-pair (cadr (assoc class classes)))))
(define (add-size-tag m)
(append (number->octet-pair (length m)) m))
(define (rr-data rr)
(cadddr (cdr rr)))
(define (rr-type rr)
(cadr rr))
(define (rr-name rr)
(car rr))
(define (parse-name start reply)
(let ((v (char->ascii (car start))))
(cond
((zero? v)
;; End of name
(values #f (cdr start)))
((zero? (bitwise-and #xc0 v))
;; Normal label
(let loop ((len v)
(start (cdr start))
(accum '()))
(cond
((zero? len)
(call-with-values
(lambda () (parse-name start reply))
(lambda (s start)
(let ((s0 (list->string (reverse! accum))))
(values (if s
(string-append s0 "." s)
s0)
start)))))
(else (loop (- len 1)
(cdr start)
(cons (car start) accum))))))
(else
;; Compression offset
(let ((offset (+ (arithmetic-shift (bitwise-and #x3f v) 8)
(char->ascii (cadr start)))))
(call-with-values
(lambda () (parse-name (list-tail reply offset) reply))
(lambda (s ignore-start)
(values s (cddr start)))))))))
(define (parse-rr start reply)
(call-with-values
(lambda () (parse-name start reply))
(lambda (name start)
(let ((type (car (cossa (octet-pair->number (car start) (cadr start)) types)))
(start (cddr start)))
(let ((class (car (cossa (octet-pair->number (car start) (cadr start)) classes)))
(start (cddr start)))
(let ((ttl (octet-quad->number (car start) (cadr start)
(caddr start) (cadddr start)))
(start (cddddr start)))
(let ((len (octet-pair->number (car start) (cadr start)))
(start (cddr start)))
; Extract next len bytes for data:
(let loop ((len len)
(start start)
(accum '()))
(if (zero? len)
(values (list name type class ttl (reverse! accum))
start)
(loop (- len 1)
(cdr start)
(cons (car start) accum)))))))))))
(define (parse-ques start reply)
(call-with-values
(lambda () (parse-name start reply))
(lambda (name start)
(let ((type (car (cossa (octet-pair->number (car start) (cadr start)) types)))
(start (cddr start)))
(let ((class (car (cossa (octet-pair->number (car start) (cadr start)) classes)))
(start (cddr start)))
(values (list name type class) start))))))
(define (parse-n parse start reply n)
(let loop ((n n) (start start) (accum '()))
(if (zero? n)
(values (reverse! accum) start)
(call-with-values
(lambda () (parse start reply))
(lambda (rr start)
(loop (- n 1) start (cons rr accum)))))))
(define random
(let ((crank (make-random (modulo (time) (- (expt 2 27) 1)))))
(lambda (limit)
(quotient (* (modulo (crank) 314159265)
limit)
314159265))))
(define (dns-query nameserver addr type class)
(if (not (assoc type types))
(error "dns-query: invalid DNS query type ~a" type))
(if (not (assoc class classes))
(error "dns-query: invalid DNS query class ~a" class))
(let* ((query (make-query (random 256) addr type class))
(reply
(let ((socket (socket-connect protocol-family/internet
socket-type/stream
nameserver 53)))
(let ((r (socket:inport socket))
(w (socket:outport socket)))
(dynamic-wind
(lambda ()
'fick-dich-ins-knie)
(lambda ()
(display (list->string (add-size-tag query)) w)
(force-output w)
(let ((a (read-char r))
(b (read-char r)))
(let ((len (octet-pair->number a b)))
(let ((s (read-string len r)))
(if (not (= len (string-length s)))
(error "dns-query: unexpected EOF from server"))
(string->list s)))))
(lambda ()
(close-socket socket)))))))
;; First two bytes must match sent message id:
(if (not (and (char=? (car reply) (car query))
(char=? (cadr reply) (cadr query))))
(error "dns-query: bad reply id from server"))
(let ((v0 (caddr reply))
(v1 (cadddr reply)))
;; Check for error code:
(let ((rcode (bitwise-and #xf (char->ascii v1))))
(if (not (zero? rcode))
(error "dns-query: error from server: ~a"
(case rcode
((1) "format error")
((2) "server failure")
((3) "name error")
((4) "not implemented")
((5) "refused")))))
(let ((qd-count (octet-pair->number (list-ref reply 4) (list-ref reply 5)))
(an-count (octet-pair->number (list-ref reply 6) (list-ref reply 7)))
(ns-count (octet-pair->number (list-ref reply 8) (list-ref reply 9)))
(ar-count (octet-pair->number (list-ref reply 10) (list-ref reply 11))))
(let ((start (list-tail reply 12)))
(call-with-values
(lambda () (parse-n parse-ques start reply qd-count))
(lambda (qds start)
(call-with-values
(lambda () (parse-n parse-rr start reply an-count))
(lambda (ans start)
(call-with-values
(lambda () (parse-n parse-rr start reply ns-count))
(lambda (nss start)
(call-with-values
(lambda () (parse-n parse-rr start reply ar-count))
(lambda (ars start)
(if (not (null? start))
(error "dns-query: error parsing server reply"))
(values (positive? (bitwise-and #x4 (char->ascii v0)))
qds ans nss ars reply))))))))))))))
(define cache (make-string-table))
(define (dns-query/cache nameserver addr type class)
(let ((key (format #f "~a;~a;~a;~a" nameserver addr type class)))
(cond
((table-ref cache key)
=> (lambda (v)
(apply values v)))
(else
(call-with-values
(lambda () (dns-query nameserver addr type class))
(lambda (auth? qds ans nss ars reply)
(table-set! cache key (list auth? qds ans nss ars reply))
(values auth? qds ans nss ars reply)))))))
(define (ip->string s)
(format #f
"~a.~a.~a.~a"
(char->ascii (list-ref s 0))
(char->ascii (list-ref s 1))
(char->ascii (list-ref s 2))
(char->ascii (list-ref s 3))))
(define (try-forwarding k nameserver)
(let loop ((nameserver nameserver)
(tried (list nameserver)))
;; Normally the recursion is done for us, but it's technically optional
(call-with-values
(lambda () (k nameserver))
(lambda (v ars auth?)
(or v
(and (not auth?)
(let* ((ns (any
(lambda (ar)
(and (eq? (rr-type ar) 'a)
(ip->string (rr-data ar))))
ars)))
(and ns
(not (member ns tried))
(loop ns (cons ns tried))))))))))
(define (dns-get-address nameserver addr)
(or (try-forwarding
(lambda (nameserver)
(call-with-values
(lambda () (dns-query/cache nameserver addr 'a 'in))
(lambda (auth? qds ans nss ars reply)
(values (and (positive? (length ans))
(let ((s (rr-data (car ans))))
(ip->string s)))
ars auth?))))
nameserver)
(error "dns-get-address: bad address ~a" addr)))
(define (dns-get-mail-exchanger nameserver addr)
(or (try-forwarding
(lambda (nameserver)
(call-with-values
(lambda () (dns-query/cache nameserver addr 'mx 'in))
(lambda (auth? qds ans nss ars reply)
(values (let loop ((ans ans)
(best-pref 99999) ; this is enough
(exchanger #f))
(cond
((null? ans)
(or exchanger
;; Does 'soa mean that the input address is fine?
(and (any? (lambda (ns)
(eq? (rr-type ns) 'soa))
nss)
addr)))
(else
(let ((d (rr-data (car ans))))
(let ((pref (octet-pair->number (car d) (cadr d))))
(if (< pref best-pref)
(call-with-values
(lambda () (parse-name (cddr d) reply))
(lambda (name start)
(loop (cdr ans) pref name)))
(loop (cdr ans) best-pref exchanger)))))))
ars auth?))))
nameserver)
(error "dns-get-mail-exchanger: bad address")))
(define (dns-find-nameserver)
(with-input-from-file "/etc/resolv.conf"
(lambda ()
(let loop ()
(let ((l (read-line)))
(cond
((eof-object? l)
#f)
((string-match "nameserver[ ]+([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)"
l)
=> (lambda (match)
(match:substring match 1)))
(else
(loop))))))))

View File

@ -699,3 +699,18 @@
(define (eval-safely exp)
(ignore-errors (lambda () (eval exp (new-safe-package)))))))
(define-interface dns-interface
(export dns-get-address
dns-get-mail-exchanger
dns-find-nameserver))
(define-structure dns dns-interface
(open scheme
scsh
big-util
tables
ascii
formats
signals
random)
(files dns))