Switch to the MIT licence.
[iolib.git] / net.sockets / dns / query.lisp
blob3e57645819e03d7aa9bcc43d09bbf1a27b844f8f
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; indent-tabs-mode: nil -*-
2 ;;;
3 ;;; --- Make DNS queries.
4 ;;;
6 (in-package :net.sockets)
8 (defvar *dns-recursion-desired* t
9 "Whether the \"RECURSION-DESIRED\" field should be set ot not.")
11 (defvar *dns-repeat* 3
12 "The number of times a failed query will be retried.")
14 (defvar *dns-timeout* 10
15 "Timeout for DNS queries in seconds.")
17 (define-constant +max-16-bits+ (1- (expt 2 16)))
19 (defun prepare-query (name type)
20 (let* ((question (make-question name type :in))
21 (query (make-query (random +max-16-bits+)
22 question *dns-recursion-desired*)))
23 (write-dns-message query)))
25 (defun reverse-vector (vector)
26 (let* ((vector-length (length vector))
27 (reverse-vector
28 (make-array vector-length
29 :element-type (array-element-type vector))))
30 (loop :for target-index :below vector-length
31 :for source-index := (- vector-length target-index 1)
32 :do (setf (aref reverse-vector target-index)
33 (aref vector source-index)))
34 (values reverse-vector)))
36 (defun ipv4-dns-ptr-name (address)
37 (declare (type ipv4-array address))
38 (concatenate 'string (vector-to-dotted (reverse-vector address))
39 ".in-addr.arpa."))
41 (defun ipv6-vector-to-dotted (vector)
42 (declare (type ipv6-array vector))
43 (with-standard-io-syntax
44 (let ((*print-base* 16))
45 (with-output-to-string (dotted-address)
46 (loop :for index :below (length vector)
47 :for element := (aref vector index) :do
48 (when (plusp index)
49 (princ #\. dotted-address))
50 (princ (ldb (byte 4 0) element) dotted-address)
51 (princ #\. dotted-address)
52 (princ (ldb (byte 4 4) element) dotted-address)
53 (princ #\. dotted-address)
54 (princ (ldb (byte 4 8) element) dotted-address)
55 (princ #\. dotted-address)
56 (princ (ldb (byte 4 12) element) dotted-address))))))
58 (defun ipv6-dns-ptr-name (address)
59 (declare (type (simple-array ub16 (8)) address))
60 (concatenate 'string (ipv6-vector-to-dotted (reverse-vector address))
61 ".ip6.arpa."))
63 (defun dns-ptr-name (address)
64 (multiple-value-bind (vector address-type)
65 (address-to-vector address)
66 (when (null address)
67 (error "The argument is not a valid IP address"))
68 (ecase address-type
69 (:ipv4 (ipv4-dns-ptr-name vector))
70 (:ipv6 (ipv6-dns-ptr-name vector)))))
72 ;;;; Resource Record Decoding
74 (defgeneric %decode-rr (rr type class))
76 (defmethod %decode-rr ((rr dns-rr) type class)
77 (declare (ignore type class))
78 (cons (dns-rr-ttl rr) (dns-rr-data rr)))
80 (defmethod %decode-rr ((rr dns-rr) (type (eql :cname)) class)
81 (declare (ignore class))
82 (let ((cname (dns-rr-data rr)))
83 (cons (dns-rr-ttl rr)
84 (subseq cname 0 (1- (length cname))))))
86 (defmethod %decode-rr ((rr dns-rr) (type (eql :a)) (class (eql :in)))
87 (let ((address (dns-rr-data rr)))
88 (cons (dns-rr-ttl rr)
89 (make-address address))))
91 (defmethod %decode-rr ((rr dns-rr) (type (eql :aaaa)) (class (eql :in)))
92 (let ((address (dns-rr-data rr)))
93 (cons (dns-rr-ttl rr)
94 (make-address address))))
96 (defmethod %decode-rr ((rr dns-rr) (type (eql :ptr)) class)
97 (declare (ignore class))
98 (let ((name (dns-rr-data rr)))
99 (cons (dns-rr-ttl rr)
100 (subseq name 0 (1- (length name))))))
102 (defmethod %decode-rr ((rr dns-rr) (type (eql :mx)) class)
103 (declare (ignore class))
104 (destructuring-bind (preference name) (dns-rr-data rr)
105 (cons (dns-rr-ttl rr)
106 (cons preference
107 (subseq name 0 (1- (length name)))))))
109 (defun decode-rr (rr)
110 (%decode-rr rr (dns-record-type rr) (dns-record-class rr)))
112 ;;;; Response Decoding
114 (defgeneric %decode-response (dns-message question-type))
116 (defmethod %decode-response :around ((msg dns-message) question-type)
117 (declare (ignore question-type))
118 (let ((return-code (rcode-field msg)))
119 (if (eq :no-error return-code) ; no error
120 (call-next-method)
121 (values return-code))))
123 (defun remove-trailing-dot (string)
124 (assert (>= (length string) 2) (string)
125 "String length must be at least 2: ~S" string)
126 (assert (char= #\. (char string (1- (length string)))) (string)
127 "Must end with a dot: ~S" string)
128 (subseq string 0 (1- (length string))))
130 (defun find-cname (msg)
131 (let ((answer (dns-message-answer msg))
132 (answer-count (dns-message-answer-count msg))
133 (cnames (make-hash-table :test 'equal :size 3))
134 (consumed 0))
135 (loop :for i :below answer-count
136 :for ans := (aref answer i) :do
137 (if (eq :cname (dns-record-type ans))
138 (setf (gethash (dns-record-name ans) cnames)
139 (dns-rr-data ans))
140 (loop-finish))
141 :finally (setf consumed i))
142 (do ((cname (dns-record-name (aref (dns-message-question msg) 0)))
143 (exit nil))
144 (exit (values (remove-trailing-dot cname) consumed))
145 (let ((name (gethash cname cnames)))
146 (cond (name
147 (remhash cname cnames)
148 (setf cname name))
149 (t (setf exit t)))))))
151 (defun decode-a-or-aaaa-response (msg)
152 (declare (type dns-message msg))
153 (let ((answer (dns-message-answer msg))
154 (answer-count (dns-message-answer-count msg))
155 (cname nil)
156 (first-address-place 0)
157 (first-address nil)
158 (other-addresses nil))
159 ;; when the address is valid(we have at least one answer)
160 (when (plusp answer-count)
161 (setf (values cname first-address-place) (find-cname msg))
162 ;; this means the message actually contains addresses
163 (when (> (dns-message-answer-count msg) first-address-place)
164 (setf first-address (decode-rr (aref answer first-address-place))))
165 (setf other-addresses
166 (loop :for i :from (1+ first-address-place)
167 :below (dns-message-answer-count msg)
168 :collect (decode-rr (aref answer i)))))
169 (values cname first-address other-addresses)))
171 (defmethod %decode-response ((msg dns-message) (question-type (eql :a)))
172 (declare (ignore question-type))
173 (decode-a-or-aaaa-response msg))
175 (defmethod %decode-response ((msg dns-message) (question-type (eql :aaaa)))
176 (declare (ignore question-type))
177 (decode-a-or-aaaa-response msg))
179 (defmethod %decode-response ((msg dns-message) (question-type (eql :ptr)))
180 (declare (ignore question-type))
181 (decode-rr (aref (dns-message-answer msg) 0)))
183 ;; TODO: got a lot to do here
184 (defmethod %decode-response ((msg dns-message) (question-type (eql :mx)))
185 (declare (ignore question-type))
186 (let ((rr (aref (dns-message-answer msg) 0)))
187 (decode-rr rr)))
189 (defmethod %decode-response ((msg dns-message) (question-type (eql :txt)))
190 (declare (ignore question-type))
191 (decode-rr (aref (dns-message-answer msg) 0)))
193 (defmethod %decode-response ((msg dns-message) question-type)
194 (declare (ignore question-type))
195 (values msg))
197 (defun decode-response (message)
198 (%decode-response message
199 (dns-record-type
200 (aref (dns-message-question message) 0))))
202 ;;;; DNS-QUERY
204 (defconstant +dns-port+ 53)
206 (defun dns-query/udp (buffer length nameserver timeout)
207 (with-open-socket
208 (socket :type :datagram :ipv6 (ipv6-address-p nameserver)
209 :remote-host nameserver :remote-port +dns-port+)
210 (send-to socket buffer :end length)
211 (iomux:wait-until-fd-ready (fd-of socket) :read timeout t)
212 (multiple-value-bind (buf len)
213 (receive-from socket :size +dns-max-datagram-size+)
214 (values buf len))))
216 (defun wait-until-socket-connected (socket timeout)
217 (if (nth-value 1 (iomux:wait-until-fd-ready (fd-of socket) :write timeout))
218 (let ((errcode (socket-option socket :error)))
219 (when (minusp errcode) (signal-socket-error)))
220 (error 'socket-connection-timeout-error)))
222 (defun send-tcp-dns-query (socket buffer length)
223 (let ((minibuf (make-array (+ length 2) :element-type 'ub8)))
224 ;; two-octet length prefix
225 (replace minibuf (ub16-to-vector length))
226 (replace minibuf buffer :start1 2 :end2 length)
227 (send-to socket minibuf :end (+ length 2))))
229 (defun get-tcp-query-length (socket timeout)
230 (iomux:wait-until-fd-ready (fd-of socket) :read timeout t)
231 (multiple-value-bind (minibuf)
232 (receive-from socket :size 2)
233 (+ (ash (aref minibuf 0) 8)
234 (aref minibuf 1))))
236 (defun receive-tcp-dns-message (socket time-fn)
237 (with-accessors ((fd fd-of)) socket
238 (let* ((message-length (get-tcp-query-length socket (funcall time-fn)))
239 (input-buffer (make-array message-length :element-type 'ub8)))
240 (loop :with off := 0 :do
241 (iomux:wait-until-fd-ready fd :read (funcall time-fn) t)
242 (let ((inbytes (nth-value 1 (receive-from socket :buffer input-buffer :start off))))
243 (incf off inbytes)
244 (when (= off message-length)
245 (return (values input-buffer off))))))))
247 (defun dns-query/tcp (buffer length nameserver timeout)
248 (let* ((t0 (osicat-sys:get-monotonic-time))
249 (tend (+ t0 timeout)))
250 (flet ((remtime ()
251 (let ((rem (- tend (osicat-sys:get-monotonic-time))))
252 (if (not (minusp rem)) rem
253 (error 'socket-connection-timeout-error)))))
254 (with-open-socket
255 (socket :connect :active :type :stream
256 :ipv6 (ipv6-address-p nameserver))
257 (setf (fd-non-blocking socket) t)
258 (handler-case
259 (connect socket nameserver :port +dns-port+)
260 (socket-connection-in-progress-error ()
261 (wait-until-socket-connected socket (remtime))))
262 (send-tcp-dns-query socket buffer length)
263 (receive-tcp-dns-message socket #'remtime)))))
265 (defun do-one-dns-query (name type search ns repeat timeout)
266 (declare (optimize (debug 3)))
267 ;; TODO: implement search
268 (declare (ignore search))
269 (let* ((query (prepare-query name type))
270 (buffer (sequence-of query))
271 (bufflen (write-cursor-of query))
272 (tries-left repeat))
273 (labels
274 ((start (protocol)
275 ;; if the query size fits into a datagram(512 bytes max) do a
276 ;; UDP query, otherwise use TCP
277 (ecase protocol
278 (:udp (query/udp))
279 (:tcp (query/tcp))))
280 (query/udp ()
281 ;; do a UDP query; in case of a socket error, try again
282 (handler-case
283 (dns-query/udp buffer bufflen ns timeout)
284 (socket-error () (%error "UDP socket error"))
285 (iomux:poll-timeout () (try-again :udp))
286 (:no-error (buf bytes) (parse-response buf bytes))))
287 (query/tcp ()
288 ;; do a TCP query; in case of a socket error, try again
289 (handler-case
290 (dns-query/tcp buffer bufflen ns timeout)
291 (socket-connection-timeout-error () (try-again :tcp))
292 (socket-error () (%error "TCP socket error"))
293 (iomux:poll-timeout () (try-again :tcp))
294 (:no-error (buf bytes) (parse-response buf bytes t))))
295 (parse-response (buf bytes &optional on-tcp)
296 ;; try to parse the response; in case of a parse error, try again
297 (handler-case
298 (read-dns-message (make-instance 'dynamic-buffer :sequence buf :size bytes))
299 (dynamic-buffer-input-error () (try-again :tcp))
300 (dns-message-error () (try-again :tcp))
301 (:no-error (response)
302 ;; if a truncated response was received by UDP, try TCP
303 ;; if it was received by TCP, err
304 (if (truncated-field response)
305 (if on-tcp (%error "TCP truncated messae") (try-again :tcp))
306 (return-response response)))))
307 (try-again (protocol)
308 ;; if no response received and there are tries left, try again
309 (if (plusp (decf tries-left)) (start protocol) (%error "No more retries left")))
310 (return-response (response) response)
311 (%error (&optional cause) (declare (ignore cause))))
312 (start :udp))))
314 (defun preprocess-dns-name (name type)
315 (if (eq :ptr type)
316 (dns-ptr-name name)
317 name))
319 (defun dns-query (name &key (type :a) (search *dns-search-domain*)
320 (nameservers *dns-nameservers*) decode
321 (repeat *dns-repeat*) (timeout *dns-timeout*))
322 (setf nameservers (ensure-list nameservers))
323 (assert nameservers (nameservers) "Must supply a nameserver")
324 (let ((pname (preprocess-dns-name name type)))
325 (dolist (ns (mapcar #'ensure-address nameservers))
326 (when-let ((response (do-one-dns-query pname type search
327 ns repeat timeout)))
328 (return-from dns-query
329 (if decode (decode-response response) response))))))