#lang racket/base ;; DNS server using os-big-bang.rkt and os-udp.rkt. (require racket/match) (require racket/set) (require racket/bool) (require "../racket-bitsyntax/main.rkt") (require "api.rkt") (require "codec.rkt") (require "zonedb.rkt") (require "resolver.rkt") (require "../racket-matrix/os2.rkt") (require "../racket-matrix/os2-udp.rkt") (require "os2-dns.rkt") ;; Instantiated with a SOA record for the zone it is serving as well ;; as a zone's worth of DNS data which is used to answer queries ;; authoritatively. Never caches information, never performs recursive ;; queries. ;; Rules: ;; - Answers authoritative NXDOMAIN answers for queries falling within ;; its zone. (This is the only responder entitled to answer NXDOMAIN!) ;; - Answers with referrals for queries falling in subzones. It ;; determines subzones based on the RRs it is configured with at ;; startup. ;; start-server : UInt16 RR ListOf -> Void ;; Starts a server that will answer questions received on the given ;; UDP port based on the RRs it is given and the zone origin specified ;; in the soa-rr given. (require racket/pretty) (define (start-server port-number soa-rr rrs) ;; Compile the zone hash table (define zone (compile-zone-db (cons soa-rr rrs))) (define local-addr (udp-listener port-number)) (display ";; Ready.\n") (ground-vm (transition 'no-state ;; (spawn udp-spy #:debug-name 'udp-spy) (spawn udp-driver #:debug-name 'udp-driver) (spawn (nested-vm #:debug-name 'dns-vm (transition 'no-state (spawn dns-spy #:debug-name 'dns-spy) (spawn (dns-read-driver local-addr) #:debug-name 'dns-read-driver) (spawn (dns-write-driver local-addr) #:debug-name 'dns-write-driver) (role (topic-subscriber (bad-dns-packet (wild) (wild) (wild) (wild))) [p (begin (log-error (pretty-format p)) '())]) (role (topic-subscriber (dns-request (wild) (wild) (wild))) [r (map send-message (handle-request soa-rr zone r))]))) #:debug-name 'dns-vm)))) (define (handle-request soa-rr zone request) (match-define (dns-request request-message request-source request-sink) request) (define (make-reply name send-name-error? answers authorities additional) (dns-message (dns-message-id request-message) 'response 'query (if (in-bailiwick? name (rr-name soa-rr)) 'authoritative 'non-authoritative) 'not-truncated (dns-message-recursion-desired request-message) 'no-recursion-available (if send-name-error? 'name-error 'no-error) (dns-message-questions request-message) (rr-set->list answers) (rr-set->list authorities) (rr-set->list additional))) (define (answer-question q make-reply) ;; Notice that we claim to be authoritative for our configured ;; zone. If we ever answer name-error, that means there are no RRs ;; *at all* for the queried name. If there are RRs for the queried ;; name, but they happen not to be the ones asked for, name-error ;; must *not* be returned: instead, a normal no-error reply is ;; sent with an empty answer section. ;; ;; If we wanted to support caching of negative replies, we'd ;; follow the guidelines in section 4.3.4 "Negative response ;; caching" of RFC1034, adding our zone SOA with an appropriate ;; TTL to the additional section of the reply. ;; ;; TODO: We support returning out-of-bailiwick records (glue) ;; here. Reexamine the rules for doing so. (match-define (question qname qtype qclass #f) q) (define (expand-cnames worklist ans) (match worklist ['() (match-define (complete-answer ns us ds) ans) (make-reply qname #f ns us ds)] [(cons next-cname rest) (define a (resolve-from-zone (question next-cname qtype qclass q) zone soa-rr (set))) (incorporate-answer a rest ans)])) (define (incorporate-answer this-answer worklist ans) (match this-answer [(partial-answer new-info more-cnames) (expand-cnames (append worklist more-cnames) (merge-answers new-info ans))] [(? complete-answer?) (expand-cnames worklist (merge-answers this-answer ans))] [_ ;; #f or a referral (expand-cnames worklist ans)])) (match (resolve-from-zone q zone soa-rr (set)) [#f ;; Signal name-error/NXDOMAIN (make-reply qname #t (set) (set) (set))] [(referral _ ns-rrs additional) (make-reply qname #f ns-rrs (set soa-rr) additional)] [this-answer (incorporate-answer this-answer '() (empty-complete-answer))])) ;; TODO: check opcode and direction in request ;; TODO: think again about multiple questions in one packet (match (dns-message-questions request-message) ['() '()] [(cons q _) (list (dns-reply (answer-question q make-reply) request-sink request-source))])) (require "test-rrs.rkt") (start-server (test-port-number) test-soa-rr test-rrs)