(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. ;; is a domain name represented as a series of labels, and ;; terminated by a label with zero length. is a single ;; length octet followed by that number of characters. ;; 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 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 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 of the name server that was the ;; original or primary source of data for this zone. ;; RNAME A 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 s. ((TXT) '(counted-string 1)) ;; A 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 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)))))