feat(lisp/dns): Use new DNS deserialiser in dns:lookup-generic

This enables arbitrary DNS lookups (with the caveat that RRDATAs are
currently not deserialised into a record-type-specific format).

An error condition has been defined for error-responses from the HTTP
server which provides interactive restarts for attempting a new call
with different parameters.
This commit is contained in:
Vincent Ambo 2020-01-26 19:58:52 +00:00
parent 1440fc0dd7
commit 8f805a29d1

View file

@ -4,19 +4,65 @@
(in-package #:dns) (in-package #:dns)
;; The DoH client is configured with a URI Template [RFC6570] ;; The DoH client is configured with a URI Template [RFC6570]
(defvar *doh-base-url* "https://dns.google/dns-query" (defvar *doh-base-url* "https://dns.google/resolve"
"Base URL of the service providing DNS-over-HTTP(S). Defaults to the "Base URL of the service providing DNS-over-HTTP(S). Defaults to the
Google-hosted API.") Google-hosted API.")
(defun lookup-generic (name type) (define-condition doh-error (error)
(multiple-value-bind (stream) ((query-name :initarg :query-name
(drakma:http-request *doh-base-url* :reader doh-error-query-name
:type string)
(query-type :initarg :query-type
:reader doh-error-query-type
:type string)
(doh-url :initarg :doh-url
:reader doh-error-doh-url
:type string)
(status-code :initarg :status-code
:reader doh-error-status-code
:type integer)
(response-body :initarg :response-body
:reader doh-error-response-body
:type (or nil (vector (unsigned-byte 8)) string)))
(:report (lambda (condition stream)
(let ((url (doh-error-doh-url condition))
(status (doh-error-status-code condition))
(body (doh-error-response-body condition)))
(format stream "DoH service at '~A' responded with non-success (~A): ~%~%~A"
url status body)))))
(defun lookup-generic (name type &key (doh-url *doh-base-url*))
(multiple-value-bind (body status)
(drakma:http-request doh-url
:decode-content t :decode-content t
:want-stream t ;; TODO(tazjin): Figure out why 'want-stream' doesn't work
:parameters `(("type" . ,type) :parameters `(("type" . ,type)
("name" . ,name) ("name" . ,name)
("ct" . "application/dns-message"))) ("ct" . "application/dns-message")))
(read-binary 'dns-message stream))) (if (= 200 status)
(read-binary 'dns-message (flexi-streams:make-in-memory-input-stream body))
(restart-case (error 'doh-error
:query-name name
:query-type type
:doh-url doh-url
:status-code status
:response-body body)
(call-with-other-name (new-name)
:interactive (lambda () (list (the string (read))))
:test (lambda (c) (typep c 'doh-error))
(lookup-generic new-name type :doh-url doh-url))
(call-with-other-type (new-type)
:interactive (lambda () (list (the string (read))))
:test (lambda (c) (typep c 'doh-error))
(lookup-generic name new-type :doh-url doh-url))
(call-with-other-url (new-url)
:interactive (lambda () (list (the string (read))))
:test (lambda (c) (typep c 'doh-error))
(lookup-generic name type :doh-url new-url))))))
(defun lookup-txt (name) (defun lookup-txt (name)
"Look up the TXT records at NAME." "Look up the TXT records at NAME."
@ -25,149 +71,3 @@
(defun lookup-mx (name) (defun lookup-mx (name)
"Look up the MX records at NAME." "Look up the MX records at NAME."
(lookup-generic name "MX")) (lookup-generic name "MX"))
;; The URI Template defined in this document is processed without any
;; variables when the HTTP method is POST. When the HTTP method is GET,
;; the single variable "dns" is defined as the content of the DNS
;; request (as described in Section 6), encoded with base64url
;; [RFC4648].
;; When using the POST method, the DNS query is included as the message
;; body of the HTTP request, and the Content-Type request header field
;; indicates the media type of the message. POSTed requests are
;; generally smaller than their GET equivalents.
;; Using the GET method is friendlier to many HTTP cache
;; implementations.
;; The DoH client SHOULD include an HTTP Accept request header field to
;; indicate what type of content can be understood in response.
;; Irrespective of the value of the Accept request header field, the
;; client MUST be prepared to process "application/dns-message" (as
;; described in Section 6) responses but MAY also process other DNS-
;; related media types it receives.
;; In order to maximize HTTP cache friendliness, DoH clients using media
;; formats that include the ID field from the DNS message header, such
;; as "application/dns-message", SHOULD use a DNS ID of 0 in every DNS
;; request. HTTP correlates the request and response, thus eliminating
;; the need for the ID in a media type such as "application/dns-
;; message". The use of a varying DNS ID can cause semantically
;; equivalent DNS queries to be cached separately.
;; DoH clients can use HTTP/2 padding and compression [RFC7540] in the
;; same way that other HTTP/2 clients use (or don't use) them.
;; 4.1.1. HTTP Request Examples
;; These examples use HTTP/2-style formatting from [RFC7540].
;; These examples use a DoH service with a URI Template of
;; "https://dnsserver.example.net/dns-query{?dns}" to resolve IN A
;; records.
;; The requests are represented as bodies with media type "application/
;; dns-message".
;; The first example request uses GET to request "www.example.com".
;; :method = GET
;; :scheme = https
;; :authority = dnsserver.example.net
;; :path = /dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB
;; accept = application/dns-message
;; Finally, a GET-based query for "a.62characterlabel-makes-base64url-
;; distinct-from-standard-base64.example.com" is shown as an example to
;; emphasize that the encoding alphabet of base64url is different than
;; regular base64 and that padding is omitted.
;; The only response type defined in this document is "application/dns-
;; message", but it is possible that other response formats will be
;; defined in the future. A DoH server MUST be able to process
;; "application/dns-message" request messages.
;; Each DNS request-response pair is mapped to one HTTP exchange.
;; DNS response codes indicate either success or failure for the DNS
;; query. A successful HTTP response with a 2xx status code (see
;; Section 6.3 of [RFC7231]) is used for any valid DNS response,
;; HTTP responses with non-successful HTTP status codes do not contain
;; replies to the original DNS question in the HTTP request. DoH
;; clients need to use the same semantic processing of non-successful
;; HTTP status codes as other HTTP clients.
;; 4.2.2. HTTP Response Example
;; This is an example response for a query for the IN AAAA records for
;; "www.example.com" with recursion turned on. The response bears one
;; answer record with an address of 2001:db8:abcd:12:1:2:3:4 and a TTL
;; of 3709 seconds.
;; :status = 200
;; content-type = application/dns-message
;; content-length = 61
;; cache-control = max-age=3709
;; <61 bytes represented by the following hex encoding>
;; 00 00 81 80 00 01 00 01 00 00 00 00 03 77 77 77
;; 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 1c 00
;; 01 c0 0c 00 1c 00 01 00 00 0e 7d 00 10 20 01 0d
;; b8 ab cd 00 12 00 01 00 02 00 03 00 04
;; This protocol MUST be used with the https URI scheme [RFC7230].
;; In particular, DoH servers SHOULD assign an explicit HTTP freshness
;; lifetime (see Section 4.2 of [RFC7234]) so that the DoH client is
;; more likely to use fresh DNS data. This requirement is due to HTTP
;; caches being able to assign their own heuristic freshness (such as
;; that described in Section 4.2.2 of [RFC7234]), which would take
;; control of the cache contents out of the hands of the DoH server.
;; The assigned freshness lifetime of a DoH HTTP response MUST be less
;; than or equal to the smallest TTL in the Answer section of the DNS
;; response. A freshness lifetime equal to the smallest TTL in the
;; Answer section is RECOMMENDED. For example, if a HTTP response
;; carries three RRsets with TTLs of 30, 600, and 300, the HTTP
;; freshness lifetime should be 30 seconds (which could be specified as
;; "Cache-Control: max-age=30"). This requirement helps prevent expired
;; RRsets in messages in an HTTP cache from unintentionally being
;; served.
;; If the DNS response has no records in the Answer section, and the DNS
;; response has an SOA record in the Authority section, the response
;; freshness lifetime MUST NOT be greater than the MINIMUM field from
;; that SOA record (see [RFC2308]).
;; DoH clients MUST account for the Age response header field's value
;; [RFC7234] when calculating the DNS TTL of a response. For example,
;; if an RRset is received with a DNS TTL of 600, but the Age header
;; field indicates that the response has been cached for 250 seconds,
;; the remaining lifetime of the RRset is 350 seconds. This requirement
;; applies to both DoH client HTTP caches and DoH client DNS caches.
;; Those features were introduced to HTTP in HTTP/2 [RFC7540].
;; Earlier versions of HTTP are capable of conveying the semantic
;; requirements of DoH but may result in very poor performance.
;; In order to maximize interoperability, DoH clients and DoH servers
;; MUST support the "application/dns-message" media type.
;; The data payload for the "application/dns-message" media type is a
;; single message of the DNS on-the-wire format defined in Section 4.2.1
;; of [RFC1035], which in turn refers to the full wire format defined in
;; Section 4.1 of that RFC.
;; This media type restricts the maximum size of the DNS message to
;; 65535 bytes.
;; When using the GET method, the data payload for this media type MUST
;; be encoded with base64url [RFC4648] and then provided as a variable
;; named "dns" to the URI Template expansion. Padding characters for
;; base64url MUST NOT be included.
;; When using the POST method, the data payload for this media type MUST
;; NOT be encoded and is used directly as the HTTP message body.