1374 lines
45 KiB
Scheme
1374 lines
45 KiB
Scheme
;
|
|
; dns.scm
|
|
;
|
|
; Implementation of the RFC1035
|
|
|
|
;;; This file is part of the Scheme Untergrund Networking package.
|
|
|
|
;;; Copyright (c) 2002 by Marcus Crestani.
|
|
;;; For copyright information, see the file COPYING which comes with
|
|
;;; the distribution.
|
|
|
|
; domain names - implementation and specification
|
|
; based on the PLT-implementation.
|
|
;
|
|
;
|
|
; TODO:
|
|
; - test, test, test
|
|
; - types from newer RFCs (41, unknown)
|
|
; - more documentation
|
|
;
|
|
; ---
|
|
; sample usage & documentation:
|
|
;
|
|
; <ip-address32> is a 32bit integer internet->address, shortly address32.
|
|
; <ip-string> is a string in standard dot notation "xxx.xxx.xxx.xxx".
|
|
; <name> is a string
|
|
;
|
|
; <nameserver> can either be a domainname, an ip-string or an ip-address32.
|
|
; if it is a domainname, its ip is looked up on a nameserver listed in
|
|
; /etc/resolv.conf.
|
|
;
|
|
; (dns-find-nameserver) --> <ip-address32>
|
|
; this parses the /etc/resolv.conf file and returns the first found
|
|
; nameserver in address32 format.
|
|
;
|
|
;
|
|
;
|
|
; (dns-lookup-name <name> [nameserver]) --> <ip-address32>
|
|
; (dns-lookup-ip <ip-string | ip-address32> [nameserver]) --> <name>
|
|
; (dns-lookup-nameserver <name> [nameserver])
|
|
; --> <list of ip-address32s of authoritative nameservers>
|
|
; (dns-lookup-mail-exchanger <name> [nameserver])
|
|
; --> <list of names of mail-exchangers>
|
|
;
|
|
; dns-lookup-name, dns-lookup-ip, dns-lookup-nameserver and
|
|
; dns-lookup-mail-exchanger are "simple lookup functions",
|
|
; they return the wanted information or #f.
|
|
; dns-lookup-ip can either be given an ip-string or an ip-address32.
|
|
;
|
|
; concurrent dns lookup:
|
|
; if a list of nameservers is given to the optional <nameserver> argument,
|
|
; a concurrent lookup to all nameservers in this list is started.
|
|
; The nameservers in this list could either be ip-strings or ip-address32s.
|
|
; example: (dns-lookup-name "www.uni-tuebingen.de" (dns-find-nameserver-list))
|
|
; starts an concurrent lookup which contacts all nameservers in
|
|
; /etc/resolv.conf.
|
|
;
|
|
;
|
|
; (dns-lookup <name | ip-string | ip-address32> <type> [nameserver])
|
|
; --> <dns-message>
|
|
; (show-dns-message <dns-message) --> the whole message, human readable
|
|
;
|
|
; a <dns-message> is a record, with several entries, which holds the whole
|
|
; query/response dialog. the simplest way to get detailed information about
|
|
; the record structure is to view the result of show-dns-message.
|
|
;
|
|
; dns-lookup returns much more information than the simple lookup functions,
|
|
; only useful in very special cases.
|
|
;
|
|
;
|
|
; some lookups return a hostname (e.g. mx).
|
|
; many applications need instead of a hostname a ip address.
|
|
; force-ip and force-ip-list guarantee that a ip address is
|
|
; returned.
|
|
;
|
|
; (force-ip <name>) --> <ip>
|
|
; (force-ip-list <list of names>) --> <list of ips>
|
|
;
|
|
;
|
|
; useful converters:
|
|
;
|
|
; (address32->ip-string <ip-address32>) -> <ip-string>
|
|
; (ip-string->address32 <ip-string>) -> <ip-address32>
|
|
|
|
|
|
|
|
|
|
;;; should debug-msgs be printed out?
|
|
(define *debug* #f)
|
|
|
|
|
|
;; --- error conditions
|
|
(define-condition-type 'invalid-type '())
|
|
(define invalid-type? (condition-predicate 'invalid-type))
|
|
|
|
(define-condition-type 'invalid-class '())
|
|
(define invalid-class? (condition-predicate 'invalid-class))
|
|
|
|
(define-condition-type 'parse-error '())
|
|
(define parse-error? (condition-predicate 'parse))
|
|
|
|
(define-condition-type 'unexpected-eof-from-server '())
|
|
(define unexpected-eof-from-server? (condition-predicate 'unexpected-eof-from-server))
|
|
|
|
(define-condition-type 'bad-address '())
|
|
(define bad-address? (condition-predicate 'bad-address))
|
|
|
|
(define-condition-type 'no-nameservers '())
|
|
(define no-nameservers? (condition-predicate 'no-nameservers))
|
|
|
|
(define-condition-type 'not-a-hostname '())
|
|
(define not-a-hostname? (condition-predicate 'not-a-hostname))
|
|
|
|
(define-condition-type 'not-a-ip '())
|
|
(define not-a-ip? (condition-predicate 'not-a-ip))
|
|
|
|
|
|
(define-condition-type 'dns-format-error '())
|
|
(define dns-format-error? (condition-predicate 'dns-format-error))
|
|
|
|
(define-condition-type 'dns-server-failure '())
|
|
(define dns-server-failure? (condition-predicate 'dns-server-failure))
|
|
|
|
(define-condition-type 'dns-name-error '())
|
|
(define dns-name-error? (condition-predicate 'dns-name-error))
|
|
|
|
(define-condition-type 'dns-not-implemented '())
|
|
(define dns-not-implemented? (condition-predicate 'dns-not-implemented))
|
|
|
|
(define-condition-type 'dns-refused '())
|
|
(define dns-refused? (condition-predicate 'dns-refused))
|
|
|
|
|
|
(define-condition-type 'dns-error '(dns-format-error
|
|
dns-server-failure
|
|
dns-name-error
|
|
dns-not-implemented
|
|
dns-refused))
|
|
|
|
(define dns-error? (condition-predicate 'dns-error))
|
|
|
|
|
|
;; called by the error-handlers, prints out error descriptions
|
|
(define (dns-error-messages condition more)
|
|
(display "dns-error: ")
|
|
(cond
|
|
((invalid-type? condition)
|
|
(display "make-octet-question: invalid DNS query type\n"))
|
|
((invalid-class? condition)
|
|
(display "make-octet-question: invalid DNS query class\n"))
|
|
((parse-error? condition)
|
|
(display "parse: error parsing server message\n"))
|
|
((unexpected-eof-from-server? condition)
|
|
(display "send-receive-message: unexpected EOF from server\n"))
|
|
((bad-address? condition)
|
|
(display "dns-get-information: bad address (in combination with query type)\n"))
|
|
((no-nameservers? condition)
|
|
(display "dns-find-nameserver: no nameservers found in /etc/resolv.conf\n"))
|
|
((not-a-hostname? condition)
|
|
(display "no hostname given\n"))
|
|
((not-a-ip? condition)
|
|
(display "no ip given\n"))
|
|
((dns-format-error? condition)
|
|
(display "error from server: (1) format error\n"))
|
|
((dns-server-failure? condition)
|
|
(display "error from server: (2) server failure\n"))
|
|
((dns-name-error? condition)
|
|
(display "error from server: (3) name error\n"))
|
|
((dns-not-implemented? condition)
|
|
(display "error from server: (4) not implemented\n"))
|
|
((dns-refused? condition)
|
|
(display "error from server: (5) refused\n"))
|
|
(else (more))))
|
|
|
|
|
|
;;; -- globals and types
|
|
;; off
|
|
(define *nul* (ascii->char 0))
|
|
|
|
;; on
|
|
(define *on* (ascii->char 1))
|
|
|
|
;; message types
|
|
(define types
|
|
'((unknown 0); types, which are not yet implemented
|
|
(a 1) ; a host address
|
|
(ns 2) ; an authoritative name server
|
|
(md 3) ; (obsolete)
|
|
(mf 4) ; (obsolete)
|
|
(cname 5) ; the canonical name for an alias
|
|
(soa 6) ; marks the start of a zone of authority
|
|
(mb 7) ; (experimental)
|
|
(mg 8) ; (experimental)
|
|
(mr 9) ; (experimental)
|
|
(null 10) ; (experimental)
|
|
(wks 11) ; a well known service description
|
|
(ptr 12) ; a domain name pointer
|
|
(hinfo 13) ; host information
|
|
(minfo 14) ; (experimental)
|
|
(mx 15) ; mail exchange
|
|
(txt 16))) ; text strings
|
|
|
|
;; message classes
|
|
(define classes
|
|
'((in 1) ; the Internet
|
|
(cs 2) ; (obsolete)
|
|
(ch 3) ; the CHAOS class
|
|
(hs 4))) ; Hesoid
|
|
|
|
|
|
;;; -- useful stuff
|
|
|
|
;; assoc the other way round
|
|
(define (cossa i l)
|
|
(if *debug* (display "cossa\n"))
|
|
(cond
|
|
((null? l) 'unknown)
|
|
((equal? (cadar l) i)
|
|
(car l))
|
|
(else (cossa i (cdr l)))))
|
|
|
|
;; encodes numbers (16bit) to octets
|
|
(define (number->octet-pair n)
|
|
(if *debug* (display "number->octet-pair\n"))
|
|
(list (ascii->char (arithmetic-shift n -8))
|
|
(ascii->char (modulo n 256))))
|
|
|
|
;; decodes octets to numbers (16bit)
|
|
(define (octet-pair->number a b)
|
|
(if *debug* (display "octet-pair->number\n"))
|
|
(+ (arithmetic-shift (char->ascii a) 8)
|
|
(char->ascii b)))
|
|
|
|
;; encodes numbers (32bit) to octets, needed for ttl
|
|
(define (number->octet-quad n)
|
|
(if *debug* (display "number->octet-quad\n"))
|
|
(list (ascii->char (arithmetic-shift n -24))
|
|
(ascii->char (modulo (arithmetic-shift n -16) 256))
|
|
(ascii->char (modulo (arithmetic-shift n -8) 256))
|
|
(ascii->char (modulo n 256))))
|
|
|
|
;; decodes octets to numbers, needed for 32bit ttl
|
|
(define (octet-quad->number a b c d)
|
|
(if *debug* (display "octet-quad->number\n"))
|
|
(+ (arithmetic-shift (char->ascii a) 24)
|
|
(arithmetic-shift (char->ascii b) 16)
|
|
(arithmetic-shift (char->ascii c) 8)
|
|
(char->ascii d)))
|
|
|
|
;; encodes a domain-name string to octets
|
|
(define (name->octets s)
|
|
(define (encode-portion s)
|
|
(cons
|
|
(ascii->char (string-length s))
|
|
(string->list s)))
|
|
|
|
(if *debug* (display "name->octets\n"))
|
|
(let loop ((s s))
|
|
(cond
|
|
((regexp-search (posix-string->regexp "^([^.]*)\\.(.*)") s)
|
|
=> (lambda (match)
|
|
(append
|
|
(encode-portion (match:substring match 1))
|
|
(loop (match:substring match 2)))))
|
|
(else
|
|
(if (= 0 (string-length s))
|
|
(list *nul*)
|
|
(append
|
|
(encode-portion s)
|
|
(list *nul*)))))))
|
|
|
|
;; for tcp: message has to be tagged with its length
|
|
(define (add-size-tag m)
|
|
(if *debug* (display "add-size-tag\n"))
|
|
(append (number->octet-pair (length m)) m))
|
|
|
|
;; converts an octeted-ip to a 32bit integer internet-address
|
|
(define (octet-ip->address32 ip)
|
|
(if *debug* (display "octet-ip->address32\n"))
|
|
(+ (arithmetic-shift (char->ascii (list-ref ip 0)) 24)
|
|
(arithmetic-shift (char->ascii (list-ref ip 1)) 16)
|
|
(arithmetic-shift (char->ascii (list-ref ip 2)) 8)
|
|
(char->ascii (list-ref ip 3))))
|
|
|
|
;; converts a 32 bit integer internet-address to an octeted-ip
|
|
(define (address32->octet-ip ip)
|
|
(if *debug* (display "number->octet-quad\n"))
|
|
(list (arithmetic-shift ip -24)
|
|
(modulo (arithmetic-shift ip -16) 256)
|
|
(modulo (arithmetic-shift ip -8) 256)
|
|
(modulo ip 256)))
|
|
|
|
;; converts an ip-string to an 32bit integer internet-address
|
|
(define (ip-string->address32 ip)
|
|
(if *debug* (display "ip-string->address32\n"))
|
|
(octet-ip->address32 (string->octet-ip ip)))
|
|
|
|
;; converts an ip-string to an 32bit integer internet-address
|
|
(define (address32->ip-string ip)
|
|
; (if *debug* (display "address32->ip-string\n"))
|
|
(format #f
|
|
"~a.~a.~a.~a"
|
|
(arithmetic-shift ip -24)
|
|
(modulo (arithmetic-shift ip -16) 256)
|
|
(modulo (arithmetic-shift ip -8) 256)
|
|
(modulo ip 256)))
|
|
|
|
;; converts an octeted-ip to an human readable ip-string
|
|
(define (octet-ip->string s)
|
|
(if *debug* (display "octet-ip->string\n"))
|
|
(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))))
|
|
|
|
;; converts an ip-string to octets
|
|
(define (string->octet-ip s)
|
|
(if *debug* (display "string->octet-ip\n"))
|
|
(let loop ((s s)
|
|
(result '()))
|
|
(cond
|
|
((regexp-search (posix-string->regexp "^([^.]*)\\.(.*)") s)
|
|
=> (lambda (match)
|
|
(loop (match:substring match 2) (append result (list (ascii->char (string->number (match:substring match 1))))))))
|
|
(else
|
|
(append result (list (ascii->char (string->number s))))))))
|
|
|
|
;; calculates a "random" number, needed for message-ids
|
|
(define random
|
|
(let ((crank (make-random (modulo (time) (- (expt 2 27) 1)))))
|
|
(lambda (limit)
|
|
(quotient (* (modulo (crank) 314159265)
|
|
limit)
|
|
314159265))))
|
|
|
|
;; checks if a string is a ip
|
|
(define (ip-string? s)
|
|
(if *debug* (display "ip-string->in-addr\n"))
|
|
(let loop ((s s)
|
|
(count 0))
|
|
(cond
|
|
((regexp-search (posix-string->regexp "^([^.]*)\\.(.*)") s)
|
|
=> (lambda (match)
|
|
(let* ((portion (match:substring match 1))
|
|
(number (string->number portion)))
|
|
(if (and number (< number 256))
|
|
(loop (match:substring match 2) (+ count 1))
|
|
#f))))
|
|
(else
|
|
(let ((number (string->number s)))
|
|
(and number
|
|
(< number 256)
|
|
(= count 3)
|
|
#t))))))
|
|
|
|
;; checks if v is a address32
|
|
(define (address32? v)
|
|
(and (number? v)
|
|
(<= 0 v #xffffffff)))
|
|
|
|
;; returns a in-addr.arpa name-string or #f (needed to resolve hostname by ip)
|
|
(define (ip-string->in-addr s)
|
|
(if *debug* (display "ip-string->in-addr\n"))
|
|
(let loop ((s s)
|
|
(count 0)
|
|
(result ""))
|
|
(cond
|
|
((regexp-search (posix-string->regexp "^([^.]*)\\.(.*)") s)
|
|
=> (lambda (match)
|
|
(let* ((portion (match:substring match 1))
|
|
(number (string->number portion)))
|
|
(if (and number (< number 256))
|
|
(loop (match:substring match 2) (+ count 1) (string-append portion "." result))
|
|
#f))))
|
|
(else
|
|
(let ((number (string->number s)))
|
|
(and number
|
|
(< number 256)
|
|
(= count 3)
|
|
(string-append s "." result "in-addr.arpa")))))))
|
|
|
|
;; filters types in a list of rrs
|
|
(define (filter-type list type)
|
|
(if *debug* (display "ip-string->in-addr\n"))
|
|
(filter (lambda (rr)
|
|
(eq? (rr:type rr) type))
|
|
list))
|
|
|
|
;; sorts a mx-rr-list by preference. needed for dns-lookup-mail-exchanger.
|
|
(define (sort-by-preference mx-list)
|
|
(sort-list mx-list
|
|
(lambda (a b)
|
|
(< (rr-data-mx:preference (rr:data a)) (rr-data-mx:preference (rr:data b))))))
|
|
|
|
|
|
;; returns a IP if available (additonal type-a processing)
|
|
(define (force-ip name)
|
|
(let loop ((result (dns-lookup-name name)))
|
|
(if (ip-string? result)
|
|
result
|
|
(loop (dns-lookup-name result)))))
|
|
|
|
;; returns a list of IPs (additional type-a processing)
|
|
(define (force-ip-list names)
|
|
(map (lambda (elem) (force-ip elem)) names))
|
|
|
|
|
|
;;; -- message constructors: encode to octet-messages
|
|
|
|
;; makes an message header
|
|
(define (make-octet-header id qr opcode aa tc rd ra z rcode qdcount ancount nscount arcount)
|
|
(if *debug* (display "make-octet-header\n"))
|
|
(let* ((header-id (number->octet-pair id))
|
|
(header-flags (list
|
|
(ascii->char (+ (arithmetic-shift qr 7)
|
|
(arithmetic-shift opcode 3)
|
|
(arithmetic-shift aa 2)
|
|
(arithmetic-shift tc 1)
|
|
rd))
|
|
(ascii->char (+ (arithmetic-shift ra 7)
|
|
(arithmetic-shift z 4)
|
|
rcode))))
|
|
(header-qdcount (number->octet-pair qdcount))
|
|
(header-ancount (number->octet-pair ancount))
|
|
(header-nscount (number->octet-pair nscount))
|
|
(header-arcount (number->octet-pair arcount)))
|
|
(append header-id
|
|
header-flags
|
|
header-qdcount
|
|
header-ancount
|
|
header-nscount
|
|
header-arcount)))
|
|
|
|
|
|
;; a standard query header, usefull for most queries
|
|
(define (make-std-octet-query-header id question-count)
|
|
(if *debug* (display "make-std-octet-query-header\n"))
|
|
(let* ((qr 0) ; querytype: query 0, response 1
|
|
(opcode 0) ; opcode: query 0, iquery 1 (OBSOLETE), status 2
|
|
(aa 0) ; authorative answer (in answers only)
|
|
(tc 0) ; truncation (size matters only with UDP)
|
|
(rd 1) ; recursion desired: nameserver pursues the query recursivly (optional)
|
|
(ra 0) ; recursion available (in answers only)
|
|
(z 0) ; future use
|
|
(rcode 0) ; response code: error conditions (in answers only)
|
|
(qdcount question-count)
|
|
(ancount 0) ; answer count (in answers only)
|
|
(nscount 0) ; name server resources (in answers only)
|
|
(arcount 0)) ; additional records (in answers only)
|
|
|
|
(make-octet-header id qr opcode aa tc rd ra z rcode qdcount ancount nscount arcount)))
|
|
|
|
|
|
;; makes a question (name, type, class)
|
|
(define (make-octet-question name type class)
|
|
(if *debug* (display "make-octet-question\n"))
|
|
(if (not (assoc type types))
|
|
(signal 'invalid-type))
|
|
(if (not (assoc class classes))
|
|
(signal 'invalid-class))
|
|
|
|
(let* ((qname (name->octets name))
|
|
(qtype (number->octet-pair (cadr (assoc type types))))
|
|
(qclass (number->octet-pair (cadr (assoc class classes)))))
|
|
(append qname qtype qclass)))
|
|
|
|
|
|
;; makes a query-message (header and question only)
|
|
(define (make-octet-query-message id name type class)
|
|
(if *debug* (display "make-octet-query-message\n"))
|
|
(append
|
|
(make-std-octet-query-header id 1)
|
|
(make-octet-question name type class)))
|
|
|
|
|
|
;; makes a resource record for ans, nss, ars (name, type, class, ttl, data)
|
|
(define (make-octet-rr name type class ttl rdata)
|
|
(if *debug* (display "make-octet-rr\n"))
|
|
(let* ((name (name->octets name))
|
|
(type (number->octet-pair (cadr (assoc type types))))
|
|
(class (number->octet-pair (cadr (assoc class classes))))
|
|
(ttl (number->octet-quad ttl))
|
|
(rdlength (number->octet-pair (length rdata)))
|
|
(rdata rdata))
|
|
(append name type class ttl rdlength rdata)))
|
|
|
|
|
|
|
|
;;; -- parsed message records
|
|
|
|
;;; -- dns-message: complete data-structure of an dns-lookup
|
|
(define-record dns-message
|
|
query
|
|
reply
|
|
cache?
|
|
protocol
|
|
tried-nameservers)
|
|
|
|
;; message
|
|
(define-record message
|
|
header
|
|
questions
|
|
answers
|
|
nameservers
|
|
additionals
|
|
source)
|
|
|
|
;; header
|
|
(define-record header
|
|
id
|
|
flags
|
|
qdc
|
|
anc
|
|
nsc
|
|
arc)
|
|
|
|
;; flags
|
|
(define-record flags
|
|
querytype
|
|
opcode
|
|
auth
|
|
trunc
|
|
recursiondesired
|
|
recursionavailable
|
|
z
|
|
rcode)
|
|
|
|
;; question
|
|
(define-record question
|
|
name
|
|
type
|
|
class)
|
|
|
|
;; rr
|
|
(define-record rr
|
|
name
|
|
type
|
|
class
|
|
ttl
|
|
data)
|
|
|
|
;; cache
|
|
(define-record cache
|
|
answer
|
|
ttl
|
|
time)
|
|
|
|
;;; -- message parser
|
|
|
|
;; parses a domain-name in an message. returns the name and the rest of the message.
|
|
(define (parse-name start message)
|
|
(if *debug* (display "parse-name\n"))
|
|
(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 message))
|
|
(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 message offset) message))
|
|
(lambda (s ignore-start)
|
|
(values s (cddr start)))))))))
|
|
|
|
;; parses a question in a message. returns the question and the rest of the message.
|
|
(define (parse-question start message)
|
|
(if *debug* (display "parse-question\n"))
|
|
(call-with-values
|
|
(lambda () (parse-name start message))
|
|
(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 (make-question name type class) start))))))
|
|
|
|
;; parses a resourcerecord in a message. returns the rr and the rest of the message.
|
|
(define (parse-rr start message)
|
|
(if *debug* (display "parse-rr\n"))
|
|
(call-with-values
|
|
(lambda () (parse-name start message))
|
|
(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 of data:
|
|
(let loop ((len len)
|
|
(start start)
|
|
(accum '()))
|
|
(if (zero? len)
|
|
(values (make-rr name type class ttl (parse-rr-data type class (reverse! accum) message)) start)
|
|
(loop (- len 1)
|
|
(cdr start)
|
|
(cons (car start) accum)))))))))))
|
|
|
|
;;; -- rr-data-type records
|
|
|
|
(define-record rr-data-a
|
|
ip)
|
|
|
|
(define-record rr-data-ns
|
|
name)
|
|
|
|
(define-record rr-data-cname
|
|
name)
|
|
|
|
;; ###
|
|
;; hinfo not correctly implemented, trying to find examples
|
|
(define-record rr-data-hinfo
|
|
data)
|
|
|
|
(define-record rr-data-mx
|
|
preference
|
|
exchanger)
|
|
|
|
(define-record rr-data-ptr
|
|
name)
|
|
|
|
(define-record rr-data-soa
|
|
mname
|
|
rname
|
|
serial
|
|
refresh
|
|
retry
|
|
expire
|
|
minimum)
|
|
|
|
;; ### same as hinfo
|
|
(define-record rr-data-txt
|
|
text)
|
|
|
|
;; ### same as hinfo and txt
|
|
(define-record rr-data-wks
|
|
data)
|
|
|
|
;;
|
|
|
|
(define (parse-rr-data type class data message)
|
|
(if *debug* (display "parse-rr-data\n"))
|
|
(cond
|
|
((eq? type 'a)
|
|
(make-rr-data-a (octet-ip->address32 data)))
|
|
|
|
((eq? type 'ns)
|
|
(make-rr-data-ns (call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type 'cname)
|
|
(make-rr-data-cname (call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type 'mx)
|
|
(make-rr-data-mx (octet-pair->number (car data) (cadr data))
|
|
(call-with-values
|
|
(lambda ()(parse-name (cddr data) message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type 'ptr)
|
|
(make-rr-data-ptr (call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type 'soa)
|
|
(call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (mname rest)
|
|
(call-with-values
|
|
(lambda () (parse-name rest message))
|
|
(lambda (rname rest)
|
|
(let ((serial (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
|
|
(rest (cddddr rest)))
|
|
(let ((refresh (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
|
|
(rest (cddddr rest)))
|
|
(let ((retry (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
|
|
(rest (cddddr rest)))
|
|
(let ((expire (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
|
|
(rest (cddddr rest)))
|
|
(let ((minimum (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
|
|
(rest (cddddr rest)))
|
|
(make-rr-data-soa mname rname serial refresh retry expire minimum)))))))))))
|
|
|
|
((eq? type 'hinfo)
|
|
(make-rr-data-hinfo (list->string data)))
|
|
|
|
((eq? type 'txt)
|
|
(make-rr-data-txt (list->string data)))
|
|
|
|
((eq? type 'wks)
|
|
(make-rr-data-wks data))
|
|
|
|
(else (list data))))
|
|
|
|
;; parses n-times a message with parse. returns a list of parse-returns.
|
|
(define (parse-n parse start message n)
|
|
(if *debug* (display "parse-n\n"))
|
|
(let loop ((n n) (start start) (accum '()))
|
|
(if (zero? n)
|
|
(values (reverse! accum) start)
|
|
(call-with-values
|
|
(lambda () (parse start message))
|
|
(lambda (rr start)
|
|
(loop (- n 1) start (cons rr accum)))))))
|
|
|
|
;; parses a message-headers flags. returns the flags.
|
|
(define (parse-flags message)
|
|
(if *debug* (display "parse-flags\n"))
|
|
(let ((v0 (list-ref message 2))
|
|
(v1 (list-ref message 3)))
|
|
;; Check for error code:
|
|
(let ((rcode (bitwise-and #xf (char->ascii v1)))
|
|
(z (arithmetic-shift (bitwise-and 112 (char->ascii v1)) -4))
|
|
(ra (arithmetic-shift (bitwise-and 64 (char->ascii v1)) -7))
|
|
(rd (bitwise-and 1 (char->ascii v0)))
|
|
(tc (arithmetic-shift (bitwise-and 2 (char->ascii v0)) -1))
|
|
(aa (arithmetic-shift (bitwise-and 4 (char->ascii v0)) -2))
|
|
(opcode (arithmetic-shift (bitwise-and 120 (char->ascii v0)) -3))
|
|
(qr (arithmetic-shift (bitwise-and 128 (char->ascii v0)) -7)))
|
|
(make-flags qr opcode aa tc rd ra z rcode))))
|
|
|
|
|
|
;; parses a message-header. returns the header.
|
|
(define (parse-header message)
|
|
(if *debug* (display "parse-header\n"))
|
|
(let ((id (octet-pair->number (list-ref message 0) (list-ref message 1)))
|
|
(flags (parse-flags message))
|
|
(qd-count (octet-pair->number (list-ref message 4) (list-ref message 5)))
|
|
(an-count (octet-pair->number (list-ref message 6) (list-ref message 7)))
|
|
(ns-count (octet-pair->number (list-ref message 8) (list-ref message 9)))
|
|
(ar-count (octet-pair->number (list-ref message 10) (list-ref message 11))))
|
|
(make-header id flags qd-count an-count ns-count ar-count)))
|
|
|
|
|
|
;; parses a message. returns the parsed message.
|
|
(define (parse message)
|
|
(if *debug* (display "parse\n"))
|
|
(let* ((header (parse-header message))
|
|
(start (list-tail message 12)))
|
|
(call-with-values
|
|
(lambda () (parse-n parse-question start message (header:qdc header)))
|
|
(lambda (qds start)
|
|
(call-with-values
|
|
(lambda () (parse-n parse-rr start message (header:anc header)))
|
|
(lambda (ans start)
|
|
(call-with-values
|
|
(lambda () (parse-n parse-rr start message (header:nsc header)))
|
|
(lambda (nss start)
|
|
(call-with-values
|
|
(lambda () (parse-n parse-rr start message (header:arc header)))
|
|
(lambda (ars start)
|
|
(if (not (null? start))
|
|
(signal 'parse-error))
|
|
(make-message header qds ans nss ars message)))))))))))
|
|
|
|
|
|
|
|
;;; -- send, receive and validate message
|
|
|
|
;; checks if the received reply is valid. returns #t or error-msg.
|
|
(define (reply-acceptable? reply query)
|
|
(if *debug* (display "reply-acceptable?\n"))
|
|
;; Check correct id
|
|
(if (not (and (char=? (car reply) (car query))
|
|
(char=? (cadr reply) (cadr query))))
|
|
(display "send-receive-message: bad reply id from server"))
|
|
;; Check for error code:
|
|
(let ((rcode (flags:rcode (parse-flags reply))))
|
|
(if (not (zero? rcode))
|
|
(case rcode
|
|
((1) (signal 'dns-format-error))
|
|
((2) (signal 'dns-server-failure))
|
|
((3) (signal 'dns-name-error))
|
|
((4) (signal 'dns-not-implemented))
|
|
((5) (signal 'dns-refused))))))
|
|
|
|
;; #t if message is truncated (could happen via UDP)
|
|
(define (truncated? reply)
|
|
(let ((trunc (flags:trunc (parse-flags reply))))
|
|
trunc))
|
|
|
|
|
|
;; connects to nameserver and sends and receives messages. returns the reply.
|
|
;; here: via TCP
|
|
(define (send-receive-message-tcp nameserver question)
|
|
(if *debug* (display "send-receive-message\n"))
|
|
(let* ((query question)
|
|
(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 ()
|
|
'nothing-to-be-done-before)
|
|
(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)))
|
|
(signal 'unexpected-eof-from-server))
|
|
(string->list s)))))
|
|
(lambda ()
|
|
(close-socket socket)))))))
|
|
(reply-acceptable? reply query)
|
|
(parse reply)))
|
|
|
|
;; here: via UDP
|
|
(define (send-receive-message-udp nameserver question)
|
|
(if *debug* (display "send-receive-message\n"))
|
|
(let* ((query question)
|
|
(reply
|
|
(let ((socket (socket-connect protocol-family/internet
|
|
socket-type/datagram
|
|
nameserver 53)))
|
|
(let ((r (socket:inport socket))
|
|
(w (socket:outport socket)))
|
|
(dynamic-wind
|
|
(lambda ()
|
|
'nothing-to-be-done-before)
|
|
(lambda ()
|
|
(display (list->string query) w)
|
|
(force-output w)
|
|
(let ((s (read-string/partial 512 r))) ; 512 is the maximum udp-message size
|
|
(string->list s)))
|
|
(lambda ()
|
|
(close-socket socket)))))))
|
|
(reply-acceptable? reply query)
|
|
(if (truncated? reply)
|
|
(send-receive-message-tcp nameserver question)
|
|
(parse reply))))
|
|
|
|
|
|
;;; -- cache
|
|
|
|
;; creates the cache, an emoty string-table
|
|
(define cache (make-string-table))
|
|
|
|
;; resets the cache
|
|
(define (dns-clear-cache)
|
|
(set! cache (make-string-table)))
|
|
|
|
;; searches in a dns-msg for the shortest ttl. this is needed for cache-management.
|
|
(define (find-shortest-ttl dns-msg)
|
|
(if *debug* (display "find-shortest-ttl\n"))
|
|
(letrec ((minimum #f)
|
|
(find-shortest-ttl-1
|
|
(lambda (dns-msg)
|
|
(cond
|
|
((dns-message? dns-msg)
|
|
(find-shortest-ttl-1 (dns-message:reply dns-msg)))
|
|
((message? dns-msg)
|
|
(for-each (lambda (x) (find-shortest-ttl-1 x)) (message:answers dns-msg))
|
|
(for-each (lambda (x) (find-shortest-ttl-1 x)) (message:nameservers dns-msg))
|
|
(for-each (lambda (x) (find-shortest-ttl-1 x)) (message:additionals dns-msg))
|
|
minimum)
|
|
((rr? dns-msg)
|
|
(cond
|
|
((not minimum) (set! minimum (rr:ttl dns-msg)))
|
|
(else
|
|
(if (and (not minimum) (> minimum (rr:ttl dns-msg)))
|
|
(set! minimum (rr:ttl dns-msg))))))))))
|
|
(find-shortest-ttl-1 dns-msg)))
|
|
|
|
;; makes a dns-query. optional cache-check.
|
|
;; returns a dns-message with cache-flag and either cache-data or new received data.
|
|
(define (dns-query/cache question use-cache? protocol nameserver tried)
|
|
(if *debug* (display "dns-query/cache\n"))
|
|
(let ((send-receive-message
|
|
(cond
|
|
((eq? protocol 'tcp) send-receive-message-tcp)
|
|
((eq? protocol 'udp) send-receive-message-udp))))
|
|
(let ((dns-query
|
|
(lambda ()
|
|
(if *debug* (display "dns-query/cache:dns-query\n"))
|
|
;; returns new retrieved data
|
|
(make-dns-message (parse question) (send-receive-message nameserver question) #f protocol (reverse tried))))
|
|
(dns-query-with-cache
|
|
(lambda ()
|
|
(if *debug* (display "dns-query/cache:dns-query-with-cache\n"))
|
|
(let* ((qds (message:questions (parse question)))
|
|
;; cache-key relevant data
|
|
(name (question:name (car qds)))
|
|
(type (question:type (car qds)))
|
|
(class (question:class (car qds)))
|
|
(key (format #f "~a;~a;~a;~a" nameserver name type class))
|
|
(found-data (table-ref cache key)))
|
|
(cond
|
|
((and found-data
|
|
;; checks if cached-data is still valid
|
|
(< (time) (+ (cache:time found-data) (cache:ttl found-data))))
|
|
;; returns cached data
|
|
(make-dns-message (parse question) (cache:answer found-data) #t protocol (reverse tried)))
|
|
(else
|
|
(let ((reply-msg (send-receive-message nameserver question)))
|
|
(if *debug* (display "write to cache\n"))
|
|
(table-set! cache key (make-cache reply-msg (find-shortest-ttl reply-msg) (time)))
|
|
;; returns new retrieved data and updates cache
|
|
(make-dns-message (parse question) reply-msg #f protocol (reverse tried)))))))))
|
|
(if use-cache?
|
|
(dns-query-with-cache)
|
|
(dns-query)))))
|
|
|
|
;; dns and recursion
|
|
;; recursion means, if the demanded information is not available from the
|
|
;; nameserver, another nameserver (usualy an authority) has to be contacted.
|
|
;; normally the recursion is done for us by the nameserver istself, but
|
|
;; this feature is technically optional (RFC 1035).
|
|
;; dns-get-information implements the resovler-side recursion.
|
|
;; it returns a dns-message
|
|
(define (dns-get-information question use-cache? protocol nameserver check-answer)
|
|
(if *debug* (display "dns-get-information\n"))
|
|
(letrec ((tried (list nameserver))
|
|
;; with every (even unanswerd) requests authoritative nameservers are sent back
|
|
;; try-recursive tries to get information from these nameservers
|
|
(try-recursive
|
|
(lambda (auth? nss)
|
|
(if (or auth? (null? nss))
|
|
(signal 'bad-address)
|
|
(let* ((ns (and (eq? (rr:type (car nss)) 'a) (rr-data-a:ip (rr:data (car nss)))))
|
|
(dns-msg (if (and ns
|
|
(not (member ns tried))
|
|
(set! tried (cons ns tried)))
|
|
(dns-query/cache question use-cache? protocol ns tried)
|
|
(try-recursive auth? (cdr nss)))))
|
|
(check-success dns-msg)))))
|
|
;; checks if the answer is useful. returns a dns-message.
|
|
(check-success
|
|
(lambda (dns-msg)
|
|
(if *debug* (display "dns-get-information:check-success\n"))
|
|
(let ((useful-answer? (check-answer dns-msg)))
|
|
(if useful-answer?
|
|
dns-msg
|
|
(let ((auth? (not (zero? (flags:auth (header:flags (message:header (dns-message:reply dns-msg)))))))
|
|
;; other nameservers names are found in the nameserver-part,
|
|
;; but their ip-adresses are found in the additonal-rrs
|
|
(other-nameservers (filter (lambda (elem) (eq? (rr:type elem) 'a))
|
|
(message:additionals (dns-message:reply dns-msg)))))
|
|
(try-recursive auth? other-nameservers)))))))
|
|
(check-success (dns-query/cache question use-cache? protocol nameserver tried))))
|
|
|
|
|
|
|
|
;; parses the resolv.conf file and returns a list of found nameserver
|
|
(define (dns-find-nameserver-list)
|
|
(with-input-from-file "/etc/resolv.conf"
|
|
(lambda ()
|
|
(let loop ((ns '()))
|
|
(let ((l (read-line)))
|
|
(cond
|
|
((eof-object? l)
|
|
(if (null? ns)
|
|
(signal 'no-nameservers)
|
|
ns))
|
|
((regexp-search (posix-string->regexp "nameserver[ ]+([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)") l)
|
|
=> (lambda (match)
|
|
(loop (append ns (list (ip-string->address32 (match:substring match 1)))))))
|
|
(else
|
|
(loop ns))))))))
|
|
|
|
|
|
;; returns the first found nameserver
|
|
(define (dns-find-nameserver)
|
|
(let ((ns (dns-find-nameserver-list)))
|
|
(if (null? ns)
|
|
(signal 'no-nameservers)
|
|
(car ns))))
|
|
|
|
|
|
|
|
;; concurrent-lookup
|
|
;; starts a <lookup>-lookup to all nameservers in (dns-find-nameserver-list)
|
|
(define (concurrent-lookup lookup name nameservers)
|
|
(let* ((return 'no-value)
|
|
(lock (make-lock))
|
|
(queue (make-queue))
|
|
(nameserver-list (map (lambda (nameserver)
|
|
(if (address32? nameserver)
|
|
(address32->ip-string nameserver)
|
|
nameserver))
|
|
nameservers)))
|
|
|
|
(obtain-lock lock)
|
|
|
|
(spawn (lambda ()
|
|
(for-each (lambda (nameserver)
|
|
(spawn
|
|
(lambda ()
|
|
;(display "query sent to ")(display nameserver)(display " \n")
|
|
(let* ((result (apply lookup (list name nameserver))))
|
|
(enqueue! queue result)
|
|
;(display "received reply from ")(display nameserver)(display ": ")(display result)(newline)
|
|
(release-lock lock)))))
|
|
nameserver-list)))
|
|
|
|
(obtain-lock lock)
|
|
(let loop ((count (length nameserver-list)))
|
|
(if (not (queue-empty? queue))
|
|
(let ((result (dequeue! queue)))
|
|
(if (or result (= 1 (length nameserver-list)))
|
|
result
|
|
(loop (- count 1))))))))
|
|
|
|
;; checks the arguments of the simple lookup functions.
|
|
;; if a nameserver-name is given and not a nameserver-ip
|
|
;; (dns-lookup-name nameserver) is called.
|
|
(define (check-args args)
|
|
(if (null? args)
|
|
(address32->ip-string (dns-find-nameserver) )
|
|
(let ((nameserver (car args)))
|
|
(cond
|
|
((ip-string? nameserver) nameserver)
|
|
((address32? nameserver) (address32->ip-string nameserver))
|
|
(else (address32->ip-string (dns-lookup-name nameserver)))))))
|
|
|
|
|
|
;; dns-lookup with more options than dns-lookup-*
|
|
;; optional: nameserver could be passed to the function.
|
|
(define (dns-lookup name type . args)
|
|
(call-with-current-continuation
|
|
(lambda (exit)
|
|
(with-handler
|
|
(lambda (condition more)
|
|
(dns-error-messages condition more)
|
|
(exit #f))
|
|
(lambda ()
|
|
(let* ((ip-string (if (address32? name)
|
|
(ip-string->in-addr (address32->ip-string name))
|
|
(ip-string->in-addr name)))
|
|
(question (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(make-octet-query-message (random 256) ip-string type 'in)
|
|
(make-octet-query-message (random 256) name type 'in)))
|
|
(use-cache? #t)
|
|
(protocol 'udp)
|
|
(nameserver (check-args args))
|
|
(check-answer (lambda (dns-msg) #t))
|
|
(dns-msg (dns-get-information question use-cache? protocol nameserver check-answer))
|
|
(answers (message:answers (dns-message:reply dns-msg))))
|
|
(if (not (null? answers))
|
|
(for-each (lambda (x) (show-dns-message x)(newline)) answers)
|
|
(display "no answers received - but resolved information in other sections.\n"))
|
|
dns-msg))))))
|
|
|
|
|
|
;; returns a lookup-function with concurrent-flag
|
|
(define (make-lookup-function simple-lookup-function)
|
|
(lambda (name . args)
|
|
(if (null? args)
|
|
(simple-lookup-function name)
|
|
(if (list? (car args))
|
|
(concurrent-lookup simple-lookup-function name (car args))
|
|
(simple-lookup-function name (car args))))))
|
|
|
|
;; looks up a hostname, returns an ip.
|
|
;; (dns-lookup-name <name> [nameserver])
|
|
(define (dns-simple-lookup-name name . args)
|
|
(call-with-current-continuation
|
|
(lambda (exit)
|
|
(with-handler
|
|
(lambda (condition more)
|
|
(dns-error-messages condition more)
|
|
(exit #f))
|
|
(lambda ()
|
|
(let* ((ip-string (if (address32? name)
|
|
(ip-string->in-addr (address32->ip-string name))
|
|
(ip-string->in-addr name)))
|
|
(question (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(signal 'not-a-hostname)
|
|
(make-octet-query-message (random 256) name 'a 'in)))
|
|
(use-cache? #t)
|
|
(protocol 'udp)
|
|
(nameserver (check-args args))
|
|
(check-answer (lambda (dns-msg)
|
|
(let* ((reply (dns-message:reply dns-msg))
|
|
(answers (message:answers reply)))
|
|
(not (null? (filter-type answers 'a))))))
|
|
(dns-msg (dns-get-information question use-cache? protocol nameserver check-answer))
|
|
(answers (filter-type (message:answers (dns-message:reply dns-msg)) 'a)))
|
|
(rr-data-a:ip (rr:data (car answers)))))))))
|
|
|
|
(define dns-lookup-name (make-lookup-function dns-simple-lookup-name))
|
|
|
|
;; looks up an ip, returns a hostname
|
|
;; (dns-inverse-lookup <name> [nameserver])
|
|
(define (dns-simple-lookup-ip ip . args)
|
|
(call-with-current-continuation
|
|
(lambda (exit)
|
|
(with-handler
|
|
(lambda (condition more)
|
|
(dns-error-messages condition more)
|
|
(exit #f))
|
|
(lambda ()
|
|
(let* ((ip-string (if (address32? ip)
|
|
(ip-string->in-addr (address32->ip-string ip))
|
|
(ip-string->in-addr ip)))
|
|
(question (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(make-octet-query-message (random 256) ip-string 'ptr 'in)
|
|
(signal 'not-a-ip)))
|
|
(use-cache? #t)
|
|
(protocol 'udp)
|
|
(nameserver (check-args args))
|
|
(check-answer (lambda (dns-msg)
|
|
(let* ((reply (dns-message:reply dns-msg))
|
|
(answers (message:answers reply)))
|
|
(not (null? (filter-type answers 'ptr))))))
|
|
(dns-msg (dns-get-information question use-cache? protocol nameserver check-answer))
|
|
(answers (filter-type (message:answers (dns-message:reply dns-msg)) 'ptr)))
|
|
(rr-data-ptr:name (rr:data (car answers)))))))))
|
|
|
|
(define dns-lookup-ip (make-lookup-function dns-simple-lookup-ip))
|
|
|
|
(define dns-inverse-lookup dns-lookup-ip)
|
|
|
|
|
|
;; looks up an authoritative nameserver for a hostname
|
|
;; returns a list of nameservers
|
|
;; (dns-lookup-nameserver <name> [nameserver])
|
|
(define (dns-simple-lookup-nameserver name . args)
|
|
(call-with-current-continuation
|
|
(lambda (exit)
|
|
(with-handler
|
|
(lambda (condition more)
|
|
(dns-error-messages condition more)
|
|
(exit #f))
|
|
(lambda ()
|
|
(let* ((ip-string (if (address32? name)
|
|
(ip-string->in-addr (address32->ip-string name))
|
|
(ip-string->in-addr name)))
|
|
(question (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(signal 'not-a-hostname)
|
|
(make-octet-query-message (random 256) name 'ns 'in)))
|
|
(use-cache? #t)
|
|
(protocol 'udp)
|
|
(nameserver (check-args args))
|
|
(check-answer (lambda (dns-msg)
|
|
(let* ((reply (dns-message:reply dns-msg))
|
|
(answers (message:answers reply))
|
|
(nameservers (message:nameservers reply)))
|
|
(or (not (null? (filter-type nameservers 'soa)))
|
|
(not (null? (filter-type answers 'ns)))))))
|
|
(dns-msg (dns-get-information question use-cache? protocol nameserver check-answer))
|
|
(reply (dns-message:reply dns-msg))
|
|
(soa (filter-type (message:nameservers reply) 'soa))
|
|
(nss (filter-type (message:answers reply) 'ns))
|
|
(add (filter-type (message:additionals reply) 'a)))
|
|
(if (null? nss)
|
|
(list (dns-lookup-name (rr-data-soa:mname (rr:data (car soa)))))
|
|
(map (lambda (elem) (rr-data-a:ip (rr:data elem))) add))))))))
|
|
|
|
(define dns-lookup-nameserver (make-lookup-function dns-simple-lookup-nameserver))
|
|
|
|
|
|
;; looks up a mail-exchanger for a hostname.
|
|
;; returns a list of mail-exchanger, sorted by their preference
|
|
;; if there are no mx-records in the answer-section,
|
|
;; implementation based on RFC2821
|
|
;; (dns-lookup-mail-exchanger <name> [nameserver])
|
|
(define (dns-simple-lookup-mail-exchanger name . args)
|
|
(call-with-current-continuation
|
|
(lambda (exit)
|
|
(with-handler
|
|
(lambda (condition more)
|
|
(dns-error-messages condition more)
|
|
(exit #f))
|
|
(lambda ()
|
|
(let* ((ip-string (if (address32? name)
|
|
(ip-string->in-addr (address32->ip-string name))
|
|
(ip-string->in-addr name)))
|
|
(question (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(signal 'not-a-hostname)
|
|
(make-octet-query-message (random 256) name 'mx 'in)))
|
|
(use-cache? #t)
|
|
(protocol 'tcp)
|
|
(nameserver (check-args args))
|
|
(check-answer (lambda (dns-msg)
|
|
(let* ((reply (dns-message:reply dns-msg))
|
|
(answers (message:answers reply))
|
|
(nameservers (message:nameservers reply)))
|
|
(or (not (null? (filter-type answers 'mx)))
|
|
(not (null? (filter-type answers 'cname)))
|
|
(not (null? (filter-type answers 'a)))))))
|
|
(dns-msg (dns-get-information question use-cache? protocol nameserver check-answer))
|
|
(reply (dns-message:reply dns-msg))
|
|
(mx (filter-type (message:answers reply) 'mx))
|
|
(soa (filter-type (message:nameservers reply) 'soa))
|
|
(cname (filter-type (message:answers reply) 'cname))
|
|
(a (filter-type (message:answers reply) 'a)))
|
|
|
|
(cond
|
|
((not (null? a))
|
|
(list (rr-data-a:ip (rr:data (car a)))))
|
|
((not (null? cname))
|
|
(dns-lookup-mail-exchanger (rr-data-cname:name (rr:data (car cname)))))
|
|
((null? mx)
|
|
(list (rr-data-soa:rname (rr:data (car soa)))))
|
|
(else
|
|
(map (lambda (elem) (rr-data-mx:exchanger (rr:data elem))) (sort-by-preference mx))))))))))
|
|
|
|
(define dns-lookup-mail-exchanger (make-lookup-function dns-simple-lookup-mail-exchanger))
|
|
|
|
|
|
;;; pretty-prints a dns-msg
|
|
(define (show-dns-message dns-msg)
|
|
(let* ((d
|
|
(lambda (n s1 s2)
|
|
(letrec ((loop (lambda (n)
|
|
(if (zero? n)
|
|
""
|
|
(string-append " " (loop (- n 1)))))))
|
|
(display (loop n))
|
|
(display s1)
|
|
(display ": ")
|
|
(display s2)
|
|
(newline)))))
|
|
|
|
(cond
|
|
((dns-message? dns-msg)
|
|
(begin
|
|
(d 0 "DNS-MESSAGE" "")
|
|
(d 1 "QUERY" "")(show-dns-message (dns-message:query dns-msg))(newline)
|
|
(d 1 "REPLY" "")(show-dns-message (dns-message:reply dns-msg))(newline)
|
|
(d 1 "CACHE?" (if (dns-message:cache? dns-msg)
|
|
"found in cache"
|
|
"not found in cache"))
|
|
(d 1 "PROTOCOL" (let ((protocol (dns-message:protocol dns-msg)))
|
|
(cond
|
|
((eq? protocol 'tcp) "TCP")
|
|
((eq? protocol 'udp) "UDP"))))
|
|
(d 1 "TRIED-NAMESERVERS" (if (> (length (dns-message:tried-nameservers dns-msg)) 1)
|
|
(begin
|
|
(display " had perform recursion: ")
|
|
(dns-message:tried-nameservers dns-msg))
|
|
(begin
|
|
(display " without recursion: ")
|
|
(dns-message:tried-nameservers dns-msg))))))
|
|
((message? dns-msg)
|
|
(begin
|
|
(d 2 "MESSAGE" "")
|
|
(d 3 "Header " "")(show-dns-message (message:header dns-msg))
|
|
(d 3 "Questions " "")(for-each (lambda (x) (show-dns-message x)(newline)) (message:questions dns-msg))
|
|
(d 3 "Answers " "")(for-each (lambda (x) (show-dns-message x)(newline)) (message:answers dns-msg))
|
|
(d 3 "Nameservers" "")(for-each (lambda (x) (show-dns-message x)(newline)) (message:nameservers dns-msg))
|
|
(d 3 "Additionals" "")(for-each (lambda (x) (show-dns-message x)(newline)) (message:additionals dns-msg))))
|
|
((header? dns-msg)
|
|
(begin
|
|
(d 4 "id" (header:id dns-msg))
|
|
(d 4 "Flags" "")(show-dns-message (header:flags dns-msg))
|
|
(d 4 "question-count " (header:qdc dns-msg))
|
|
(d 4 "answer-count " (header:anc dns-msg))
|
|
(d 4 "nameserver-count " (header:nsc dns-msg))
|
|
(d 4 "additional-count " (header:arc dns-msg))))
|
|
((flags? dns-msg)
|
|
(begin
|
|
(d 5 "querytype" (flags:querytype dns-msg))
|
|
(d 5 "opcode" (flags:opcode dns-msg))
|
|
(d 5 "auth" (flags:auth dns-msg))
|
|
(d 5 "trunc" (flags:trunc dns-msg))
|
|
(d 5 "recursiondesired" (flags:recursiondesired dns-msg))
|
|
(d 5 "recursionavailable" (flags:recursionavailable dns-msg))
|
|
(d 5 "z" (flags:z dns-msg))
|
|
(d 5 "rcode" (flags:rcode dns-msg))))
|
|
((question? dns-msg)
|
|
(begin
|
|
(d 4 "name " (question:name dns-msg))
|
|
(d 4 "type " (question:type dns-msg))
|
|
(d 4 "class" (question:class dns-msg))))
|
|
((rr? dns-msg)
|
|
(begin
|
|
(d 4 "name " (rr:name dns-msg))
|
|
(d 4 "type " (rr:type dns-msg))
|
|
(d 4 "class" (rr:class dns-msg))
|
|
(d 4 "ttl " (rr:ttl dns-msg))
|
|
(d 4 "data " "") (show-dns-message (rr:data dns-msg))))
|
|
((rr-data-a? dns-msg)
|
|
(d 5 "ip " (rr-data-a:ip dns-msg)))
|
|
((rr-data-ns? dns-msg)
|
|
(d 5 "name " (rr-data-ns:name dns-msg)))
|
|
((rr-data-cname? dns-msg)
|
|
(d 5 "name " (rr-data-cname:name dns-msg)))
|
|
((rr-data-mx? dns-msg)
|
|
(begin
|
|
(d 5 "preference " (rr-data-mx:preference dns-msg))
|
|
(d 5 "exchanger " (rr-data-mx:exchanger dns-msg))))
|
|
((rr-data-ptr? dns-msg)
|
|
(d 5 "name " (rr-data-ptr:name dns-msg)))
|
|
((rr-data-soa? dns-msg)
|
|
(begin
|
|
(d 5 "mname " (rr-data-soa:mname dns-msg))
|
|
(d 5 "rname " (rr-data-soa:rname dns-msg))
|
|
(d 5 "serial " (rr-data-soa:serial dns-msg))
|
|
(d 5 "refresh " (rr-data-soa:refresh dns-msg))
|
|
(d 5 "expire " (rr-data-soa:expire dns-msg))
|
|
(d 5 "minimum " (rr-data-soa:expire dns-msg))))
|
|
;; ###
|
|
((rr-data-hinfo? dns-msg)
|
|
(d 5 "data " (rr-data-hinfo:data dns-msg)))
|
|
((rr-data-txt? dns-msg)
|
|
(d 5 "text " (rr-data-txt:text dns-msg)))
|
|
((rr-data-wks? dns-msg)
|
|
(d 5 "data " (rr-data-wks:data dns-msg)))
|
|
|
|
)))
|
|
|
|
(define *fqdn-lock* (make-lock))
|
|
(define *fqdn-cache* '())
|
|
|
|
(define (socket-address->fqdn addr cache?)
|
|
(receive (ip32 port)
|
|
(socket-address->internet-address addr)
|
|
(internet-address->fqdn ip32 cache?)))
|
|
|
|
(define (internet-address->fqdn ip32 cache?)
|
|
(if cache?
|
|
(begin
|
|
(obtain-lock *fqdn-lock*)
|
|
(cond
|
|
((assv ip32 *fqdn-cache*) =>
|
|
(lambda (pair)
|
|
(release-lock *fqdn-lock*)
|
|
(cdr pair)))
|
|
(else
|
|
(release-lock *fqdn-lock*)
|
|
(let ((fqdn (dns-lookup-ip ip32)))
|
|
(set! *fqdn-cache*
|
|
(cons (cons ip32 fqdn) *fqdn-cache*))
|
|
fqdn))))
|
|
(dns-lookup-ip ip32)))
|
|
|
|
(define (host-fqdn name-or-socket-address)
|
|
(if (socket-address? name-or-socket-address)
|
|
(socket-address->fqdn name-or-socket-address #f)
|
|
(internet-address->fqdn
|
|
(car
|
|
(host-info:addresses
|
|
(host-info name-or-socket-address)))
|
|
#f)))
|
|
|
|
(define (system-fqdn)
|
|
(internet-address->fqdn (car (host-info:addresses (host-info (system-name))))
|
|
#t))
|