about summary refs log tree commit diff
path: root/lisp/dns/message.lisp
blob: 46243ac8d3f006c0b6ab46020071d29cd83b51dc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
(in-package :dns)

;; 3.3. Standard RRs

;; The following RR definitions are expected to occur, at least
;; potentially, in all classes.  In particular, NS, SOA, CNAME, and PTR
;; will be used in all classes, and have the same format in all classes.
;; Because their RDATA format is known, all domain names in the RDATA
;; section of these RRs may be compressed.

;; <domain-name> is a domain name represented as a series of labels, and
;; terminated by a label with zero length.  <character-string> is a single
;; length octet followed by that number of characters.  <character-string>
;; is treated as binary information, and can be up to 256 characters in
;; length (including the length octet).


;; 3.3.11. NS RDATA format

;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     /                   NSDNAME                     /
;;     /                                               /
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

;; where:

;; NSDNAME         A <domain-name> which specifies a host which should be
;;                 authoritative for the specified class and domain.

;; NS records cause both the usual additional section processing to locate
;; a type A record, and, when used in a referral, a special search of the
;; zone in which they reside for glue information.

;; The NS RR states that the named host should be expected to have a zone
;; starting at owner name of the specified class.  Note that the class may
;; not indicate the protocol family which should be used to communicate
;; with the host, although it is typically a strong hint.  For example,
;; hosts which are name servers for either Internet (IN) or Hesiod (HS)
;; class information are normally queried using IN class protocols.

;; 3.3.12. PTR RDATA format

;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     /                   PTRDNAME                    /
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

;; where:

;; PTRDNAME        A <domain-name> which points to some location in the
;;                 domain name space.

;; PTR records cause no additional section processing.  These RRs are used
;; in special domains to point to some other location in the domain space.
;; These records are simple data, and don't imply any special processing
;; similar to that performed by CNAME, which identifies aliases.  See the
;; description of the IN-ADDR.ARPA domain for an example.

;; 3.3.13. SOA RDATA format

;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     /                     MNAME                     /
;;     /                                               /
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     /                     RNAME                     /
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     |                    SERIAL                     |
;;     |                                               |
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     |                    REFRESH                    |
;;     |                                               |
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     |                     RETRY                     |
;;     |                                               |
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     |                    EXPIRE                     |
;;     |                                               |
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     |                    MINIMUM                    |
;;     |                                               |
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

;; where:

;; MNAME           The <domain-name> of the name server that was the
;;                 original or primary source of data for this zone.

;; RNAME           A <domain-name> which specifies the mailbox of the
;;                 person responsible for this zone.

;; SERIAL          The unsigned 32 bit version number of the original copy
;;                 of the zone.  Zone transfers preserve this value.  This
;;                 value wraps and should be compared using sequence space
;;                 arithmetic.

;; REFRESH         A 32 bit time interval before the zone should be
;;                 refreshed.

;; RETRY           A 32 bit time interval that should elapse before a
;;                 failed refresh should be retried.

;; EXPIRE          A 32 bit time value that specifies the upper limit on
;;                 the time interval that can elapse before the zone is no
;;                 longer authoritative.

;; MINIMUM         The unsigned 32 bit minimum TTL field that should be
;;                 exported with any RR from this zone.

;; SOA records cause no additional section processing.

;; All times are in units of seconds.

;; Most of these fields are pertinent only for name server maintenance
;; operations.  However, MINIMUM is used in all query operations that
;; retrieve RRs from a zone.  Whenever a RR is sent in a response to a
;; query, the TTL field is set to the maximum of the TTL field from the RR
;; and the MINIMUM field in the appropriate SOA.  Thus MINIMUM is a lower
;; bound on the TTL field for all RRs in a zone.  Note that this use of
;; MINIMUM should occur when the RRs are copied into the response and not
;; when the zone is loaded from a master file or via a zone transfer.  The
;; reason for this provison is to allow future dynamic update facilities to
;; change the SOA RR with known semantics.


;; 3.3.14. TXT RDATA format

;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
;;     /                   TXT-DATA                    /
;;     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

;; where:

;; TXT-DATA

;; TXT RRs are used to hold descriptive text.  The semantics of the text
;; depends on the domain where it is found.

(defbinary dns-header (:byte-order :big-endian)
           ;; A 16 bit identifier assigned by the program that
           ;; generates any kind of query. This identifier is copied
           ;; the corresponding reply and can be used by the requester
           ;; to match up replies to outstanding queries.
           (id 0 :type 16)

           ;; A one bit field that specifies whether this message is a
           ;; query (0), or a response (1).
           (qr 0 :type 1)

           ;; A four bit field that specifies kind of query in this
           ;; message. This value is set by the originator of a query
           ;; and copied into the response. The values are:
           ;;
           ;; 0               a standard query (QUERY)
           ;; 1               an inverse query (IQUERY)
           ;; 2               a server status request (STATUS)
           ;; 3-15            reserved for future use
           (opcode 0 :type 4)

           ;; Authoritative Answer - this bit is valid in responses,
           ;; and specifies that the responding name server is an
           ;; authority for the domain name in question section.
           (aa nil :type 1)

           ;; TrunCation - specifies that this message was truncated
           ;; due to length greater than that permitted on the
           ;; transmission channel.
           (tc nil :type 1)

           ;; Recursion Desired - this bit may be set in a query and
           ;; is copied into the response.  If RD is set, it directs
           ;; the name server to pursue the query recursively.
           ;; Recursive query support is optional.
           (rd nil :type 1)

           ;; Recursion Available - this be is set or cleared in a
           ;; response, and denotes whether recursive query support is
           ;; available in the name server.
           (ra nil :type 1)

           ;; Reserved for future use. Must be zero in all queries and
           ;; responses.
           (z 0 :type 3)

           ;; Response code - this 4 bit field is set as part of
           ;; responses.  The values have the following
           ;; interpretation:
           ;; 0               No error condition
           ;; 1               Format error - The name server was
           ;;                 unable to interpret the query.
           ;; 2               Server failure - The name server was
           ;;                 unable to process this query due to a
           ;;                 problem with the name server.
           ;; 3               Name Error - Meaningful only for
           ;;                 responses from an authoritative name
           ;;                 server, this code signifies that the
           ;;                 domain name referenced in the query does
           ;;                 not exist.
           ;; 4               Not Implemented - The name server does
           ;;                 not support the requested kind of query.
           ;; 5               Refused - The name server refuses to
           ;;                 perform the specified operation for
           ;;                 policy reasons.  For example, a name
           ;;                 server may not wish to provide the
           ;;                 information to the particular requester,
           ;;                 or a name server may not wish to perform
           ;;                 a particular operation (e.g., zone
           ;;                 transfer) for particular data.
           ;; 6-15            Reserved for future use.
           (rcode 0 :type 4)

           ;; an unsigned 16 bit integer specifying the number of
           ;; entries in the question section.
           (qdcount 0 :type 16)

           ;; an unsigned 16 bit integer specifying the number of
           ;; resource records in the answer section.
           (ancount 0 :type 16)

           ;; an unsigned 16 bit integer specifying the number of name
           ;; server resource records in the authority records
           ;; section.
           (nscount 0 :type 16)

           ;; an unsigned 16 bit integer specifying the number of
           ;; resource records in the additional records section.
           (arcount 0 :type 16))


;; Representation of DNS QNAMEs.
;;
;; A QNAME can be either made up entirely of labels, which is
;; basically a list of strings, or be terminated with a pointer to an
;; offset within the original message.

(deftype qname-field ()
  '(or
    ;; pointer
    (unsigned-byte 14)
    ;; label
    string))

(defstruct qname
  (start-at 0 :type (unsigned-byte 14))
  (names #() :type (vector qname-field)))

;; Domain names in questions and resource records are represented as a
;; sequence of labels, where each label consists of a length octet
;; followed by that number of octets.
;;
;; The domain name terminates with the zero length octet for the null
;; label of the root. Note that this field may be an odd number of
;; octets; no padding is used.
(declaim (ftype (function (stream) (values qname integer)) read-qname))
(defun read-qname (stream)
  "Reads a DNS QNAME from STREAM."

  (let ((start-at (file-position stream)))
    (iter (for byte next (read-byte stream))
      ;; Each fragment is collected into this byte vector pre-allocated
      ;; with the correct size.
      (for fragment = (make-array byte :element-type '(unsigned-byte 8)
                                       :fill-pointer 0))

      ;; If the bit sequence (1 1) is encountered at the beginning of
      ;; the fragment, a qname pointer is being read.
      (let ((byte-copy byte))
        (when (equal #b11 (lisp-binary/integer:pop-bits 2 8 byte-copy))
          (let ((next (read-byte stream)))
            (lisp-binary/integer:push-bits byte-copy 8 next)
            (collect next into fragments result-type vector)
            (sum 2 into size)
            (finish))))

      ;; Total size is needed, count for each iteration byte, plus its
      ;; own value.
      (sum (+ 1 byte) into size)
      (until (equal byte 0))

      ;; On each iteration, this will interpret the current byte as an
      ;; unsigned integer and read from STREAM an equivalent amount of
      ;; times to assemble the current fragment.
      ;;
      ;; Advancing the stream like this also ensures that the next
      ;; iteration occurs on a new fragment or the final terminating
      ;; byte.
      (dotimes (_ byte (collect (babel:octets-to-string fragment)
                         into fragments result-type vector))
        (vector-push (read-byte stream) fragment))

      (finally (return (values (make-qname :start-at start-at
                                           :names fragments)
                               size))))))

(declaim (ftype (function (stream qname)) write-qname))
(defun write-qname (stream qname)
  "Write a DNS qname to STREAM."

  ;; Write each fragment starting with its (byte-) length, followed by
  ;; the bytes.
  (iter (for fragment in-vector (qname-names qname))
    (for bytes = (babel:string-to-octets fragment))
    (write-byte (length bytes) stream)
    (iter (for byte in-vector bytes)
      (write-byte byte stream)))

  ;; Always finish off the serialisation with a null-byte!
  (write-byte 0 stream))

(define-enum dns-type 2
    (:byte-order :big-endian)

    ;; http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
    (A 1)
    (NS 2)
    (CNAME 5)
    (SOA 6)
    (PTR 12)
    (MX 15)
    (TXT 16)
    (SRV 33)
    (AAAA 28)

    ;; ANY typically wants SOA, MX, NS and MX
    (ANY 255))

(defbinary dns-question (:byte-order :big-endian :export t)
           ;; a domain name represented
           (qname "" :type (custom :lisp-type qname
                                   :reader #'read-qname
                                   :writer #'write-qname))

           ;; a two octet code which specifies the type of the query.
           (qtype 0 :type dns-type)

           ;; a two octet code that specifies the class of the query. For
           ;; example, the QCLASS field is IN for the Internet.
           (qclass 0 :type 16))

(defbinary dns-rr (:byte-order :big-endian :export t)
           (name nil :type (custom :lisp-type qname
                                   :reader #'read-qname
                                   :writer #'write-qname))

           ;; two octets containing one of the RR type codes. This field
           ;; specifies the meaning of the data in the RDATA field.
           (type 0 :type dns-type)

           ;; two octets which specify the class of the data in the RDATA
           ;; field.
           (class 0 :type 16)

           ;; a 32 bit unsigned integer that specifies the time interval (in
           ;; seconds) that the resource record may be cached before it should
           ;; be discarded. Zero values are interpreted to mean that the RR
           ;; can only be used for the transaction in progress, and should not
           ;; be cached.
           (ttl 0 :type 32)

           ;; an unsigned 16 bit integer that specifies the length in octets
           ;; of the RDATA field.
           (rdlength 0 :type 16)

           ;; a variable length string of octets that describes the resource.
           ;; The format of this information varies according to the TYPE and
           ;; CLASS of the resource record. For example, the if the TYPE is A
           ;; and the CLASS is IN, the RDATA field is a 4 octet ARPA Internet
           ;; address.
           (rdata #() :type (eval (case type
                                    ;; A 32-bit internet address in its
                                    ;; canonical representation of 4 integers.
                                    ((A) '(simple-array (unsigned-byte 8) (4)))

                                    ;; TODO(tazjin): Deal with multiple strings in single RRDATA
                                    ;; One or more <character-string>s.
                                    ((TXT) '(counted-string 1))

                                    ;; A <domain-name> which specifies the
                                    ;; canonical or primary name for the
                                    ;; owner. The owner name is an alias.
                                    ((CNAME) '(custom
                                               :lisp-type qname
                                               :reader #'read-qname
                                               :writer #'write-qname))

                                    ;; A <domain-name> which specifies a host
                                    ;; which should be authoritative for the
                                    ;; specified class and domain.
                                    ((NS) '(custom
                                            :lisp-type qname
                                            :reader #'read-qname
                                            :writer #'write-qname))
                                    (otherwise `(simple-array (unsigned-byte 8) (,rdlength)))))))

(defbinary dns-message (:byte-order :big-endian :export t)
           (header nil :type dns-header)

           ;; the question for the name server
           (question #() :type (simple-array dns-question ((dns-header-qdcount header))))

           ;; ;; RRs answering the question
           ;; (answer #() :type (simple-array (unsigned-byte 8) (16)))
           (answer #() :type (simple-array dns-rr ((dns-header-ancount header))))

           ;; ;; ;; RRs pointing toward an authority
           (authority #() :type (simple-array dns-rr ((dns-header-nscount header))))

           ;; ;; RRs holding additional information
           (additional #() :type (simple-array dns-rr ((dns-header-arcount header)))))