diff --git a/test-dns.rkt b/test-dns.rkt index eada2da..78f8c2d 100644 --- a/test-dns.rkt +++ b/test-dns.rkt @@ -449,3 +449,21 @@ (check-equal? (compile-zone-db test-roots) (bit-string->zone (zone->bit-string (compile-zone-db test-roots)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CNAME sorting + +(let () + (define rrs + (list (rr (domain '(#"a")) 'cname 'in 30 (domain '(#"b"))) + (rr (domain '(#"b")) 'cname 'in 30 (domain '(#"c"))) + (rr (domain '(#"c")) 'cname 'in 30 (domain '(#"d"))))) + (define (check-transpose ns) + (define permuted (map (lambda (i) (list-ref rrs i)) ns)) + (check-equal? (cname-sort permuted) rrs)) + (check-transpose '(0 1 2)) + (check-transpose '(0 2 1)) + (check-transpose '(2 0 1)) + (check-transpose '(2 1 0)) + (check-transpose '(1 2 0)) + (check-transpose '(1 0 2))) diff --git a/zonedb.rkt b/zonedb.rkt index 7d7f329..eb4caa1 100644 --- a/zonedb.rkt +++ b/zonedb.rkt @@ -21,6 +21,7 @@ filter-by-type filter-rrs rr-set->list + cname-sort ;; provided for unit tests zone->bit-string bit-string->zone) @@ -185,7 +186,7 @@ ;; Like set->list, but places all CNAME records first. ;; This is apparently to work around bugs in old versions of BIND? ;; -;; TODO: Perhaps the CNAMEs even need to be in topologically-sorted order? +;; The CNAMEs even need to be in topologically-sorted order. ;; http://homepage.ntlworld.com./jonathan.deboynepollard/FGA/dns-response-taxonomy.html ;; has this to say on this topic: ;; "A content DNS server following the algorithm in ยง 4.3.2 of RFC @@ -193,10 +194,41 @@ ;; response. The response parsing code in most resolving proxy DNS ;; servers and DNS client libraries expects this order. However, ;; the actual text of RFC 1034 itself does not guarantee it." +;; Sure enough, the resolver in Firefox seems not to be able to handle +;; CNAMEs in any order other than strictly causal. While we could be +;; more careful about retaining the ordering of RRs all the way +;; through the resolution and CNAME expansion processes, that would +;; pollute the logic with a bunch of noise about RR order which isn't +;; even supposed to be relevant. So we *recover* the order here, which +;; is a bit expensive. (define (rr-set->list rrs) - (append (set->list (filter-by-type rrs 'cname)) + (append (cname-sort (set->list (filter-by-type rrs 'cname))) (set->list (set-filter (lambda (rr) (not (eqv? (rr-type rr) 'cname))) rrs)))) +;; cname-sort : ListOf> -> ListOf> +;; Produce an ordering of the CNAMEs given that respects their +;; "causality". For example, if a CNAME b and b CNAME c, then the RRs +;; will be presented in that order (and not the other order, with b +;; CNAME c first). +(define (cname-sort cnames) + (define lhss (list->set (map rr-name cnames))) + (define rhss (list->set (map rr-rdata cnames))) + (define roots (set->list (set-subtract lhss rhss))) ;; Nodes not the targets of some edge. + (define (targets-of name) (for/list [(rr cnames) #:when (equal? (rr-name rr) name)] rr)) + (let iterate ((remaining roots) + (seen (set)) + (acc '())) + (if (null? remaining) + (reverse acc) + (let ((source (car remaining))) + (if (set-member? seen source) + (iterate (cdr remaining) seen acc) + (let* ((rrs (targets-of source)) + (targets (map rr-rdata rrs))) + (iterate (append targets (cdr remaining)) + (set-add seen source) + (append rrs acc)))))))) + ;; CompiledZone -> Bitstring ;; Produces a serialized form of the zone suitable for saving to disk. (define (zone->bit-string zone)