1674 lines
59 KiB
Scheme
1674 lines
59 KiB
Scheme
;
|
|
; dns.scm
|
|
;
|
|
; Implementation of the RFC1035
|
|
|
|
;;; This file is part of the Scheme Untergrund Networking package.
|
|
|
|
;;; Copyright (c) 2002 by Marcus Crestani.
|
|
;;; Copyright (c) 2002-2003 by Martin Gasbichler
|
|
;;; 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-list) --> <ip-string list>
|
|
; this parses the /etc/resolv.conf file and returns the found
|
|
; nameserver in a list of dotted strings.
|
|
;
|
|
; (dns-find-nameserver) --> <ip-string>
|
|
; this parses the /etc/resolv.conf file and returns the first found
|
|
; nameserver in dotted string notation.
|
|
;
|
|
; (dns-check-namservers [nameserver list]) --> undefined
|
|
; checks if the given nameservers are reachable. If no argument is given,
|
|
; the nameservers in /etc/resolv.conf are checked.
|
|
;
|
|
;
|
|
; (dns-lookup-name <name> [nameserver list] [use-cache?]) --> <ip-address32>
|
|
; (dns-lookup-ip <ip-string | ip-address32> [nameserver list] [use-cache?])
|
|
; --> <name>
|
|
; (dns-lookup-nameserver <name> [nameserver list] [use-cache?])
|
|
; --> <list of ip-address32s of authoritative nameservers>
|
|
; (dns-lookup-mail-exchanger <name> [nameserver list] [use-cache?])
|
|
; --> <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 list])
|
|
; --> <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>
|
|
|
|
|
|
|
|
;; --- error conditions
|
|
|
|
;; supertype of all errors signaled by this library
|
|
(define-condition-type 'dns-error '(error))
|
|
(define dns-error? (condition-predicate 'dns-error))
|
|
|
|
(define-condition-type 'parse-error '(dns-error))
|
|
(define parse-error? (condition-predicate 'parse))
|
|
|
|
(define-condition-type 'unexpected-eof-from-server '(dns-error))
|
|
(define unexpected-eof-from-server? (condition-predicate 'unexpected-eof-from-server))
|
|
|
|
(define-condition-type 'bad-address '(dns-error))
|
|
(define bad-address? (condition-predicate 'bad-address))
|
|
|
|
(define-condition-type 'no-nameservers '(dns-error))
|
|
(define no-nameservers? (condition-predicate 'no-nameservers))
|
|
|
|
(define-condition-type 'no-nameserver-given '(dns-error))
|
|
(define no-nameserver-given? (condition-predicate 'no-nameserver-given))
|
|
|
|
(define-condition-type 'bad-nameserver '(dns-error))
|
|
(define bad-nameserver? (condition-predicate 'bad-nameserver))
|
|
|
|
(define-condition-type 'not-a-hostname '(dns-error))
|
|
(define not-a-hostname? (condition-predicate 'not-a-hostname))
|
|
|
|
(define-condition-type 'not-a-ip '(dns-error))
|
|
(define not-a-ip? (condition-predicate 'not-a-ip))
|
|
|
|
;; supertype of all errors signaled if the dns server returned a non-sero
|
|
;; reply code
|
|
(define-condition-type 'dns-server-error '(dns-error))
|
|
(define dns-server-error? (condition-predicate 'dns-server-error))
|
|
|
|
(define-condition-type 'dns-format-error '(dns-server-error))
|
|
(define dns-format-error? (condition-predicate 'dns-format-error))
|
|
|
|
(define-condition-type 'dns-server-failure '(dns-server-error))
|
|
(define dns-server-failure? (condition-predicate 'dns-server-failure))
|
|
|
|
(define-condition-type 'dns-name-error '(dns-server-error))
|
|
(define dns-name-error? (condition-predicate 'dns-name-error))
|
|
|
|
(define-condition-type 'dns-not-implemented '(dns-server-error))
|
|
(define dns-not-implemented? (condition-predicate 'dns-not-implemented))
|
|
|
|
(define-condition-type 'dns-refused '(dns-server-error))
|
|
(define dns-refused? (condition-predicate 'dns-refused))
|
|
|
|
(define (dns-error condition . stuff)
|
|
(apply signal condition (dns-error->string condition) stuff))
|
|
|
|
(define (dns-error->string condition)
|
|
(string-append
|
|
"dns-error: "
|
|
(case condition
|
|
((parse-error)
|
|
"parse: error parsing server message")
|
|
((unexpected-eof-from-server)
|
|
"send-receive-message: unexpected EOF from server")
|
|
((bad-address)
|
|
"dns-get-information: bad address (in combination with query type)")
|
|
((no-nameservers)
|
|
"dns-find-nameserver: no nameservers found in /etc/resolv.conf")
|
|
((no-nameserver-given)
|
|
"dns-find-nameserver: no nameservers given")
|
|
((bad-nameserver)
|
|
"send-receive-message: could not establish connection to server (no valid nameserver given)")
|
|
((not-a-hostname)
|
|
"no hostname given")
|
|
((not-a-ip)
|
|
"no ip given")
|
|
((dns-format-error)
|
|
"error from server: (1) format error")
|
|
((dns-server-failure)
|
|
"error from server: (2) server failure")
|
|
((dns-name-error)
|
|
"error from server: (3) name error")
|
|
((dns-not-implemented)
|
|
"error from server: (4) not implemented")
|
|
((dns-refused)
|
|
"error from server: (5) refused")
|
|
(else (error "Unknown dns-error" condition)))))
|
|
|
|
|
|
;;; -- globals and types
|
|
;; off
|
|
(define *nul* (ascii->char 0))
|
|
|
|
;; on
|
|
(define *on* (ascii->char 1))
|
|
|
|
;; message types
|
|
(define-enumerated-type message-type :message-type
|
|
message-type?
|
|
the-message-types
|
|
message-type-name
|
|
message-type-number
|
|
(unknown ; types, which are not yet implemented
|
|
a ; a host address
|
|
ns ; an authoritative name server
|
|
md ; (obsolete)
|
|
mf ; (obsolete)
|
|
cname ; the canonical name for an alias
|
|
soa ; marks the start of a zone of authority
|
|
mb ; (experimental)
|
|
mg ; (experimental)
|
|
mr ; (experimental)
|
|
null ; (experimental)
|
|
wks ; a well known service description
|
|
ptr ; a domain name pointer
|
|
hinfo ; host information
|
|
minfo ; (experimental)
|
|
mx ; mail exchange
|
|
txt)) ; text strings
|
|
|
|
;; message classes
|
|
(define-enumerated-type message-class :message-class
|
|
message-class?
|
|
the-message-classes
|
|
message-class-name
|
|
message-class-number
|
|
(placeholder ; this starts at 0...
|
|
in ; the Internet
|
|
cs ; (obsolete)
|
|
ch ; the CHAOS class
|
|
hs)) ; Hesoid
|
|
|
|
|
|
;;; -- useful stuff
|
|
|
|
;; number: 0<= x < 256
|
|
;; octet-pair: (char char)
|
|
;; octet-quad: (char char char char)
|
|
;; name: string *{"." string}
|
|
;; octets: *{(char *char)} nullchar
|
|
;; octet-ip: (char char char char)
|
|
;; address32: 0 <= x < 2^32-1
|
|
;; ip-string: "www.xxx.yyy.zzz"
|
|
;; ip-string-arpa: "zzz.yyy.xxx.www.in-addr.arpa"
|
|
|
|
;; encodes numbers (16bit) to octets
|
|
(define (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)
|
|
(+ (arithmetic-shift (char->ascii a) 8)
|
|
(char->ascii b)))
|
|
|
|
;; encodes numbers (32bit) to octets, needed for ttl
|
|
(define (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)
|
|
(+ (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)))
|
|
|
|
(let loop ((s s))
|
|
(cond
|
|
((regexp-search (rx (: bos (submatch (* (~ "."))) "." (submatch (* any))))
|
|
s)
|
|
=> (lambda (match)
|
|
(append
|
|
(encode-portion (match:substring match 1))
|
|
(loop (match:substring match 2)))))
|
|
(else
|
|
(if (= 0 (string-length s))
|
|
(list *nul*)
|
|
;;; TODO isn't this case an error?
|
|
(append
|
|
(encode-portion s)
|
|
(list *nul*)))))))
|
|
|
|
|
|
;; for tcp: message has to be tagged with its length
|
|
(define (add-size-tag m)
|
|
(append (number->octet-pair (length m)) m))
|
|
|
|
|
|
|
|
|
|
;; calculates a "random" number, needed for message-ids
|
|
;; TODO use SRFI-27
|
|
(define random
|
|
(let ((crank (make-random (modulo (time) (- (expt 2 27) 1)))))
|
|
(lambda (limit)
|
|
(quotient (* (modulo (crank) 314159265)
|
|
limit)
|
|
314159265))))
|
|
|
|
;; checks if v is a address32
|
|
(define (address32? v)
|
|
(and (number? v)
|
|
(<= 0 v #xffffffff)))
|
|
|
|
;; filters types in a list of rrs
|
|
(define (filter-type list type)
|
|
(filter (lambda (rr)
|
|
(eq? (resource-record-type rr) type))
|
|
list))
|
|
|
|
;; sorts a mx-resource-record-list by preference. needed for dns-lookup-mail-exchanger.
|
|
(define (sort-by-preference mx-list)
|
|
(sort-list mx-list
|
|
(lambda (a b)
|
|
(< (resource-record-data-mx-preference (resource-record-data a)) (resource-record-data-mx-preference (resource-record-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))
|
|
|
|
|
|
;; a standard query header, usefull for most queries
|
|
(define (make-std-query-header id question-count)
|
|
(let* ((qr 'query) ; querytype: query 0, response 1
|
|
(opcode 0) ; opcode: query 0, iquery 1 (OBSOLETE), status 2
|
|
(aa #f) ; authorative answer (in answers only)
|
|
(tc #f) ; truncation (size matters only with UDP)
|
|
(rd #t) ; recursion desired: nameserver pursues the query recursivly (optional)
|
|
(ra #f) ; recursion available (in answers only)
|
|
(zero 0) ; future use
|
|
(response-code 0) ; response code: error conditions (in answers only)
|
|
(question-count question-count)
|
|
(answer-count 0) ; answer count (in answers only)
|
|
(nameserver-count 0) ; name server resources (in answers only)
|
|
(additional-count 0)) ; additional records (in answers only)
|
|
|
|
(make-header
|
|
id
|
|
(make-flags qr opcode aa tc rd ra zero response-code)
|
|
question-count answer-count nameserver-count additional-count)))
|
|
|
|
|
|
;; makes a query-message (header and question only)
|
|
;; TODO does this really work for several questions as well?
|
|
(define (make-query-message header question . questions)
|
|
(let ((questions (cons question questions)))
|
|
(make-message header questions '() '() '()
|
|
(apply
|
|
append
|
|
(header->octets header)
|
|
(map question->octets questions)))))
|
|
|
|
(define (make-simple-query-message name type class)
|
|
(make-query-message (make-std-query-header (random 256) 1)
|
|
(make-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)
|
|
(let* ((name (name->octets name))
|
|
(type (number->octet-pair (message-type-number type)))
|
|
(class (number->octet-pair (message-class-number class)))
|
|
(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-type dns-message :dns-message
|
|
(make-dns-message query reply cache? protocol tried-nameservers)
|
|
dns-message?
|
|
(query dns-message-query)
|
|
(reply dns-message-reply)
|
|
(cache? dns-message-cache?)
|
|
(protocol dns-message-protocol)
|
|
(tried-nameservers dns-message-tried-nameservers))
|
|
|
|
;; message
|
|
(define-record-type message :message
|
|
(make-message header questions answers nameservers additionals source)
|
|
message?
|
|
(header message-header)
|
|
(questions message-questions)
|
|
(answers message-answers)
|
|
(nameservers message-nameservers)
|
|
(additionals message-additionals)
|
|
(source message-source))
|
|
|
|
;; header
|
|
(define-record-type header :header
|
|
(make-header id flags question-count answer-count nameserver-count
|
|
additional-count)
|
|
header?
|
|
(id header-id)
|
|
(flags header-flags)
|
|
(question-count header-question-count)
|
|
(answer-count header-answer-count)
|
|
(nameserver-count header-nameserver-count)
|
|
(additional-count header-additional-count))
|
|
|
|
;;; -- message constructors: encode to octet-messages
|
|
|
|
;; makes an message header
|
|
(define (header->octets header)
|
|
(let* ((header-id (number->octet-pair (header-id header)))
|
|
(header-question-count (number->octet-pair (header-question-count header)))
|
|
(header-answer-count (number->octet-pair (header-answer-count header)))
|
|
(header-nameserver-count (number->octet-pair
|
|
(header-nameserver-count header)))
|
|
(header-additional-count (number->octet-pair
|
|
(header-additional-count header))))
|
|
(append header-id
|
|
(flags->octets (header-flags header))
|
|
header-question-count
|
|
header-answer-count
|
|
header-nameserver-count
|
|
header-additional-count)))
|
|
|
|
;; flags
|
|
(define-record-type flags :flags
|
|
(make-flags query-type opcode authoritative? truncated? recursion-desired?
|
|
recursion-available? zero response-code)
|
|
flags?
|
|
(query-type flags-query-type)
|
|
(opcode flags-opcode)
|
|
(authoritative? flags-authoritative?)
|
|
(truncated? flags-truncated?)
|
|
(recursion-desired? flags-recursion-desired?)
|
|
(recursion-available? flags-recursion-available?)
|
|
(zero flags-zero)
|
|
(response-code flags-response-code))
|
|
|
|
(define (make-flags-from-numbers
|
|
querytype opcode authoritative? truncated? recursion-desired? recursion-available?
|
|
zero response-code)
|
|
(make-flags
|
|
(if (zero? querytype) 'query 'response)
|
|
opcode
|
|
(not (zero? authoritative?))
|
|
(not (zero? truncated?))
|
|
(not (zero? recursion-desired?))
|
|
(not (zero? recursion-available?))
|
|
zero
|
|
(case response-code
|
|
((0) 'dns-no-error)
|
|
((1) 'dns-format-error)
|
|
((2) 'dns-server-failure)
|
|
((3) 'dns-name-error)
|
|
((4) 'dns-not-implemented)
|
|
((5) 'dns-refused))))
|
|
|
|
(define (flags->octets flags)
|
|
(define (boolean->0/1 bool)
|
|
(if bool 1 0))
|
|
(list
|
|
(ascii->char (+ (arithmetic-shift
|
|
(if (eq? (flags-query-type flags) 'query) 0 1) 7)
|
|
(arithmetic-shift (flags-opcode flags) 3)
|
|
(arithmetic-shift
|
|
(boolean->0/1 (flags-authoritative? flags)) 2)
|
|
(arithmetic-shift
|
|
(boolean->0/1 (flags-truncated? flags)) 1)
|
|
(boolean->0/1 (flags-recursion-desired? flags))))
|
|
(ascii->char (+ (arithmetic-shift
|
|
(boolean->0/1 (flags-recursion-available? flags)) 7)
|
|
(arithmetic-shift (flags-zero flags) 4)
|
|
(flags-response-code flags)))))
|
|
|
|
|
|
;; question
|
|
(define-record-type question :question
|
|
(make-question name type class)
|
|
question?
|
|
(name question-name)
|
|
(type question-type)
|
|
(class question-class))
|
|
|
|
;; makes a question (name, type, class)
|
|
(define (question->octets q)
|
|
(let* ((qname (name->octets (question-name q)))
|
|
(qtype (number->octet-pair
|
|
(message-type-number (question-type q))))
|
|
(qclass (number->octet-pair
|
|
(message-class-number (question-class q)))))
|
|
(append qname qtype qclass)))
|
|
|
|
;;type resource-record
|
|
(define-record-type resource-record :resource-record
|
|
(make-resource-record name type class ttl data)
|
|
resource-record?
|
|
(name resource-record-name)
|
|
(type resource-record-type)
|
|
(class resource-record-class)
|
|
(ttl resource-record-ttl)
|
|
(data resource-record-data))
|
|
|
|
;; cache
|
|
(define-record-type cache :cache
|
|
(make-cache answer ttl time)
|
|
cache?
|
|
(answer cache-answer)
|
|
(ttl cache-ttl)
|
|
(time cache-time))
|
|
|
|
;;; -- message parser
|
|
|
|
;; parses a domain-name in an message. returns the name and the rest of the message.
|
|
(define (parse-name start message)
|
|
(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)
|
|
(call-with-values
|
|
(lambda () (parse-name start message))
|
|
(lambda (name start)
|
|
(let ((type (vector-ref the-message-types
|
|
(octet-pair->number (car start) (cadr start))))
|
|
(start (cddr start)))
|
|
(let ((class (vector-ref the-message-classes
|
|
(octet-pair->number (car start) (cadr start))))
|
|
(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 (type-number->type type-number)
|
|
(if (>= type-number (vector-length the-message-types))
|
|
'unsupported-message-type
|
|
(vector-ref the-message-types type-number)))
|
|
|
|
(define (class-number->class class-number)
|
|
(if (>= class-number (vector-length the-message-classes))
|
|
'unsupported-message-class
|
|
(vector-ref the-message-classes class-number)))
|
|
|
|
(define (parse-rr start message)
|
|
(call-with-values
|
|
(lambda () (parse-name start message))
|
|
(lambda (name start)
|
|
(let ((type (type-number->type
|
|
(octet-pair->number (car start) (cadr start))))
|
|
(start (cddr start)))
|
|
(let ((class (class-number->class
|
|
(octet-pair->number (car start) (cadr start))))
|
|
(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-resource-record name type class ttl (parse-resource-record-data type class (reverse! accum) message)) start)
|
|
(loop (- len 1)
|
|
(cdr start)
|
|
(cons (car start) accum)))))))))))
|
|
|
|
;;; -- resource-record-data-type records
|
|
|
|
(define-record-type resource-record-data-a :resource-record-data-a
|
|
(make-resource-record-data-a ip)
|
|
resource-record-data-a?
|
|
(ip resource-record-data-a-ip))
|
|
|
|
(define-record-type resource-record-data-ns :resource-record-data-ns
|
|
(make-resource-record-data-ns name)
|
|
resource-record-data-ns?
|
|
(name resource-record-data-ns-name))
|
|
|
|
(define-record-type resource-record-data-cname :resource-record-data-cname
|
|
(make-resource-record-data-cname name)
|
|
resource-record-data-cname?
|
|
(name resource-record-data-cname-name))
|
|
|
|
;; ###
|
|
;; hinfo not correctly implemented, trying to find examples
|
|
(define-record-type resource-record-data-hinfo :resource-record-data-hinfo
|
|
(make-resource-record-data-hinfo data)
|
|
resource-record-data-hinfo?
|
|
(data resource-record-data-hinfo-data))
|
|
|
|
(define-record-type resource-record-data-mx :resource-record-data-mx
|
|
(make-resource-record-data-mx preference exchanger)
|
|
resource-record-data-mx?
|
|
(preference resource-record-data-mx-preference)
|
|
(exchanger resource-record-data-mx-exchanger))
|
|
|
|
(define-record-type resource-record-data-ptr :resource-record-data-ptr
|
|
(make-resource-record-data-ptr name)
|
|
resource-record-data-ptr?
|
|
(name resource-record-data-ptr-name))
|
|
|
|
(define-record-type resource-record-data-soa :resource-record-data-soa
|
|
(make-resource-record-data-soa mname rname serial refresh retry expire minimum)
|
|
resource-record-data-soa?
|
|
(mname resource-record-data-soa-mname)
|
|
(rname resource-record-data-soa-rname)
|
|
(serial resource-record-data-soa-serial)
|
|
(refresh resource-record-data-soa-refresh)
|
|
(retry resource-record-data-soa-retry)
|
|
(expire resource-record-data-soa-expire)
|
|
(minimum resource-record-data-soa-minimum))
|
|
|
|
;; ### same as hinfo
|
|
(define-record-type resource-record-data-txt :resource-record-data-txt
|
|
(make-resource-record-data-txt text)
|
|
resource-record-data-txt?
|
|
(text resource-record-data-txt-text))
|
|
|
|
;; ### same as hinfo and txt
|
|
(define-record-type resource-record-data-wks :resource-record-data-wks
|
|
(make-resource-record-data-wks data)
|
|
resource-record-data-wks?
|
|
(data resource-record-data-wks-data))
|
|
|
|
;;
|
|
|
|
(define (parse-resource-record-data type class data message)
|
|
(cond
|
|
((eq? type (message-type a))
|
|
(make-resource-record-data-a (octet-ip->address32 data)))
|
|
|
|
((eq? type (message-type ns))
|
|
(make-resource-record-data-ns (call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type (message-type cname))
|
|
(make-resource-record-data-cname (call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type (message-type mx))
|
|
(make-resource-record-data-mx (octet-pair->number (car data) (cadr data))
|
|
(call-with-values
|
|
(lambda ()(parse-name (cddr data) message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type (message-type ptr))
|
|
(make-resource-record-data-ptr (call-with-values
|
|
(lambda () (parse-name data message))
|
|
(lambda (name rest) name))))
|
|
|
|
((eq? type (message-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-resource-record-data-soa mname rname serial refresh retry expire minimum)))))))))))
|
|
|
|
((eq? type (message-type hinfo))
|
|
(make-resource-record-data-hinfo (list->string data)))
|
|
|
|
((eq? type (message-type txt))
|
|
(make-resource-record-data-txt (list->string data)))
|
|
|
|
((eq? type (message-type wks))
|
|
(make-resource-record-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)
|
|
(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)
|
|
(let ((v0 (list-ref message 2))
|
|
(v1 (list-ref message 3)))
|
|
;; Check for error code:
|
|
(let ((response-code (bitwise-and #xf (char->ascii v1)))
|
|
(zero (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-from-numbers qr opcode aa tc rd ra zero response-code))))
|
|
|
|
|
|
;; parses a message-header. returns the header.
|
|
(define (parse-header message)
|
|
(let ((id (octet-pair->number (list-ref message 0) (list-ref message 1)))
|
|
(flags (parse-flags message))
|
|
(question-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 question-count an-count ns-count ar-count)))
|
|
|
|
|
|
;; parses a message. returns the parsed message.
|
|
(define (parse message)
|
|
(let* ((header (parse-header message))
|
|
(start (list-tail message 12)))
|
|
(call-with-values
|
|
(lambda () (parse-n parse-question start message (header-question-count header)))
|
|
(lambda (qds start)
|
|
(call-with-values
|
|
(lambda () (parse-n parse-rr start message (header-answer-count header)))
|
|
(lambda (ans start)
|
|
(call-with-values
|
|
(lambda () (parse-n parse-rr start message (header-nameserver-count header)))
|
|
(lambda (nss start)
|
|
(call-with-values
|
|
(lambda () (parse-n parse-rr start message (header-additional-count header)))
|
|
(lambda (ars start)
|
|
(if (not (null? start))
|
|
(dns-error '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)
|
|
;; Check correct id
|
|
(if (not (= (header-id (message-header reply))
|
|
(header-id (message-header query))))
|
|
;; TODO replace error
|
|
(error "send-receive-message: bad reply id from server"))
|
|
;; Check for error code:
|
|
(let ((response-code (flags-response-code
|
|
(header-flags (message-header reply)))))
|
|
(if (not (eq? response-code 'dns-no-error))
|
|
(dns-error response-code))))
|
|
|
|
|
|
(define *max-tries* 3)
|
|
(define *timeout* 1)
|
|
|
|
;; connects to nameserver and sends and receives messages. returns the reply.
|
|
;; here: via TCP
|
|
(define (send-receive-message-tcp nameservers query)
|
|
(receive (reply hit-ns other-nss)
|
|
(let ((sockets (map
|
|
(lambda (nameserver)
|
|
(let ((sock (create-socket protocol-family/internet
|
|
socket-type/stream))
|
|
(addr (internet-address->socket-address
|
|
nameserver 53)))
|
|
;; we ignore the return value and select
|
|
;; unconditionally later
|
|
(call-with-current-continuation
|
|
(lambda (k)
|
|
(with-handler (lambda (cond more)
|
|
(k #f))
|
|
(lambda ()
|
|
(connect-socket-no-wait sock addr)
|
|
sock))))))
|
|
nameservers)))
|
|
(let* ((nameservers
|
|
(let loop ((sockets sockets)
|
|
(nss nameservers))
|
|
(cond
|
|
((or (null? sockets) (null? nss)) '())
|
|
((socket? (car sockets))
|
|
(cons (car nss) (loop (cdr sockets) (cdr nss))))
|
|
(else (loop (cdr sockets) (cdr nss))))))
|
|
|
|
(sockets (filter socket? sockets))
|
|
(ws (map socket:outport sockets))
|
|
(wport-nameserver-alist (map cons ws nameservers))
|
|
(wport-socket-alist (map cons ws sockets)))
|
|
(dynamic-wind
|
|
(lambda ()
|
|
'nothing-to-be-done-before)
|
|
(lambda ()
|
|
(let loop-port-channels ((tried-channels '())
|
|
(number-tries 1))
|
|
(letrec ((delete-list
|
|
(lambda (elems list)
|
|
(cond
|
|
((null? elems) list)
|
|
((null? list) '())
|
|
(else (delete-list (cdr elems) (delete (car elems) list))))))
|
|
(ws-new (delete-list tried-channels ws)))
|
|
(if (or (null? ws-new) (>= number-tries *max-tries*))
|
|
(dns-error 'bad-nameserver)
|
|
(let ((ready (apply select-port-channels *timeout* ws)))
|
|
(if (= (length tried-channels) (length ws))
|
|
(dns-error 'bad-nameserver)
|
|
(let loop-ready-channels ((ready-channels ready))
|
|
(if (null? ready-channels)
|
|
(loop-port-channels (append tried-channels ready) (+ number-tries 1))
|
|
(let* ((w (car ready-channels))
|
|
(hit-ns (cdr (assoc w wport-nameserver-alist)))
|
|
(sock (cdr (assoc w wport-socket-alist))))
|
|
(if (not (connect-socket-successful? sock))
|
|
(loop-ready-channels (cdr ready-channels))
|
|
(let ((query-string
|
|
(list->string (add-size-tag (message-source query))))
|
|
(r (socket:inport sock)))
|
|
(call-with-current-continuation
|
|
(lambda (k)
|
|
(with-handler (lambda (cond more)
|
|
(k (loop-ready-channels (cdr ready-channels))))
|
|
(lambda ()
|
|
(display query-string 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 (and (not (= 0 (string-length s)))
|
|
(not (= len (string-length s))))
|
|
(dns-error 'unexpected-eof-from-server))
|
|
(values (parse (string->list s))
|
|
hit-ns
|
|
(delete hit-ns nameservers))))))))))))))))))))
|
|
(lambda ()
|
|
(for-each close-socket sockets)))))
|
|
(reply-acceptable? reply query)
|
|
(values reply
|
|
hit-ns
|
|
other-nss)))
|
|
|
|
;; here: via UDP
|
|
(define (send-receive-message-udp nameservers query)
|
|
(receive (reply hit-ns other-nss)
|
|
(let ((sockets (map (lambda (nameserver)
|
|
(let ((sock (create-socket protocol-family/internet
|
|
socket-type/datagram))
|
|
(addr (internet-address->socket-address
|
|
nameserver 53)))
|
|
(connect-socket sock addr)
|
|
sock))
|
|
nameservers)))
|
|
(let ((rs (map socket:inport sockets))
|
|
(ws (map socket:outport sockets)))
|
|
(dynamic-wind
|
|
(lambda ()
|
|
'nothing-to-be-done-before)
|
|
(lambda ()
|
|
(let ((query-string (list->string (message-source query)))
|
|
(rsv (list->vector rs))
|
|
(rport-nameserver-alist (map cons rs nameservers))
|
|
(rport-socket-alist (map cons rs sockets)))
|
|
(for-each (lambda (w) (display query-string w)) ws)
|
|
(for-each force-output ws)
|
|
(let loop-port-channels ((tried-channels '())
|
|
(number-tries 1))
|
|
(letrec ((delete-list
|
|
(lambda (elems list)
|
|
(cond
|
|
((null? elems) list)
|
|
((null? list) '())
|
|
(else (delete-list (cdr elems) (delete (car elems) list))))))
|
|
(rs-new (delete-list tried-channels rs)))
|
|
(if (or (null? rs-new) (>= number-tries *max-tries*))
|
|
(dns-error 'bad-nameserver)
|
|
(let ((ready (apply select-port-channels *timeout* rs-new)))
|
|
(if (= (length tried-channels) (length rs))
|
|
(dns-error 'bad-nameserver)
|
|
(let loop-ready-channels ((ready-channels ready))
|
|
(if (null? ready-channels)
|
|
(loop-port-channels (append tried-channels ready) (+ number-tries 1))
|
|
(let* ((r (car ready-channels))
|
|
(hit-ns (cdr (assoc r rport-nameserver-alist))))
|
|
(if (not (connect-socket-successful? (cdr (assoc r rport-socket-alist))))
|
|
(loop-ready-channels (cdr ready-channels))
|
|
;; 512 is the maximum udp-message size:
|
|
(let ((answer (string->list (read-string/partial 512 r))))
|
|
(if (null? answer)
|
|
(loop-ready-channels (cdr ready-channels))
|
|
(values (parse answer)
|
|
hit-ns
|
|
(delete hit-ns nameservers)))))))))))))))
|
|
(lambda ()
|
|
(for-each close-socket sockets)))))
|
|
(reply-acceptable? reply query)
|
|
(if (flags-truncated? (header-flags (message-header reply)))
|
|
(send-receive-message-tcp nameservers query)
|
|
(values reply
|
|
hit-ns
|
|
other-nss))))
|
|
|
|
|
|
;;; -- cache
|
|
|
|
;; creates the cache, an empty 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)
|
|
(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)
|
|
((resource-record? dns-msg)
|
|
(cond
|
|
((not minimum) (set! minimum (resource-record-ttl dns-msg)))
|
|
(else
|
|
(if (and (not minimum) (> minimum (resource-record-ttl dns-msg)))
|
|
(set! minimum (resource-record-ttl dns-msg))))))))))
|
|
(find-shortest-ttl-1 dns-msg)))
|
|
|
|
|
|
(define (make-key qds nameserver)
|
|
(let*;; cache-key relevant data
|
|
((name (question-name (car qds)))
|
|
(type (question-type (car qds)))
|
|
(class (question-class (car qds))))
|
|
(format #f "~a;~a;~a;~a"
|
|
nameserver
|
|
name
|
|
(message-type-name type)
|
|
(message-class-name class))))
|
|
|
|
(define (lookup-cache qds nameserver)
|
|
(let* ((key (make-key qds nameserver))
|
|
(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))))
|
|
found-data)
|
|
(else #f))))
|
|
|
|
(define (update-cache! key entry)
|
|
(table-set! cache key entry))
|
|
|
|
(define (dns-query-no-cache query protocol nameservers tried)
|
|
;; returns new retrieved data
|
|
(receive (dns-msg hit-ns nss-with-no-reply)
|
|
(send-receive-message nameservers query protocol)
|
|
(values
|
|
(make-dns-message query dns-msg #f protocol (reverse tried))
|
|
hit-ns
|
|
nss-with-no-reply)))
|
|
|
|
(define (dns-query-with-cache query protocol nameservers tried)
|
|
(let ((qds (message-questions query)))
|
|
(let lp ((ns nameservers))
|
|
(if (null? ns)
|
|
(receive (reply-msg hit-ns nss-with-no-reply)
|
|
(send-receive-message nameservers query protocol)
|
|
(update-cache! (make-key qds hit-ns)
|
|
(make-cache reply-msg (find-shortest-ttl reply-msg) (time)))
|
|
;; returns new retrieved data and updates cache
|
|
(values (make-dns-message query reply-msg #f protocol (reverse tried))
|
|
hit-ns
|
|
nss-with-no-reply))
|
|
(cond ((lookup-cache qds (car ns))
|
|
=> (lambda (found-data)
|
|
;; returns cached data
|
|
(values (make-dns-message query (cache-answer found-data) #t protocol '())
|
|
#f
|
|
nameservers)))
|
|
(else (lp (cdr ns))))))))
|
|
|
|
(define-enumerated-type network-protocol :network-protocol
|
|
network-protocol?
|
|
network-protocols
|
|
network-protocol-name
|
|
network-protocol-index
|
|
(udp tcp))
|
|
|
|
(define (send-receive-message nameservers query protocol)
|
|
((cond
|
|
((eq? protocol (network-protocol tcp)) send-receive-message-tcp)
|
|
((eq? protocol (network-protocol udp)) send-receive-message-udp))
|
|
nameservers query))
|
|
|
|
;; 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 query use-cache? protocol nameservers tried)
|
|
(if use-cache?
|
|
(dns-query-with-cache query protocol nameservers tried)
|
|
(dns-query-no-cache query protocol nameservers tried)))
|
|
|
|
;; 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 query protocol check-answer . args)
|
|
(receive (nameservers use-cache?) (lookup-optional-args args)
|
|
(let lp ((tried '()) (nss nameservers))
|
|
(if (null? nss)
|
|
(if (null? tried)
|
|
(dns-error 'no-nameserver-given)
|
|
(dns-error 'bad-address))
|
|
(receive (dns-msg hit-ns nss-with-no-reply)
|
|
(dns-query/cache query use-cache? protocol nss tried)
|
|
(if (check-answer dns-msg)
|
|
dns-msg
|
|
(let ((auth? (flags-authoritative? (header-flags
|
|
(message-header
|
|
(dns-message-reply dns-msg))))))
|
|
(if auth?
|
|
(dns-error 'bad-address)
|
|
;; other nameservers names are found in the nameserver-part,
|
|
;; but their ip-adresses are found in the additonal-rrs
|
|
(let ((other-nameservers
|
|
(filter (lambda (elem) (eq? (resource-record-type elem) (message-type a)))
|
|
(message-additionals (dns-message-reply dns-msg)))))
|
|
(lp (if (not (member hit-ns tried)) (cons hit-ns tried) tried)
|
|
(lset-union equal?
|
|
nss-with-no-reply
|
|
(lset-difference equal? other-nameservers tried))))))))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Parsing of /etc/resolv.conf
|
|
|
|
(define-condition-type 'resolv.conf-parse-error '(dns-error))
|
|
(define resolv.conf-parse-error? (condition-predicate 'resolv.conf-parse-error))
|
|
|
|
(define (parse-nameserver rest-of-line)
|
|
(let ((match (regexp-search
|
|
(rx (: (submatch (** 1 3 digit) "."
|
|
(** 1 3 digit) "."
|
|
(** 1 3 digit) "."
|
|
(** 1 3 digit))
|
|
(* white))); don't complain about tailing white space
|
|
rest-of-line)))
|
|
(if match
|
|
(cons 'nameserver (match:substring match 1))
|
|
(signal 'resolv.conf-parse-error))))
|
|
|
|
; could be more restrictive...
|
|
(define domain-name-regexp (rx (+ (| alphanum #\. #\-))))
|
|
|
|
(define (parse-domain rest-of-line)
|
|
(let ((match (regexp-search
|
|
(rx (: (submatch ,domain-name-regexp)
|
|
(* white))); don't complain about tailing white space
|
|
rest-of-line)))
|
|
(if match
|
|
(cons 'domain (match:substring match 1))
|
|
(signal 'resolv.conf-parse-error))))
|
|
|
|
(define (parse-search rest-of-line)
|
|
(let ((domains (regexp-fold-right domain-name-regexp
|
|
(lambda (match junk accu)
|
|
(cons (match:substring match 0) accu))
|
|
'()
|
|
rest-of-line)))
|
|
(if (null? domains)
|
|
(signal 'resolv.conf-parse-error)
|
|
(cons 'search domains))))
|
|
|
|
(define (parse-sortlist rest-of-line)
|
|
(let ((netmask-pairs (regexp-fold-right (rx (+ (| digit #\. #\/)))
|
|
(lambda (match junk accu)
|
|
(cons (match:substring match 0) accu))
|
|
'()
|
|
rest-of-line)))
|
|
(if (null? netmask-pairs)
|
|
(signal 'resolv.conf-parse-error)
|
|
(cons 'sortlist netmask-pairs))))
|
|
|
|
(define (parse-options rest-of-line)
|
|
(regexp-fold-right
|
|
(rx (| "debug" "no_tld_query" (: "ndots:" (submatch digit))))
|
|
(lambda (match junk accu)
|
|
(let ((str (match:substring match 0)))
|
|
(cond ((string=? str "debug")
|
|
(cons 'debug accu))
|
|
((string=? str "no_tld_query")
|
|
(cons 'no_tld_query accu))
|
|
(else (cons (cons 'ndots
|
|
(string->number (match:substring match 1))) accu)))))
|
|
'()
|
|
rest-of-line))
|
|
|
|
(define *resolv.conf-cache*)
|
|
(define *resolv.conf-cache-date* 0)
|
|
(define *resolv.conf-file* "/etc/resolv.conf")
|
|
|
|
(define (resolv.conf)
|
|
(let ((actual-m-time (file-info:mtime (file-info *resolv.conf-file*))))
|
|
(if (> actual-m-time *resolv.conf-cache-date*)
|
|
(parse-resolv.conf!))
|
|
*resolv.conf-cache*))
|
|
|
|
(define (parse-resolv.conf!)
|
|
(let ((actual-m-time (file-info:mtime (file-info *resolv.conf-file*)))
|
|
(contents (really-parse-resolv.conf *resolv.conf-file*)))
|
|
(set! *resolv.conf-cache* contents)
|
|
(set! *resolv.conf-cache-date* actual-m-time)))
|
|
|
|
(define (really-parse-resolv.conf file-name)
|
|
|
|
;; accumulate nameserver entries
|
|
;; domain and search are mutual exclusive, take the last
|
|
(define (adjust-result rev-result have-search-or-domain? nameservers)
|
|
(cond ((null? rev-result)
|
|
(if (null? nameservers)
|
|
'()
|
|
(list (cons 'nameserver nameservers))))
|
|
((eq? (caar rev-result) 'domain)
|
|
(if have-search-or-domain?
|
|
(adjust-result (cdr rev-result) have-search-or-domain? nameservers)
|
|
(cons (car rev-result)
|
|
(adjust-result (cdr rev-result)
|
|
#t
|
|
nameservers))))
|
|
((eq? (caar rev-result) 'search)
|
|
(if have-search-or-domain?
|
|
(adjust-result (cdr rev-result) have-search-or-domain? nameservers)
|
|
(cons (car rev-result)
|
|
(adjust-result (cdr rev-result)
|
|
#t
|
|
nameservers))))
|
|
((eq? (caar rev-result) 'nameserver)
|
|
(adjust-result (cdr rev-result)
|
|
have-search-or-domain?
|
|
(cons (cdar rev-result)
|
|
nameservers)))
|
|
(else (cons (car rev-result)
|
|
(adjust-result (cdr rev-result)
|
|
have-search-or-domain?
|
|
nameservers)))))
|
|
|
|
(with-input-from-file file-name
|
|
(lambda ()
|
|
(let loop ((rev-result '()))
|
|
(let ((l (read-line)))
|
|
(cond
|
|
((eof-object? l)
|
|
(adjust-result rev-result #f '()))
|
|
((regexp-search
|
|
(rx (: bos (| "#" ";")))
|
|
l)
|
|
(loop rev-result))
|
|
((regexp-search
|
|
(rx (: bos "nameserver" (+ (| " " "\t")
|
|
(submatch (* any))
|
|
eos)))
|
|
l)
|
|
=> (lambda (match)
|
|
(loop (cons (parse-nameserver (match:substring match 1))
|
|
rev-result))))
|
|
((regexp-search
|
|
(rx (: bos "domain" (+ (| " " "\t")
|
|
(submatch (* any))
|
|
eos)))
|
|
l)
|
|
=> (lambda (match)
|
|
(loop (cons (parse-domain (match:substring match 1))
|
|
rev-result))))
|
|
((regexp-search
|
|
(rx (: bos "search" (+ (| " " "\t")
|
|
(submatch (* any))
|
|
eos)))
|
|
l)
|
|
=> (lambda (match)
|
|
(loop (cons (parse-search (match:substring match 1))
|
|
rev-result))))
|
|
|
|
((regexp-search
|
|
(rx (: bos "sortlist" (+ (| " " "\t")
|
|
(submatch (* any))
|
|
eos)))
|
|
l)
|
|
=> (lambda (match)
|
|
(loop (cons (parse-sortlist (match:substring match 1))
|
|
rev-result))))
|
|
|
|
|
|
((regexp-search
|
|
(rx (: bos "options" (+ (| " " "\t")
|
|
(submatch (* any))
|
|
eos)))
|
|
l)
|
|
=> (lambda (match)
|
|
(loop (cons (parse-options (match:substring match 1))
|
|
rev-result))))
|
|
;; skips lines with parse errors, instead of
|
|
;; raising a 'resolv.conf-parse-error
|
|
(else (loop rev-result))))))))
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Figure out the default name servers
|
|
|
|
(define (dns-find-nameserver-list)
|
|
(cond ((assoc 'nameserver (resolv.conf))
|
|
=> (lambda (nameserver.list)
|
|
(cdr nameserver.list)))
|
|
(else '("127.0.0.1"))))
|
|
|
|
;; returns the first found nameserver
|
|
(define (dns-find-nameserver)
|
|
(let ((ns (dns-find-nameserver-list)))
|
|
(if (null? ns)
|
|
(dns-error 'no-nameservers)
|
|
(car ns))))
|
|
|
|
;; checks if the nameservers are working, prints a summary
|
|
(define (dns-check-nameservers . args)
|
|
(let* ((print-summary
|
|
(lambda (working-channels non-working-channels)
|
|
(for-each (lambda (channel)
|
|
(display "FAIL: ")(display channel)
|
|
(display " - host not reachable.")(newline))
|
|
non-working-channels)
|
|
(for-each (lambda (channel)
|
|
(display "PASS: ")(display channel)
|
|
(display " - connection established.")(newline))
|
|
working-channels)
|
|
(if (null? working-channels)
|
|
(begin (display "ERROR: no working nameserver found.")(newline)))))
|
|
|
|
(nameservers (if (null? args)
|
|
(dns-find-nameserver-list)
|
|
(car args)))
|
|
(sockets (map
|
|
(lambda (nameserver)
|
|
(let ((sock (create-socket protocol-family/internet
|
|
socket-type/stream))
|
|
(addr (internet-address->socket-address
|
|
(ip-string->address32 nameserver) 53)))
|
|
(call-with-current-continuation
|
|
(lambda (k)
|
|
(with-handler (lambda (cond more)
|
|
(display "FAIL: ")
|
|
(display nameserver)
|
|
(display " - no DNS Service available.")
|
|
(newline)
|
|
(k #f))
|
|
(lambda ()
|
|
(connect-socket-no-wait sock addr)
|
|
sock))))))
|
|
nameservers)))
|
|
|
|
(let* ((nameservers
|
|
(let loop ((sockets sockets)
|
|
(nss nameservers))
|
|
(cond
|
|
((or (null? sockets) (null? nss)) '())
|
|
((socket? (car sockets))
|
|
(cons (car nss) (loop (cdr sockets) (cdr nss))))
|
|
(else (loop (cdr sockets) (cdr nss))))))
|
|
|
|
(sockets (filter socket? sockets))
|
|
(ws (map socket:outport sockets))
|
|
(wport-nameserver-alist (map cons ws nameservers))
|
|
(wport-socket-alist (map cons ws sockets)))
|
|
(dynamic-wind
|
|
(lambda ()
|
|
'nothing-to-be-done-before)
|
|
(lambda ()
|
|
(let loop-port-channels ((working-channels '())
|
|
(non-working-channels '())
|
|
(tried-channels '())
|
|
(number-tries 1))
|
|
(letrec ((delete-list
|
|
(lambda (elems list)
|
|
(cond
|
|
((null? elems) list)
|
|
((null? list) '())
|
|
(else (delete-list (cdr elems) (delete (car elems) list))))))
|
|
(ready (delete-list tried-channels
|
|
(apply select-port-channels *timeout* ws))))
|
|
|
|
(if (or (>= number-tries *max-tries*))
|
|
(print-summary working-channels
|
|
(delete-list working-channels
|
|
(delete-list non-working-channels
|
|
nameservers)))
|
|
(let loop-ready-channels ((working-channels working-channels)
|
|
(non-working-channels non-working-channels)
|
|
(ready-channels ready))
|
|
(if (null? ready-channels)
|
|
(loop-port-channels working-channels
|
|
non-working-channels
|
|
(append tried-channels ready)
|
|
(+ number-tries 1))
|
|
(let* ((w (car ready-channels))
|
|
(hit-ns (cdr (assoc w wport-nameserver-alist)))
|
|
(sock (cdr (assoc w wport-socket-alist))))
|
|
(if (connect-socket-successful? sock)
|
|
(loop-ready-channels (append working-channels (list hit-ns))
|
|
non-working-channels (cdr ready-channels))
|
|
(loop-ready-channels working-channels
|
|
(append non-working-channels
|
|
(list hit-ns))
|
|
(cdr ready-channels))))))))))
|
|
(lambda ()
|
|
(for-each close-socket sockets))))))
|
|
|
|
|
|
;; computes the nameservers argument of the lookup functions.
|
|
;; if a nameserver-name is given and not a nameserver-ip
|
|
;; (dns-lookup-name nameserver) is called.
|
|
;; use-cache? defaults to #t
|
|
(define (lookup-optional-args args)
|
|
(if (null? args)
|
|
(values (map ip-string->address32 (dns-find-nameserver-list)) #t)
|
|
(values
|
|
(map (lambda (nameserver)
|
|
(cond
|
|
((address32? nameserver) nameserver)
|
|
((ip-string? nameserver) (ip-string->address32 nameserver))
|
|
(else (map dns-lookup-name (dns-find-nameserver-list)))))
|
|
(car args))
|
|
(if (null? (cdr args))
|
|
#t
|
|
(cadr args)))))
|
|
|
|
;; dns-lookup with more options than dns-lookup-*
|
|
(define (dns-lookup name type . args)
|
|
(receive (nameservers use-cache?) (lookup-optional-args args)
|
|
(let* ((maybe-ip-string (if (address32? name)
|
|
(ip-string->in-addr.arpa-string (address32->ip-string name))
|
|
(ip-string->in-addr.arpa-string name)))
|
|
(query (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(make-simple-query-message
|
|
maybe-ip-string type (message-class in))
|
|
(make-simple-query-message name type (message-class in))))
|
|
(protocol (network-protocol udp))
|
|
(check-answer (lambda (dns-msg) #t))
|
|
(dns-msg (dns-get-information query protocol check-answer nameservers use-cache?))
|
|
(answers (message-answers (dns-message-reply dns-msg))))
|
|
dns-msg)))
|
|
|
|
|
|
;; looks up a hostname, returns an ip.
|
|
;; (dns-lookup-name <name> nameservers)
|
|
(define (dns-lookup-name name . args)
|
|
(receive (nameservers use-cache?) (lookup-optional-args args)
|
|
(let* ((maybe-ip-string (if (address32? name)
|
|
(ip-string->in-addr.arpa-string (address32->ip-string name))
|
|
(ip-string->in-addr.arpa-string name)))
|
|
(query (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(dns-error 'not-a-hostname)
|
|
(make-simple-query-message name (message-type a) (message-class in))))
|
|
(protocol (network-protocol udp))
|
|
(check-answer (lambda (dns-msg)
|
|
(let* ((reply (dns-message-reply dns-msg))
|
|
(answers (message-answers reply)))
|
|
(not (null? (filter-type answers (message-type a)))))))
|
|
(dns-msg (dns-get-information query protocol check-answer nameservers use-cache?))
|
|
(answers (filter-type (message-answers (dns-message-reply dns-msg)) (message-type a))))
|
|
(resource-record-data-a-ip (resource-record-data (car answers))))))
|
|
|
|
;; looks up an ip, returns a hostname
|
|
;; (dns-inverse-lookup <name> [nameserver])
|
|
(define (dns-lookup-ip ip . args)
|
|
(receive (nameservers use-cache?) (lookup-optional-args args)
|
|
(let* ((maybe-ip-string (if (address32? ip)
|
|
(ip-string->in-addr.arpa-string (address32->ip-string ip))
|
|
(ip-string->in-addr.arpa-string ip)))
|
|
(query (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(make-simple-query-message maybe-ip-string (message-type ptr) (message-class in))
|
|
(dns-error 'not-a-ip)))
|
|
(protocol (network-protocol udp))
|
|
(check-answer (lambda (dns-msg)
|
|
(let* ((reply (dns-message-reply dns-msg))
|
|
(answers (message-answers reply)))
|
|
(not (null? (filter-type answers (message-type ptr)))))))
|
|
(dns-msg (dns-get-information query protocol check-answer nameservers use-cache?))
|
|
(answers (filter-type (message-answers (dns-message-reply dns-msg)) (message-type ptr))))
|
|
(resource-record-data-ptr-name (resource-record-data (car answers))))))
|
|
|
|
(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-lookup-nameserver name . args)
|
|
(receive (nameservers use-cache?) (lookup-optional-args args)
|
|
(let* ((maybe-ip-string (if (address32? name)
|
|
(ip-string->in-addr.arpa-string (address32->ip-string name))
|
|
(ip-string->in-addr.arpa-string name)))
|
|
(query (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(dns-error 'not-a-hostname)
|
|
(make-simple-query-message
|
|
name (message-type ns) (message-class in))))
|
|
(protocol (network-protocol udp))
|
|
(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 (message-type soa))))
|
|
(not (null? (filter-type answers (message-type ns))))))))
|
|
(dns-msg (dns-get-information query protocol check-answer nameservers use-cache?))
|
|
(reply (dns-message-reply dns-msg))
|
|
(soa (filter-type (message-nameservers reply) (message-type soa)))
|
|
(nss (filter-type (message-answers reply) (message-type ns)))
|
|
(add (filter-type (message-additionals reply) (message-type a))))
|
|
(if (null? nss)
|
|
(list (dns-lookup-name (resource-record-data-soa-mname (resource-record-data (car soa)))))
|
|
(map (lambda (elem) (resource-record-data-a-ip (resource-record-data elem))) add)))))
|
|
|
|
;; 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-lookup-mail-exchanger name . args)
|
|
(receive (nameservers use-cache?) (lookup-optional-args args)
|
|
(let* ((ip-string (if (address32? name)
|
|
(ip-string->in-addr.arpa-string (address32->ip-string name))
|
|
(ip-string->in-addr.arpa-string name)))
|
|
(query (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
|
|
(dns-error 'not-a-hostname)
|
|
(make-simple-query-message
|
|
name (message-type mx) (message-class in))))
|
|
(protocol (network-protocol tcp))
|
|
(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 (message-type mx))))
|
|
(not (null? (filter-type answers (message-type cname))))
|
|
(not (null? (filter-type answers (message-type a))))))))
|
|
(dns-msg (dns-get-information query protocol check-answer nameservers use-cache?))
|
|
(reply (dns-message-reply dns-msg))
|
|
(mx (filter-type (message-answers reply) (message-type mx)))
|
|
(soa (filter-type (message-nameservers reply)(message-type soa)))
|
|
(cname (filter-type (message-answers reply) (message-type cname)))
|
|
(a (filter-type (message-answers reply) (message-type a))))
|
|
|
|
(cond
|
|
((not (null? a))
|
|
(list (resource-record-data-a-ip (resource-record-data (car a)))))
|
|
((not (null? cname))
|
|
(dns-lookup-mail-exchanger (resource-record-data-cname-name (resource-record-data (car cname)))))
|
|
((null? mx)
|
|
(list (resource-record-data-soa-rname (resource-record-data (car soa)))))
|
|
(else
|
|
(map (lambda (elem) (resource-record-data-mx-exchanger (resource-record-data elem))) (sort-by-preference mx)))))))
|
|
|
|
;;; pretty-prints a dns-msg
|
|
(define (pretty-print-dns-message dns-msg . maybe-port)
|
|
(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)))))
|
|
(with-current-output-port*
|
|
(if (null? maybe-port)
|
|
(current-output-port)
|
|
(car maybe-port))
|
|
(lambda ()
|
|
(define (show-dns-message dns-msg)
|
|
(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 (network-protocol tcp)) "TCP")
|
|
((eq? protocol (network-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-question-count dns-msg))
|
|
(d 4 "answer-count " (header-answer-count dns-msg))
|
|
(d 4 "nameserver-count " (header-nameserver-count dns-msg))
|
|
(d 4 "additional-count " (header-additional-count dns-msg))))
|
|
((flags? dns-msg)
|
|
(begin
|
|
(d 5 "querytype" (flags-query-type dns-msg))
|
|
(d 5 "opcode" (flags-opcode dns-msg))
|
|
(d 5 "authoritative?" (flags-authoritative? dns-msg))
|
|
(d 5 "truncated?" (flags-truncated? dns-msg))
|
|
(d 5 "recursion-desired?" (flags-recursion-desired? dns-msg))
|
|
(d 5 "recursion-available?" (flags-recursion-available? dns-msg))
|
|
(d 5 "zero" (flags-zero dns-msg))
|
|
(d 5 "response-code" (flags-response-code dns-msg))))
|
|
((question? dns-msg)
|
|
(begin
|
|
(d 4 "name " (question-name dns-msg))
|
|
(d 4 "type " (message-type-name (question-type dns-msg)))
|
|
(d 4 "class" (message-class-name (question-class dns-msg)))))
|
|
((resource-record? dns-msg)
|
|
(begin
|
|
(d 4 "name " (resource-record-name dns-msg))
|
|
(d 4 "type "(message-type-name (resource-record-type dns-msg)))
|
|
(d 4 "class" (message-class-name (resource-record-class dns-msg)))
|
|
(d 4 "ttl " (resource-record-ttl dns-msg))
|
|
(d 4 "data " "") (show-dns-message (resource-record-data dns-msg))))
|
|
((resource-record-data-a? dns-msg)
|
|
(d 5 "ip " (resource-record-data-a-ip dns-msg)))
|
|
((resource-record-data-ns? dns-msg)
|
|
(d 5 "name " (resource-record-data-ns-name dns-msg)))
|
|
((resource-record-data-cname? dns-msg)
|
|
(d 5 "name " (resource-record-data-cname-name dns-msg)))
|
|
((resource-record-data-mx? dns-msg)
|
|
(begin
|
|
(d 5 "preference " (resource-record-data-mx-preference dns-msg))
|
|
(d 5 "exchanger " (resource-record-data-mx-exchanger dns-msg))))
|
|
((resource-record-data-ptr? dns-msg)
|
|
(d 5 "name " (resource-record-data-ptr-name dns-msg)))
|
|
((resource-record-data-soa? dns-msg)
|
|
(begin
|
|
(d 5 "mname " (resource-record-data-soa-mname dns-msg))
|
|
(d 5 "rname " (resource-record-data-soa-rname dns-msg))
|
|
(d 5 "serial " (resource-record-data-soa-serial dns-msg))
|
|
(d 5 "refresh " (resource-record-data-soa-refresh dns-msg))
|
|
(d 5 "expire " (resource-record-data-soa-expire dns-msg))
|
|
(d 5 "minimum " (resource-record-data-soa-expire dns-msg))))
|
|
;; ###
|
|
((resource-record-data-hinfo? dns-msg)
|
|
(d 5 "data " (resource-record-data-hinfo-data dns-msg)))
|
|
((resource-record-data-txt? dns-msg)
|
|
(d 5 "text " (resource-record-data-txt-text dns-msg)))
|
|
((resource-record-data-wks? dns-msg)
|
|
(d 5 "data " (resource-record-data-wks-data dns-msg)))
|
|
))
|
|
(show-dns-message dns-msg)))))
|
|
|
|
(define (socket-address->fqdn addr . args)
|
|
(receive (ip32 port)
|
|
(socket-address->internet-address addr)
|
|
(apply dns-lookup-ip ip32 args)))
|
|
|
|
;; Some code to test the components of domain names
|
|
;;(define label-regexp
|
|
;; (rx (: alpha (? (* (| alphanumeric "-")) alphanumeric))))
|
|
;;(define (unqualified-hostname name)
|
|
;; (regexp-search? (rx (: ,label-regexp)) name))
|
|
|
|
|
|
(define (maybe-dns-lookup-name name . args)
|
|
(call-with-current-continuation
|
|
(lambda (k)
|
|
(with-handler (lambda (cond more)
|
|
(if (dns-error? cond)
|
|
(k #f)
|
|
(more)))
|
|
(lambda ()
|
|
(apply dns-lookup-name name args))))))
|
|
|
|
(define (maybe-dns-lookup-ip ip-addr . args)
|
|
(call-with-current-continuation
|
|
(lambda (k)
|
|
(with-handler (lambda (cond more)
|
|
(if (dns-error? cond)
|
|
(k #f)
|
|
(more)))
|
|
(lambda ()
|
|
(apply dns-lookup-ip ip-addr args))))))
|
|
|
|
(define (domains-for-search)
|
|
(cond ((assoc 'domain (resolv.conf))
|
|
=> (lambda (pair)
|
|
(list (cdr pair))))
|
|
((assoc 'search (resolv.conf))
|
|
=> (lambda (pair)
|
|
(cdr pair)))
|
|
(else '())))
|
|
|
|
(define (host-fqdn name-or-socket-address . args)
|
|
(if (socket-address? name-or-socket-address)
|
|
(apply socket-address->fqdn name-or-socket-address args)
|
|
(let ((name name-or-socket-address))
|
|
(cond ((apply maybe-dns-lookup-name name args)
|
|
=> (lambda (ip)
|
|
(apply dns-lookup-ip ip args)))
|
|
(else
|
|
(let lp ((domains (domains-for-search)))
|
|
(if (null? domains)
|
|
#f
|
|
(cond ((apply maybe-dns-lookup-name
|
|
(string-append name "." (car domains)) args)
|
|
=> (lambda (ip)
|
|
(apply dns-lookup-ip ip args)))
|
|
(else (lp (cdr domains)))))))))))
|
|
|
|
(define (system-fqdn . args)
|
|
(apply host-fqdn (system-name) args))
|
|
|