408 lines
17 KiB
Common Lisp
408 lines
17 KiB
Common Lisp
|
(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)))))
|