--- /dev/null
+;;;; DNS implementation for COMMON-NET
+
+(in-package :common-net)
+
+(defstruct dns-packet
+ (txid (random 65536) :type (unsigned-byte 16))
+ (is-response nil)
+ (opcode :query :type (member :query :iquery :status))
+ (authoritative nil)
+ (truncated nil)
+ (recurse nil)
+ (will-recurse nil)
+ (resp-code :success :type (member :success :format-error :server-failure :name-error :not-implemented :refused))
+ (queries '() :type list)
+ (answers '() :type list)
+ (authority '() :type list)
+ (additional '() :type list))
+
+(defclass resource-query ()
+ ((name :initarg :name)
+ (type :initarg :type)))
+
+(defclass resource-record ()
+ ((name :initarg :name)
+ (ttl :initarg :ttl)))
+
+(defvar *rr-coding-types* '())
+
+(defmacro define-rr-type (name class type slots)
+ (let ((format (mapcar #'(lambda (slot)
+ (list* (if (listp (car slot))
+ (caar slot)
+ (car slot))
+ (cdr slot)))
+ slots))
+ (slot-desc (mapcar #'car slots)))
+ `(progn
+ (defclass ,name (resource-record) ,slot-desc)
+ (setf *rr-coding-types* (cons '(,name (,class ,type) ,format)
+ (remove ',name *rr-coding-types* :key #'car))))))
+
+(define-rr-type a-record #x1 #x1
+ ((address ipv4-address)))
+(define-rr-type ns-record #x1 #x2
+ ((ns-name domain-name)))
+(define-rr-type cname-record #x1 #x5
+ ((cname domain-name)))
+(define-rr-type soa-record #x1 #x6
+ ((mname domain-name)
+ (rname domain-name)
+ (serial uint-32)
+ (refresh uint-32)
+ (retry uint-32)
+ (expire uint-32)))
+(define-rr-type ptr-record #x1 #xc
+ ((pointed domain-name)))
+(define-rr-type mx-record #x1 #xf
+ ((prio uint-16)
+ (mail-host domain-name)))
+(define-rr-type txt-record #x1 #x10
+ ((text text)))
+(define-rr-type aaaa-record #x1 #x1c
+ ((address ipv6-address)))
+(define-rr-type srv-record #x1 #x21
+ ((prio uint-16)
+ (weigth uint-16)
+ (port uint-16)
+ (host-name domain-name)))
+
+;;; Packet decoding logic
+
+(defstruct dns-decode-state
+ (packet nil :type (array (unsigned-byte 8)))
+ (pos 0 :type (mod 65536))
+ (prev-names '() :type list))
+
+(define-condition dns-error (error) ())
+(define-condition dns-decode-error (dns-error)
+ ((packet :initarg :packet)))
+(define-condition simple-dns-decode-error (dns-decode-error simple-error) ())
+
+(defun simple-dns-decode-error (packet format &rest args)
+ (error 'simple-dns-decode-error :packet packet :format-control format :format-argument args))
+
+(defun decode-uint-8 (buf)
+ (declare (type dns-decode-state buf))
+ (with-slots (packet pos) buf
+ (when (< (- (length packet) pos) 1)
+ (simple-dns-decode-error buf "DNS packet is too short (wanted a 8-bit number)."))
+ (prog1 (aref packet pos)
+ (incf pos))))
+
+(defun decode-uint-16 (buf)
+ (declare (type dns-decode-state buf))
+ (with-slots (packet pos) buf
+ (when (< (- (length packet) pos) 2)
+ (simple-dns-decode-error buf "DNS packet is too short (wanted a 16-bit number)."))
+ (prog1
+ (+ (* (aref packet pos) 256)
+ (aref packet (1+ pos)))
+ (incf pos 2))))
+
+(defun decode-uint-32 (buf)
+ (declare (type dns-decode-state buf))
+ (with-slots (packet pos) buf
+ (when (< (- (length packet) pos) 4)
+ (simple-dns-decode-error buf "DNS packet is too short (wanted a 32-bit number)."))
+ (prog1
+ (+ (* (aref packet pos) #x1000000)
+ (* (aref packet (+ pos 1)) #x10000)
+ (* (aref packet (+ pos 2)) #x100)
+ (aref packet (+ pos 3)))
+ (incf pos 2))))
+
+(defun decode-domain-name (buf)
+ (declare (type dns-decode-state buf))
+ (let* ((orig-off (dns-decode-state-pos buf))
+ (decoded (block decoded
+ (let ((l '()))
+ (loop (let ((len (decode-uint-8 buf)))
+ (case (ldb (byte 2 6) len)
+ ((0)
+ (when (zerop len)
+ (return-from decoded l))
+ (with-slots (packet pos) buf
+ (setf l (append l (list (handler-bind
+ ((charcode:coding-error
+ (lambda (c)
+ (declare (ignore c))
+ (simple-dns-decode-error buf "DNS label was not ASCII."))))
+ (charcode:decode-string (subseq packet
+ pos (+ pos len))
+ :ascii)))))
+ (incf pos len)))
+ ((3) (return-from decoded
+ (let* ((offset (+ (* 256 (ldb (byte 0 6) len))
+ (decode-uint-8 buf)))
+ (prev (assoc offset (dns-decode-state-prev-names buf))))
+ (unless prev
+ (simple-dns-decode-error buf "Domain name label pointed to non-label position"))
+ (append l (cdr prev)))))
+ (t (simple-dns-decode-error buf "Illegal DNS label flags: ~D" (ldb (byte 2 6) len))))))))))
+ (push (cons orig-off decoded)
+ (slot-value buf 'prev-names))))
+
+(defun decode-dns-query (buf)
+ (declare (type dns-decode-state buf))
+ (let* ((name (decode-domain-name buf))
+ (type (decode-uint-16 buf))
+ (class (decode-uint-16 buf))
+ (desc (find (list class type) *rr-coding-types* :key 'second :test 'equal)))
+ (if desc
+ (make-instance 'resource-query :name name :type (first desc))
+ (progn (warn "Unknown DNS RR type: ~D, ~D" class type)
+ nil))))
+
+(defun decode-dns-record (buf)
+ (declare (type dns-decode-state buf))
+ (let* ((name (decode-domain-name buf))
+ (type (decode-uint-16 buf))
+ (class (decode-uint-16 buf))
+ (ttl (decode-uint-32 buf))
+ (dlen (decode-uint-16 buf))
+ (desc (find (list class type) *rr-coding-types* :key 'second :test 'equal)))
+ (when (< (length (dns-decode-state-packet buf))
+ (+ (dns-decode-state-pos buf) dlen))
+ (simple-dns-decode-error buf "Not enough data left in DNS packet to decode indicated RR data length."))
+ (if desc
+ (let ((orig-off (dns-decode-state-pos buf))
+ (rr (make-instance (first desc)
+ :name name
+ :ttl ttl)))
+ (dolist (slot-desc (third desc))
+ (destructuring-bind (slot-name type) slot-desc
+ (setf (slot-value rr slot-name)
+ (with-slots (packet pos) buf
+ (ecase type
+ ((uint-16) (decode-uint-16 buf))
+ ((uint-32) (decode-uint-32 buf))
+ ((domain-name) (decode-domain-name buf))
+ ((text)
+ (let ((len (decode-uint-8 buf)))
+ (prog1 (subseq packet pos (+ pos len))
+ (incf pos len))))
+ ((ipv4-address)
+ (prog1 (make-instance 'ipv4-host-address :host-bytes (subseq packet pos (+ pos 4)))
+ (incf pos 4)))
+ ((ipv6-address)
+ (prog1 (make-instance 'ipv6-host-address :host-bytes (subseq packet pos (+ pos 16)))
+ (incf pos 16))))))))
+ (unless (= (dns-decode-state-pos buf) (+ orig-off dlen))
+ (simple-dns-decode-error buf "DNS RR data length did not match up with actual decoded data."))
+ rr)
+ (progn (warn "Unknown DNS RR type: ~D, ~D" class type)
+ (incf (dns-decode-state-pos buf) dlen)
+ nil))))
+
+(defun decode-dns-packet (buf)
+ (declare (type dns-decode-state buf))
+ (let* ((txid (decode-uint-16 buf))
+ (flags (decode-uint-16 buf))
+ (qnum (decode-uint-16 buf))
+ (ansnum (decode-uint-16 buf))
+ (autnum (decode-uint-16 buf))
+ (auxnum (decode-uint-16 buf))
+ (packet (make-dns-packet :txid txid
+ :is-response (ldb-test (byte 1 15) flags)
+ :opcode (case (ldb (byte 4 11) flags)
+ ((0) :query)
+ ((1) :iquery)
+ ((2) :status)
+ (t (simple-dns-decode-error buf "Unknown DNS opcode: ~D" (ldb (byte 4 11) flags))))
+ :authoritative (ldb-test (byte 1 10) flags)
+ :truncated (ldb-test (byte 1 9) flags)
+ :recurse (ldb-test (byte 1 8) flags)
+ :will-recurse (ldb-test (byte 1 7) flags)
+ :resp-code (case (ldb (byte 4 0) flags)
+ ((0) :success)
+ ((1) :format-error)
+ ((2) :server-failure)
+ ((3) :name-error)
+ ((4) :not-implemented)
+ ((5) :refused)
+ (t (simple-dns-decode-error buf "Unknown DNS response code: ~D" (ldb (byte 4 0) flags)))))))
+ (with-slots (queries answers authority additional) packet
+ (dotimes (i qnum)
+ (setf queries (append queries (list (decode-dns-query buf)))))
+ (dotimes (i ansnum)
+ (setf answers (append answers (list (decode-dns-record buf)))))
+ (dotimes (i autnum)
+ (setf authority (append authority (list (decode-dns-record buf)))))
+ (dotimes (i auxnum)
+ (setf additional (append additional (list (decode-dns-record buf))))))
+ packet))
+
+(defun dns-decode (packet)
+ (decode-dns-packet (make-dns-decode-state :packet packet)))
+
+;;; Packet encoding logic
+
+(defstruct dns-encode-state
+ (packet-buf (make-array '(512) :element-type '(unsigned-byte 8) :adjustable t :fill-pointer 0) :type (array (unsigned-byte 8)))
+ (prev-names '() :type list))
+
+(defun encode-uint-8 (buf num)
+ (declare (type dns-encode-state buf)
+ (type (unsigned-byte 8) num))
+ (with-slots (packet-buf) buf
+ (vector-push-extend num packet-buf)))
+
+(defun encode-uint-16 (buf num)
+ (declare (type dns-encode-state buf)
+ (type (unsigned-byte 16) num))
+ (with-slots (packet-buf) buf
+ (vector-push-extend (ldb (byte 8 8) num) packet-buf)
+ (vector-push-extend (ldb (byte 8 0) num) packet-buf)))
+
+(defun encode-uint-32 (buf num)
+ (declare (type dns-encode-state buf)
+ (type (unsigned-byte 32) num))
+ (with-slots (packet-buf) buf
+ (vector-push-extend (ldb (byte 8 24) num) packet-buf)
+ (vector-push-extend (ldb (byte 8 16) num) packet-buf)
+ (vector-push-extend (ldb (byte 8 8) num) packet-buf)
+ (vector-push-extend (ldb (byte 8 0) num) packet-buf)))
+
+(defun encode-bytes (buf bytes)
+ (declare (type dns-encode-state buf)
+ (type (array (unsigned-byte 8)) bytes))
+ (with-slots (packet-buf) buf
+ (dotimes (i (length bytes) (values))
+ (vector-push-extend (elt bytes i) packet-buf))))
+
+(defun encode-domain-name (buf name)
+ (declare (type dns-encode-state buf)
+ (type list name))
+ (with-slots (packet-buf prev-names) buf
+ (labels ((encode-label (name)
+ (let ((prev (find name prev-names :key 'first :test 'equal)))
+ (cond ((null name)
+ (encode-uint-8 buf 0))
+ (prev
+ (encode-uint-16 buf (+ #xc000 (cdr prev))))
+ (t
+ (when (< (length packet-buf) 16384)
+ (push (cons name (length packet-buf)) prev-names))
+ (let ((encoded (charcode:encode-string (car name) :ascii)))
+ (unless (< (length encoded) 64)
+ (error "DNS labels cannot exceed 63 octets in length: ~S" (car name)))
+ (encode-uint-8 buf (length encoded))
+ (encode-bytes buf encoded))
+ (encode-label (cdr name)))))))
+ (encode-label name))))
+
+(defun encode-dns-query (buf query)
+ (declare (type dns-encode-state buf)
+ (type resource-query query))
+ (let ((desc (find (slot-value query 'type) *rr-coding-types* :key 'first)))
+ (encode-domain-name buf (slot-value query 'name))
+ (encode-uint-16 buf (second (second desc)))
+ (encode-uint-16 buf (first (second desc)))))
+
+(defun encode-dns-record (buf record)
+ (declare (type dns-encode-state buf)
+ (type resource-record record))
+ (let ((desc (find (class-name (class-of record)) *rr-coding-types* :key 'first)))
+ (encode-domain-name buf (slot-value record 'name))
+ (encode-uint-16 buf (second (second desc)))
+ (encode-uint-16 buf (first (second desc)))
+ (encode-uint-32 buf (slot-value record 'ttl))
+ (with-slots (packet-buf) buf
+ (let ((orig-off (length packet-buf)))
+ (encode-uint-16 buf 0)
+ (dolist (slot-desc (third desc))
+ (destructuring-bind (slot-name type) slot-desc
+ (let ((val (slot-value record slot-name)))
+ (ecase type
+ ((uint-16) (encode-uint-16 buf val))
+ ((uint-32) (encode-uint-32 buf val))
+ ((domain-name) (encode-domain-name buf val))
+ ((text) (let ((data (etypecase val
+ (string (charcode:encode-string val :ascii))
+ ((array (unsigned-byte 8)) val))))
+ (unless (< (length data) 256)
+ (error "DNS text data length cannot exceed 255 octets."))
+ (encode-uint-8 buf (length data))
+ (encode-bytes buf data)))
+ ((ipv4-address)
+ (check-type val ipv4-host-address)
+ (encode-bytes buf (slot-value val 'host-bytes)))
+ ((ipv6-address)
+ (check-type val ipv6-host-address)
+ (encode-bytes buf (slot-value val 'host-bytes)))))))
+ (let ((dlen (- (length packet-buf) orig-off)))
+ (setf (aref packet-buf orig-off) (ldb (byte 8 8) dlen)
+ (aref packet-buf (1+ orig-off)) (ldb (byte 8 0) dlen)))))))
+
+(defun encode-dns-packet (buf packet)
+ (declare (type dns-encode-state buf)
+ (type dns-packet packet))
+ (with-slots (txid is-response opcode authoritative truncated
+ recurse will-recurse resp-code
+ queries answers authority additional) packet
+ (encode-uint-16 buf txid)
+ (let ((flags 0))
+ (setf (ldb (byte 1 15) flags) (if is-response 1 0)
+ (ldb (byte 4 11) flags) (ecase opcode
+ ((:query) 0)
+ ((:iquery) 1)
+ ((:status) 2))
+ (ldb (byte 1 10) flags) (if authoritative 1 0)
+ (ldb (byte 1 9) flags) (if truncated 1 0)
+ (ldb (byte 1 8) flags) (if recurse 1 0)
+ (ldb (byte 1 7) flags) (if will-recurse 1 0)
+ (ldb (byte 4 0) flags) (ecase resp-code
+ ((:success) 0)
+ ((:format-error) 1)
+ ((:server-failure) 2)
+ ((:name-error) 3)
+ ((:not-implemented) 4)
+ ((:refused) 5)))
+ (encode-uint-16 buf flags))
+ (encode-uint-16 buf (length queries))
+ (encode-uint-16 buf (length answers))
+ (encode-uint-16 buf (length authority))
+ (encode-uint-16 buf (length additional))
+ (dolist (query queries)
+ (encode-dns-query buf query))
+ (dolist (rr answers)
+ (encode-dns-record buf rr))
+ (dolist (rr authority)
+ (encode-dns-record buf rr))
+ (dolist (rr additional)
+ (encode-dns-record buf rr)))
+ (values))
+
+(defun dns-encode (packet)
+ (check-type packet dns-packet)
+ (let ((buf (make-dns-encode-state)))
+ (encode-dns-packet buf packet)
+ (slot-value buf 'packet-buf)))