X-Git-Url: https://git.distorted.org.uk/~mdw/zone/blobdiff_plain/85c39c0118ce36857bfc43faa3b353916936df79..f4e0c48f17d3c959d3751faba9ce9cd0becfba41:/net.lisp diff --git a/net.lisp b/net.lisp index 9e893c1..d5f4d76 100644 --- a/net.lisp +++ b/net.lisp @@ -1,7 +1,5 @@ ;;; -*-lisp-*- ;;; -;;; $Id$ -;;; ;;; Network (numbering) tools ;;; ;;; (c) 2006 Straylight/Edgeware @@ -13,32 +11,16 @@ ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2 of the License, or ;;; (at your option) any later version. -;;; +;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. -;;; +;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software Foundation, ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -;;;-------------------------------------------------------------------------- -;;; Packaging. - -(defpackage #:net - (:use #:common-lisp #:mdw.base #:mdw.str #:collect) - (:export #:ipaddr #:string-ipaddr #:ipaddr-byte #:ipaddr-string #:ipaddrp - #:integer-netmask #:ipmask #:ipmask-cidl-slash #:make-ipnet - #:string-ipnet #:ipnet #:ipnet-net #:ipnet-mask #:with-ipnet - #:ipnet-pretty #:ipnet-string #:ipnet-broadcast #:ipnet-hosts - #:ipnet-host #:ipaddr-networkp #:ipnet-subnetp - #:ipnet-changeable-bytes - #:host-find# #:host-create #:defhost #:parse-ipaddr - #:resolve-hostname #:canonify-hostname - #:net #:net-find #:net-get-as-ipnet #:net-create #:defnet - #:net-next-host #:net-host)) - (in-package #:net) ;;;-------------------------------------------------------------------------- @@ -52,6 +34,7 @@ "The type of unsigned 32-bit values." '(unsigned-byte 32)) +(export 'ipaddr) (deftype ipaddr () "The type of IP (version 4) addresses." 'u32) @@ -69,14 +52,14 @@ ;;;-------------------------------------------------------------------------- ;;; Simple messing with IP addresses. +(export 'string-ipaddr) (defun string-ipaddr (str &key (start 0) (end nil)) - "Parse STR as an IP address in dotted-quad form and return the integer -equivalent. STR may be anything at all: it's converted as if by -`stringify'. The START and END arguments may be used to parse out a -substring." + "Parse STR into an address. + + STR may be anything at all: it's converted as if by `stringify'. + The START and END arguments may be used to parse out a substring." (setf str (stringify str)) - (unless end - (setf end (length str))) + (setf-default end (length str)) (let ((addr 0) (noct 0)) (loop (let* ((pos (position #\. str :start start :end end)) @@ -92,11 +75,13 @@ substring." (error "Wrong number of octets in IP address")) addr)) +(export 'ipaddr-byte) (defun ipaddr-byte (ip n) "Return byte N (from most significant downwards) of an IP address." (assert (<= 0 n 3)) (logand #xff (ash ip (* -8 (- 3 n))))) +(export 'ipaddr-string) (defun ipaddr-string (ip) "Transform the address IP into a string in dotted-quad form." (check-type ip ipaddr) @@ -104,13 +89,16 @@ substring." (dotimes (i 4) (collect (ipaddr-byte ip i)))))) +(export 'ipaddrp) (defun ipaddrp (ip) "Answer true if IP is a valid IP address in integer form." (typep ip 'ipaddr)) (defun ipaddr (ip) - "Convert IP to an IP address. If it's an integer, return it unchanged; -otherwise convert by `string-ipaddr'." + "Convert IP to an IP address. + + If it's an integer, return it unchanged; otherwise convert by + `string-ipaddr'." (typecase ip (ipaddr ip) (t (string-ipaddr ip)))) @@ -118,22 +106,27 @@ otherwise convert by `string-ipaddr'." ;;;-------------------------------------------------------------------------- ;;; Netmasks. +(export 'integer-netmask) (defun integer-netmask (i) "Given an integer I, return a netmask with its I top bits set." (- (ash 1 32) (ash 1 (- 32 i)))) +(export 'ipmask) (defun ipmask (ip) "Transform IP into a netmask. If it's a small integer then it's converted -by `integer-netmask'; if nil, then all-bits-set; otherwise convert using -`ipaddr'." + by `integer-netmask'; if nil, then all-bits-set; otherwise convert using + `ipaddr'." (typecase ip (null (mask 32)) ((integer 0 32) (integer-netmask ip)) (t (ipaddr ip)))) +(export 'ipmask-cidl-slash) (defun ipmask-cidl-slash (mask) - "Given a netmask MASK, return an integer N such that (integer-netmask N) = -MASK, or nil if this is impossible." + "Given a netmask MASK, try to compute a prefix length. + + Return an integer N such that (integer-netmask N) = MASK, or nil if this + is impossible." (dotimes (i 33) (when (= mask (integer-netmask i)) (return i)))) @@ -141,17 +134,19 @@ MASK, or nil if this is impossible." ;;;-------------------------------------------------------------------------- ;;; Networks: pairing an address and netmask. +(export 'make-ipnet) (defun make-ipnet (net mask) "Construct an IP-network object given the NET and MASK; these are -transformed as though by `ipaddr' and `ipmask'." + transformed as though by `ipaddr' and `ipmask'." (let ((net (ipaddr net)) (mask (ipmask mask))) (cons (logand net mask) mask))) +(export 'string-ipnet) (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))) + (setf-default end (length str)) (let ((sl (position #\/ str :start start :end end))) (if sl (make-ipnet (parse-ipaddr (subseq str start sl)) @@ -163,39 +158,49 @@ transformed as though by `ipaddr' and `ipmask'." (make-ipnet (parse-ipaddr (subseq str start end)) (integer-netmask 32))))) +(export 'ipnet) (defun ipnet (net) - "Construct an IP-network object from the given argument. A number of -forms are acceptable: + "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." + * 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))))) +(export 'ipnet-net) (defun ipnet-net (ipn) "Return the base network address of IPN." (car ipn)) +(export 'ipnet-mask) (defun ipnet-mask (ipn) "Return the netmask of IPN." (cdr ipn)) +(export 'with-ipnet) (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." + "Evaluate the BODY with components of IPN in scope. + + The NET is bound to the underlying network base address and MASK is bound + to the netmask, again as an integer. Either (or both) of these 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)))) +(export 'ipnet-pretty) (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))))) +(export 'ipnet-string) (defun ipnet-string (ipn) "Convert IPN to a string." (with-ipnet (net mask) ipn @@ -203,18 +208,22 @@ IPN. Either NET or MASK (or, less usefully, both) may be nil if not wanted." (ipaddr-string net) (or (ipmask-cidl-slash mask) (ipaddr-string mask))))) +(export 'ipnet-broadcast) (defun ipnet-broadcast (ipn) "Return the broadcast address for the network IPN." (with-ipnet (net mask) ipn (logior net (logxor (mask 32) mask)))) +(export 'ipnet-hosts) (defun ipnet-hosts (ipn) "Return the number of available addresses in network IPN." (ash 1 (- 32 (logcount (ipnet-mask ipn))))) +(export 'ipnet-host) (defun ipnet-host (ipn host) - "Return the address of the given HOST in network IPN. This works even with -a non-contiguous netmask." + "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)) @@ -232,11 +241,13 @@ a non-contiguous netmask." (setf m (ash m 1)) (incf i))))) +(export 'ipaddr-networkp) (defun ipaddr-networkp (ip ipn) "Returns true if address IP is within network IPN." (with-ipnet (net mask) ipn (= net (logand ip mask)))) +(export 'ipnet-subnetp) (defun ipnet-subnetp (ipn subn) "Returns true if SUBN is a (non-strict) subnet of IPN." (with-ipnet (net mask) ipn @@ -244,43 +255,34 @@ a non-contiguous netmask." (and (= net (logand subnet mask)) (= submask (logior mask submask)))))) +(export 'ipnet-changeable-bytes) (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." + 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. +(export 'parse-ipaddr) (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" + "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) @@ -301,18 +303,20 @@ changeable. This is used when constructing reverse zones." (defvar *hosts* (make-hash-table :test #'equal) "The table of known hostnames.") +(export 'host-find) (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)) +(export 'host-create) (defun host-create (name addr) "Make host NAME map to ADDR (anything acceptable to parse-ipaddr)." (setf (host-find name) (parse-ipaddr addr))) +(export 'defhost) (defmacro defhost (name addr) "Main host definition macro. Neither NAME nor ADDR is evaluated." `(progn @@ -322,13 +326,14 @@ changeable. This is used when constructing reverse zones." ;;;-------------------------------------------------------------------------- ;;; Network names and specifiers. +(export 'net) (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 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 @@ -337,30 +342,36 @@ NEXT Index of the next unassigned host" (defvar *networks* (make-hash-table :test #'equal) "The table of known networks.") +(export 'net-find) (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)) +(export 'net-get-as-ipnet) (defun net-get-as-ipnet (form) - "Transform FORM into an ipnet. FORM may be a network name, or something -acceptable to the ipnet function." + "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." + "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) @@ -380,9 +391,12 @@ than the net has to start with." (cons (list root net mask) (frob subnets (+ net (ipnet-hosts ipn) 1) net)))))) +(export 'net-create) (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." + "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)) @@ -390,28 +404,35 @@ describe the new network, in a form acceptable to the ipnet function." :hosts (ipnet-hosts ipn) :next 1)))) +(export 'defnet) (defmacro defnet (name net &rest subnets) - "Main network definition macro. None of the arguments is evaluated." + "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)) +(export 'net-next-host) (defun net-next-host (net) "Given a NET, return the IP address (as integer) of the next available -address in the network." + 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))) +(export 'net-host) (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" + "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)))