-;;;--------------------------------------------------------------------------
-;;; Networks: pairing an address and netmask.
-
-(defun make-ipnet (net mask)
- "Construct an IP-network object given the NET and MASK; these are
-transformed as though by `ipaddr' and `ipmask'."
- (let ((net (ipaddr net))
- (mask (ipmask mask)))
- (cons (logand net mask) mask)))
-
-(defun string-ipnet (str &key (start 0) (end nil))
- "Parse an IP-network from the string STR."
- (setf str (stringify str))
- (unless end (setf end (length str)))
- (let ((sl (position #\/ str :start start :end end)))
- (if sl
- (make-ipnet (parse-ipaddr (subseq str start sl))
- (if (find #\. str :start (1+ sl) :end end)
- (string-ipaddr str :start (1+ sl) :end end)
- (integer-netmask (parse-integer str
- :start (1+ sl)
- :end end))))
- (make-ipnet (parse-ipaddr (subseq str start end))
- (integer-netmask 32)))))
-
-(defun ipnet (net)
- "Construct an IP-network object from the given argument. A number of
-forms are acceptable:
-
- * ADDR -- a single address (equivalent to ADDR 32)
- * (NET . MASK|nil) -- a single-object representation.
- * IPNET -- return an equivalent (`equal', not necessarily `eql') version."
- (cond ((or (stringp net) (symbolp net)) (string-ipnet net))
- (t (apply #'make-ipnet (pairify net 32)))))
-
-(defun ipnet-net (ipn)
- "Return the base network address of IPN."
- (car ipn))
-
-(defun ipnet-mask (ipn)
- "Return the netmask of IPN."
- (cdr ipn))
-
-(defmacro with-ipnet ((net mask) ipn &body body)
- "Evaluate BODY with NET and MASK bound to the base address and netmask of
-IPN. Either NET or MASK (or, less usefully, both) may be nil if not wanted."
- (with-gensyms tmp
- `(let ((,tmp ,ipn))
- (let (,@(and net `((,net (ipnet-net ,tmp))))
- ,@(and mask `((,mask (ipnet-mask ,tmp)))))
- ,@body))))
-
-(defun ipnet-pretty (ipn)
- "Convert IPN to a pretty cons-cell form."
- (with-ipnet (net mask) ipn
- (cons (ipaddr-string net)
- (or (ipmask-cidl-slash mask) (ipaddr-string mask)))))
-
-(defun ipnet-string (ipn)
- "Convert IPN to a string."
- (with-ipnet (net mask) ipn
- (format nil "~A/~A"
- (ipaddr-string net)
- (or (ipmask-cidl-slash mask) (ipaddr-string mask)))))
-
-(defun ipnet-broadcast (ipn)
- "Return the broadcast address for the network IPN."
- (with-ipnet (net mask) ipn
- (logior net (logxor (mask 32) mask))))
-
-(defun ipnet-hosts (ipn)
- "Return the number of available addresses in network IPN."
- (ash 1 (- 32 (logcount (ipnet-mask ipn)))))
-
-(defun ipnet-host (ipn host)
- "Return the address of the given HOST in network IPN. This works even with
-a non-contiguous netmask."
- (check-type host u32)
- (with-ipnet (net mask) ipn
- (let ((i 0) (m 1) (a net) (h host))
- (loop
- (when (>= i 32)
- (error "Host index ~D out of range for network ~A"
- host (ipnet-pretty ipn)))
- (cond ((zerop h)
- (return a))
- ((logbitp i mask)
- (setf h (ash h 1)))
- (t
- (setf a (logior a (logand m h)))
- (setf h (logandc2 h m))))
- (setf m (ash m 1))
- (incf i)))))
-
-(defun ipaddr-networkp (ip ipn)
- "Returns true if address IP is within network IPN."
- (with-ipnet (net mask) ipn
- (= net (logand ip mask))))
-
-(defun ipnet-subnetp (ipn subn)
- "Returns true if SUBN is a (non-strict) subnet of IPN."
- (with-ipnet (net mask) ipn
- (with-ipnet (subnet submask) subn
- (and (= net (logand subnet mask))
- (= submask (logior mask submask))))))
-
-(defun ipnet-changeable-bytes (mask)
- "Answers how many low-order bytes of MASK are (entirely or partially)
-changeable. This is used when constructing reverse zones."
- (dotimes (i 4 4)
- (when (/= (ipaddr-byte mask i) 255)
- (return (- 4 i)))))
-
-;;;--------------------------------------------------------------------------
-;;; Name resolution.
-
-#+cmu
-(defun resolve-hostname (name)
- "Resolve a hostname to an IP address using the DNS, or return nil."
- (let ((he (ext:lookup-host-entry name)))
- (and he
- (ext:host-entry-addr he))))
-
-#+cmu
-(defun canonify-hostname (name)
- "Resolve a hostname to canonical form using the DNS, or return nil."
- (let ((he (ext:lookup-host-entry name)))
- (and he
- (ext:host-entry-name he))))
-
-;;;--------------------------------------------------------------------------
-;;; Host names and specifiers.
-
-(defun parse-ipaddr (addr)
- "Convert the string ADDR into an IP address: tries all sorts of things:
-
- (NET [INDEX]) -- index a network: NET is a network name defined by defnet;
- INDEX is an index or one of the special symbols understood by net-host,
- and defaults to :next
- INTEGER -- an integer IP address
- IPADDR -- an IP address in dotted-quad form
- HOST -- a host name defined by defhost
- DNSNAME -- a name string to look up in the DNS"
- (cond ((listp addr)
- (destructuring-bind
- (net host)
- (pairify addr :next)
- (net-host (or (net-find net)
- (error "Network ~A not found" net))
- host)))
- ((ipaddrp addr) addr)
- (t
- (setf addr (string-downcase (stringify addr)))
- (or (host-find addr)
- (and (plusp (length addr))
- (digit-char-p (char addr 0))
- (string-ipaddr addr))
- (resolve-hostname (stringify addr))
- (error "Host name ~A unresolvable" addr)))))
-
-(defvar *hosts* (make-hash-table :test #'equal)
- "The table of known hostnames.")
-
-(defun host-find (name)
- "Find a host by NAME."
- (gethash (string-downcase (stringify name)) *hosts*))
-
-(defun (setf host-find) (addr name)
- "Make NAME map to ADDR (must be an ipaddr in integer form)."
- (setf (gethash (string-downcase (stringify name)) *hosts*) addr))
-
-(defun host-create (name addr)
- "Make host NAME map to ADDR (anything acceptable to parse-ipaddr)."
- (setf (host-find name) (parse-ipaddr addr)))
-
-(defmacro defhost (name addr)
- "Main host definition macro. Neither NAME nor ADDR is evaluated."
- `(progn
- (host-create ',name ',addr)
- ',name))
-
-;;;--------------------------------------------------------------------------
-;;; Network names and specifiers.
-
-(defstruct (net (:predicate netp))
- "A network structure. Slots:
-
-NAME The network's name, as a string
-IPNET The network base address and mask
-HOSTS Number of hosts in the network
-NEXT Index of the next unassigned host"
- name
- ipnet
- hosts
- next)
-
-(defvar *networks* (make-hash-table :test #'equal)
- "The table of known networks.")
-
-(defun net-find (name)
- "Find a network by NAME."
- (gethash (string-downcase (stringify name)) *networks*))
-
-(defun (setf net-find) (net name)
- "Make NAME map to NET."
- (setf (gethash (string-downcase (stringify name)) *networks*) net))
-
-(defun net-get-as-ipnet (form)
- "Transform FORM into an ipnet. FORM may be a network name, or something
-acceptable to the ipnet function."
- (let ((net (net-find form)))
- (if net (net-ipnet net)
- (ipnet form))))
-
-(defun process-net-form (root addr subnets)
- "Unpack a net-form. The return value is a list of entries, each of which
-is a list of the form (NAME ADDR MASK). The first entry is merely repeats
-the given ROOT and ADDR arguments (unpacking ADDR into separate network
-address and mask). The SUBNETS are then processed: they are a list of items
-of the form (NAME NUM-HOSTS . SUBNETS), where NAME names the subnet,
-NUM-HOSTS is the number of hosts in it, and SUBNETS are its sub-subnets in
-the same form. An error is signalled if a net's subnets use up more hosts
-than the net has to start with."
- (labels ((frob (subnets limit finger)
- (when subnets
- (destructuring-bind (name size &rest subs) (car subnets)
- (when (> (count-low-zero-bits size)
- (count-low-zero-bits finger))
- (error "Bad subnet size for ~A." name))
- (when (> (+ finger size) limit)
- (error "Subnet ~A out of range." name))
- (append (and name
- (list (list name finger (- (ash 1 32) size))))
- (frob subs (+ finger size) finger)
- (frob (cdr subnets) limit (+ finger size)))))))
- (let ((ipn (ipnet addr)))
- (with-ipnet (net mask) ipn
- (unless (ipmask-cidl-slash mask)
- (error "Bad mask for subnet form."))
- (cons (list root net mask)
- (frob subnets (+ net (ipnet-hosts ipn) 1) net))))))
-
-(defun net-create (name net)
- "Construct a new network called NAME and add it to the map. The ARGS
-describe the new network, in a form acceptable to the ipnet function."
- (let ((ipn (ipnet net)))
- (setf (net-find name)
- (make-net :name (string-downcase (stringify name))
- :ipnet ipn
- :hosts (ipnet-hosts ipn)
- :next 1))))
-
-(defmacro defnet (name net &rest subnets)
- "Main network definition macro. None of the arguments is evaluated."
- `(progn
- ,@(loop for (name addr mask) in (process-net-form name net subnets)
- collect `(net-create ',name '(,addr . ,mask)))
- ',name))
-
-(defun net-next-host (net)
- "Given a NET, return the IP address (as integer) of the next available
-address in the network."
- (unless (< (net-next net) (net-hosts net))
- (error "No more hosts left in network ~A" (net-name net)))
- (let ((next (net-next net)))
- (incf (net-next net))
- (net-host net next)))
-
-(defun net-host (net host)
- "Return the given HOST on the NEXT. HOST may be an index (in range, of
-course), or one of the keywords:
-:NEXT next host, as by net-next-host
-:NET network base address
-:BROADCAST network broadcast address"
- (case host
- (:next (net-next-host net))
- (:net (ipnet-net (net-ipnet net)))
- (:broadcast (ipnet-broadcast (net-ipnet net)))
- (t (ipnet-host (net-ipnet net) host))))
+ (let ((end1 (or end1 (length string1)))
+ (end2 (or end2 (length string2))))
+ (loop
+ (cond ((>= start1 end1)
+ (let ((eqp (>= start2 end2)))
+ (return (values (not eqp) nil))))
+ ((>= start2 end2)
+ (return (values nil t)))
+ ((and (digit-char-p (char string1 start1))
+ (digit-char-p (char string2 start2)))
+ (let* ((lim1 (or (position-if-not #'digit-char-p string1
+ :start start1 :end end1)
+ end1))
+ (n1 (parse-integer string1 :start start1 :end lim1))
+ (lim2 (or (position-if-not #'digit-char-p string2
+ :start start2 :end end2)
+ end2))
+ (n2 (parse-integer string2 :start start2 :end lim2)))
+ (cond ((< n1 n2) (return (values t nil)))
+ ((> n1 n2) (return (values nil t))))
+ (setf start1 lim1
+ start2 lim2)))
+ (t
+ (let ((lim1 (or (position-if #'digit-char-p string1
+ :start start1 :end end1)
+ end1))
+ (lim2 (or (position-if #'digit-char-p string2
+ :start start2 :end end2)
+ end2)))
+ (cond ((string< string1 string2
+ :start1 start1 :end1 lim1
+ :start2 start2 :end2 lim2)
+ (return (values t nil)))
+ ((string> string1 string2
+ :start1 start1 :end1 lim1
+ :start2 start2 :end2 lim2)
+ (return (values nil t))))
+ (setf start1 lim1
+ start2 lim2)))))))
+
+(defun domain-name< (name-a name-b)
+ "Answer whether NAME-A precedes NAME-B in an ordering of domain names.
+
+ Split the names into labels at the dots, and then lexicographically
+ compare the sequences of labels, right to left, using `natural-string<'.
+
+ Returns two values: whether NAME-A strictly precedes NAME-B, and whether
+ NAME-A strictly follows NAME-B."
+ (let ((pos-a (length name-a))
+ (pos-b (length name-b)))
+ (loop (let ((dot-a (or (position #\. name-a
+ :from-end t :end pos-a)
+ -1))
+ (dot-b (or (position #\. name-b
+ :from-end t :end pos-b)
+ -1)))
+ (multiple-value-bind (precp follp)
+ (natural-string< name-a name-b
+ :start1 (1+ dot-a) :end1 pos-a
+ :start2 (1+ dot-b) :end2 pos-b)
+ (cond (precp
+ (return (values t nil)))
+ (follp
+ (return (values nil t)))
+ ((= dot-a -1)
+ (let ((eqp (= dot-b -1)))
+ (return (values (not eqp) nil))))
+ ((= dot-b -1)
+ (return (values nil t)))
+ (t
+ (setf pos-a dot-a
+ pos-b dot-b))))))))