- (destructuring-bind (net &key bytes) (listify (car data))
- (setf net (zone-parse-net net name))
- (unless bytes
- (setf bytes (ipnet-changeable-bytes (ipnet-mask net))))
- (let ((seen (make-hash-table :test #'equal)))
- (dolist (z (or (cdr data)
- (hash-table-keys *zones*)))
- (dolist (zr (zone-records (zone-find z)))
- (when (and (eq (zr-type zr) :a)
- (zr-make-ptr-p zr)
- (ipaddr-networkp (zr-data zr) net))
- (let ((name (string-downcase
- (join-strings
- #\.
- (collecting ()
- (dotimes (i bytes)
- (collect (logand #xff (ash (zr-data zr)
- (* -8 i)))))
- (collect name))))))
- (unless (gethash name seen)
- (rec :name name :type :ptr
- :ttl (zr-ttl zr) :data (zr-name zr))
- (setf (gethash name seen) t)))))))))
-
-(defzoneparse (:cidr-delegation :cidr) (name data rec :zname zname)
- ":cidr-delegation ((NET :bytes BYTES) ((TARGET-NET*) [TARGET-ZONE])*)
-
- Insert CNAME records for delegating a portion of the reverse-lookup
- namespace which doesn't align with an octet boundary.
-
- The NET specifies the origin network, in which the reverse records
- naturally lie. The BYTES are the number of labels to supply for each
- address; the default is the smallest number which suffices to enumerate
- the entire NET. The TARGET-NETs are subnets of NET which are to be
- delegated. The TARGET-ZONEs are the zones to which we are delegating
- authority for the reverse records: the default is to append labels for those
- octets of the subnet base address which are not the same in all address in
- the subnet."
- (setf data (listify data))
- (destructuring-bind (net &key bytes) (listify (car data))
- (setf net (zone-parse-net net name))
- (unless bytes
- (setf bytes (ipnet-changeable-bytes (ipnet-mask net))))
- (dolist (map (or (cdr data) (list (list net))))
- (destructuring-bind (tnets &optional tdom) (listify map)
- (dolist (tnet (listify tnets))
- (setf tnet (zone-parse-net tnet name))
- (unless (ipnet-subnetp net tnet)
- (error "~A is not a subnet of ~A."
- (ipnet-pretty tnet)
- (ipnet-pretty net)))
- (unless tdom
- (with-ipnet (net mask) tnet
- (setf tdom
- (join-strings
- #\.
- (append (reverse (loop
- for i from (1- bytes) downto 0
- until (zerop (logand mask
- (ash #xff
- (* 8 i))))
- collect (ldb (byte 8 (* i 8)) net)))
- (list name))))))
- (setf tdom (string-downcase (stringify tdom)))
- (dotimes (i (ipnet-hosts tnet))
- (unless (zerop i)
- (let* ((addr (ipnet-host tnet i))
- (tail (join-strings #\.
- (loop
- for i from 0 below bytes
- collect
- (logand #xff
- (ash addr (* 8 i)))))))
- (rec :name (format nil "~A.~A" tail name)
- :type :cname
- :data (format nil "~A.~A" tail tdom))))))))))
+ (destructuring-bind (net &key prefix-bits (family *address-family*))
+ (listify (car data))
+
+ (dolist (ipn (net-parse-to-ipnets net family))
+ (let* ((seen (make-hash-table :test #'equal))
+ (width (ipnet-width ipn))
+ (frag-len (if prefix-bits (- width prefix-bits)
+ (ipnet-changeable-bits width (ipnet-mask ipn)))))
+ (dolist (z (or (cdr data) (hash-table-keys *zones*)))
+ (dolist (zr (zone-records (zone-find z)))
+ (when (and (eq (zr-type zr) (ipaddr-rrtype (ipnet-net ipn)))
+ (zr-make-ptr-p zr)
+ (ipaddr-networkp (ipaddr-addr (zr-data zr)) ipn))
+ (let* ((frag (reverse-domain-fragment (zr-data zr)
+ 0 frag-len))
+ (name (concatenate 'string frag "." name)))
+ (unless (gethash name seen)
+ (rec :name name :type :ptr
+ :ttl (zr-ttl zr) :data (zr-name zr))
+ (setf (gethash name seen) t))))))))))
+
+(defzoneparse :multi (name data rec :zname zname :ttl ttl)
+ ":multi (((NET*) &key :start :end :family :suffix) . REC)
+
+ Output multiple records covering a portion of the reverse-resolution
+ namespace corresponding to the particular NETs. The START and END bounds
+ default to the most significant variable component of the
+ reverse-resolution domain.
+
+ The REC tail is a sequence of record forms (as handled by
+ `zone-process-records') to be emitted for each covered address. Within
+ the bodies of these forms, the symbol `*' will be replaced by the
+ domain-name fragment corresponding to the current host, optionally
+ followed by the SUFFIX.
+
+ Examples:
+
+ (:multi ((delegated-subnet :start 8)
+ :ns (some.ns.delegated.example :ip \"169.254.5.2\")))
+
+ (:multi ((tiny-subnet :suffix \"128.10.254.169.in-addr.arpa\")
+ :cname *))
+
+ Obviously, nested `:multi' records won't work well."
+
+ (destructuring-bind (nets &key start end (family *address-family*) suffix)
+ (listify (car data))
+ (dolist (net (listify nets))
+ (dolist (ipn (net-parse-to-ipnets net family))
+ (let* ((addr (ipnet-net ipn))
+ (width (ipaddr-width addr))
+ (comp-width (reverse-domain-component-width addr))
+ (end (round-up (or end
+ (ipnet-changeable-bits width
+ (ipnet-mask ipn)))
+ comp-width))
+ (start (round-down (or start (- end comp-width))
+ comp-width))
+ (map (ipnet-host-map ipn)))
+ (multiple-value-bind (host-step host-limit)
+ (ipnet-index-bounds map start end)
+ (do ((index 0 (+ index host-step)))
+ ((> index host-limit))
+ (let* ((addr (ipnet-index-host map index))
+ (frag (reverse-domain-fragment addr start end))
+ (target (concatenate 'string
+ (zone-make-name
+ (if (not suffix) frag
+ (concatenate 'string
+ frag "." suffix))
+ zname)
+ ".")))
+ (dolist (zr (zone-parse-records (zone-make-name frag zname)
+ ttl
+ (subst target '*
+ (cdr data))))
+ (rec :name (zr-name zr)
+ :type (zr-type zr)
+ :data (zr-data zr)
+ :ttl (zr-ttl zr)
+ :make-ptr-p (zr-make-ptr-p zr)))))))))))
+
+;;;--------------------------------------------------------------------------
+;;; Building raw record vectors.
+
+(defvar *record-vector* nil
+ "The record vector under construction.")
+
+(defun rec-ensure (n)
+ "Ensure that at least N octets are spare in the current record."
+ (let ((want (+ n (fill-pointer *record-vector*)))
+ (have (array-dimension *record-vector* 0)))
+ (unless (<= want have)
+ (adjust-array *record-vector*
+ (do ((new (* 2 have) (* 2 new)))
+ ((<= want new) new))))))
+
+(defun rec-byte (octets value)
+ "Append an unsigned byte, OCTETS octets wide, with VALUE, to the record."
+ (rec-ensure octets)
+ (do ((i (1- octets) (1- i)))
+ ((minusp i))
+ (vector-push (ldb (byte 8 (* 8 i)) value) *record-vector*)))
+
+(defun rec-u8 (value)
+ "Append an 8-bit VALUE to the current record."
+ (rec-byte 1 value))
+(defun rec-u16 (value)
+ "Append a 16-bit VALUE to the current record."
+ (rec-byte 2 value))
+(defun rec-u32 (value)
+ "Append a 32-bit VALUE to the current record."
+ (rec-byte 4 value))
+
+(defun rec-raw-string (s &key (start 0) end)
+ "Append (a (substring of) a raw string S to the current record.
+
+ No arrangement is made for reporting the length of the string. That must
+ be done by the caller, if necessary."
+ (setf-default end (length s))
+ (rec-ensure (- end start))
+ (do ((i start (1+ i)))
+ ((>= i end))
+ (vector-push (char-code (char s i)) *record-vector*)))
+
+(defun rec-name (s)
+ "Append a domain name S.
+
+ No attempt is made to perform compression of the name."
+ (let ((i 0) (n (length s)))
+ (loop (let* ((dot (position #\. s :start i))
+ (lim (or dot n)))
+ (rec-u8 (- lim i))
+ (rec-raw-string s :start i :end lim)
+ (if dot
+ (setf i (1+ dot))
+ (return))))
+ (when (< i n)
+ (rec-u8 0))))
+
+(defmacro build-record (&body body)
+ "Build a raw record, and return it as a vector of octets."
+ `(let ((*record-vector* (make-array 256
+ :element-type '(unsigned-byte 8)
+ :fill-pointer 0
+ :adjustable t)))
+ ,@body
+ (copy-seq *record-vector*)))