3 ;;; Network (numbering) tools
5 ;;; (c) 2006 Straylight/Edgeware
8 ;;;----- Licensing notice ---------------------------------------------------
10 ;;; This program is free software; you can redistribute it and/or modify
11 ;;; it under the terms of the GNU General Public License as published by
12 ;;; the Free Software Foundation; either version 2 of the License, or
13 ;;; (at your option) any later version.
15 ;;; This program is distributed in the hope that it will be useful,
16 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;;; GNU General Public License for more details.
20 ;;; You should have received a copy of the GNU General Public License
21 ;;; along with this program; if not, write to the Free Software Foundation,
22 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 ;;;--------------------------------------------------------------------------
27 ;;; Various random utilities.
29 (declaim (inline mask))
31 "Return 2^N - 1: i.e., a mask of N set bits."
34 (defun find-first-bit-transition
35 (mask &optional (low 0) (high (integer-length mask)))
36 "Find the first (lowest bit-position) transition in MASK within the bounds.
38 The LOW bound is inclusive; the high bound is exclusive. A transition is
39 a change from zero to one, or vice-versa. The return value is the
40 upper (exclusive) bound on the initial run, and the lower (inclusive)
43 If there is no transition within the bounds, then return HIGH."
45 ;; Arrange that the initial run is ones.
46 (unless (logbitp low mask) (setf mask (lognot mask)))
48 ;; Now, note that MASK + 2^LOW is identical to MASK in all bit positions
49 ;; except for (a) the run of one bits starting at LOW, and (b) the zero bit
50 ;; just above it. So MASK xor (MASK + 2^LOW) is zero except for these
51 ;; bits; so all we need now is to find the position of its most significant
53 (let ((pos (1- (integer-length (logxor mask (+ mask (ash 1 low)))))))
54 (if (<= low pos high) pos high)))
56 (defun count-low-zero-bits (n)
57 "Return the number of low-order zero bits in the integer N."
60 (t (find-first-bit-transition n))))
62 (declaim (inline round-down))
63 (defun round-down (n step)
64 "Return the largest multiple of STEP not greater than N."
65 (* step (floor n step)))
67 (declaim (inline round-up))
68 (defun round-up (n step)
69 "Return the smallest multiple of STEP not less than N."
70 (* step (ceiling n step)))
72 (defgeneric extract-class-name (object)
73 (:documentation "Turn OBJECT into a class name.")
74 (:method ((instance standard-object))
75 (extract-class-name (class-of instance)))
76 (:method ((class standard-class))
78 (:method ((name symbol))
81 (defclass savable-object ()
83 (defmethod make-load-form ((object savable-object) &optional environment)
84 (make-load-form-saving-slots object :environment environment))
86 ;;;--------------------------------------------------------------------------
87 ;;; Parsing primitives for addresses.
89 (defun parse-partial-address
91 &key (start 0) (end nil) (delim #\.)
92 (width 8) (radix 10) (min 1) (max 32) (shiftp t)
94 "Parse a partial address from STR, which should be a sequence of integers
95 in the given RADIX, separated by the DELIM character, with each integer
96 N_i in the interval 0 <= N_i < 2^WIDTH. If the sequence is N_1, N_2, ...,
97 N_k, then the basic partial address BPA is the sum
99 SUM_{1<=i<=k} 2^{WIDTH (k-i)} N_i
101 If SHIFTP is true (the default) then let OFFSET be the smallest multiple
102 of WIDTH not less than MAX - k WIDTH; otherwise, let OFFSET be zero. The
103 partial address PA is BPA 2^SHIFT.
105 The return values are: PA, OFFSET, k WIDTH + OFFSET; i.e., the partial
106 address, and (inclusive) lower and (exclusive) upper bounds on the bits
109 (setf-default end (length str))
110 (let ((addr 0) (nbits 0) (limit (ash 1 width)))
114 (error "Too many elements in ~A" what))
115 (let* ((pos (position delim str :start start :end end))
116 (w (parse-integer str :radix radix
117 :start start :end (or pos end))))
118 (unless (and (<= 0 w) (< w limit))
119 (error "Element out of range in ~A" what))
120 (setf addr (logior (ash addr width) w))
122 (unless pos (return))
123 (setf start (1+ pos)))))
125 (error "Not enough elements in ~A" what))
127 (let* ((top (round-up max width))
128 (shift (- top nbits)))
129 (values (ash addr shift) shift top))
130 (values addr 0 nbits))))
132 ;;;--------------------------------------------------------------------------
133 ;;; Simple messing about with IP addresses.
136 (export 'ipaddr-addr)
137 (defclass ipaddr (savable-object)
140 "Base class for IP addresses."))
142 (export 'ipaddr-family)
143 (defgeneric ipaddr-family (addr))
145 (export 'family-addrclass)
146 (defgeneric family-addrclass (family)
147 (:method ((af symbol)) nil))
149 (export 'ipaddr-width)
150 (defgeneric ipaddr-width (class)
151 (:method ((object t)) (ipaddr-width (extract-class-name object))))
153 (export 'ipaddr-comparable-p)
154 (defgeneric ipaddr-comparable-p (addr-a addr-b)
155 (:method ((addr-a ipaddr) (addr-b ipaddr))
156 (eq (class-of addr-a) (class-of addr-b))))
158 (defun guess-address-class (str &key (start 0) (end nil))
159 (cond ((position #\: str :start start :end end) 'ip6addr)
162 (defgeneric parse-partial-ipaddr (class str &key start end min max)
163 (:method ((object t) str &rest keywords)
164 (apply #'parse-partial-ipaddr (extract-class-name object) str keywords)))
166 (export 'string-ipaddr)
167 (defun string-ipaddr (str &key (start 0) (end nil))
168 "Parse STR into an address; guess what kind is intended by the user.
170 STR may be anything at all: it's converted as if by `stringify'.
171 The START and END arguments may be used to parse out a substring."
172 (setf str (stringify str))
173 (let* ((class (guess-address-class str :start start :end end))
174 (width (ipaddr-width class)))
175 (make-instance class :addr
176 (parse-partial-ipaddr class str
177 :start start :end end
178 :min width :max width))))
180 (export 'integer-ipaddr)
181 (defgeneric integer-ipaddr (int like)
182 (:documentation "Convert INT into an address of type indicated by LIKE.
184 Specifically, if LIKE is an address object, then use its type; if it's
185 a class, then use it directly; if it's a symbol, then use the class it
187 (:method (int (like t)) (integer-ipaddr int (class-of like)))
188 (:method (int (like symbol))
189 (make-instance (or (family-addrclass like) like) :addr int))
190 (:method (int (like standard-class)) (make-instance like :addr int)))
192 (export 'ipaddr-string)
193 (defgeneric ipaddr-string (ip)
195 "Transform the address IP into a string in dotted-quad form."))
197 (defmethod print-object ((addr ipaddr) stream)
198 (print-unreadable-object (addr stream :type t)
199 (write-string (ipaddr-string addr) stream)))
203 "Answer true if IP is a valid IP address in integer form."
206 (defun ipaddr (ip &optional like)
207 "Convert IP to an IP address, of type similar to LIKE.
209 If it's an IP address, just return it unchanged; If it's an integer,
210 capture it; otherwise convert by `string-ipaddr'."
213 (integer (integer-ipaddr ip like))
214 (t (string-ipaddr ip))))
216 (export 'ipaddr-rrtype)
217 (defgeneric ipaddr-rrtype (addr)
218 (:documentation "Return the proper resource record type for ADDR."))
220 ;;;--------------------------------------------------------------------------
223 (export 'integer-netmask)
224 (defun integer-netmask (n i)
225 "Given an integer I, return an N-bit netmask with its I top bits set."
226 (- (ash 1 n) (ash 1 (- n i))))
228 (export 'ipmask-cidl-slash)
229 (defun ipmask-cidl-slash (width mask)
230 "Given a netmask MASK, try to compute a prefix length.
232 Return an integer N such that (integer-netmask WIDTH N) = MASK, or nil if
234 (let* ((low (logxor mask (mask width)))
235 (bits (integer-length low)))
236 (and (= low (mask bits)) (- width bits))))
239 (defgeneric ipmask (addr mask)
240 (:documentation "Convert MASK into a suitable netmask for ADDR.")
241 (:method ((addr ipaddr) (mask null))
242 (mask (ipaddr-width addr)))
243 (:method ((addr ipaddr) (mask integer))
244 (let ((w (ipaddr-width addr)))
246 (integer-netmask w mask)
247 (error "Prefix length out of range.")))))
249 (export 'mask-ipaddr)
250 (defun mask-ipaddr (addr mask)
251 "Apply the MASK to the ADDR, returning the base address."
252 (integer-ipaddr (logand mask (ipaddr-addr addr)) addr))
254 ;;;--------------------------------------------------------------------------
255 ;;; Networks: pairing an address and netmask.
260 (defclass ipnet (savable-object)
262 (:documentation "Base class for IP networks."))
264 (export 'ipnet-family)
265 (defgeneric ipnet-family (ipn)
266 (:method ((ipn ipnet)) (ipaddr-family (ipnet-net ipn))))
269 (defun ipnet-addr (ipn)
270 "Return the base network address of IPN as a raw integer."
271 (ipaddr-addr (ipnet-net ipn)))
273 (export 'ipaddr-ipnet)
274 (defgeneric ipaddr-ipnet (addr mask)
275 (:documentation "Construct an `ipnet' object given a base ADDR and MASK."))
278 (defun make-ipnet (net mask)
279 "Construct an IP-network object given the NET and MASK; these are
280 transformed as though by `ipaddr' and `ipmask'."
281 (let* ((net (ipaddr net))
282 (mask (ipmask net mask)))
283 (ipaddr-ipnet (mask-ipaddr net mask) mask)))
286 (defmacro with-ipnet ((net addr mask) ipn &body body)
287 "Evaluate the BODY with components of IPN in scope.
289 The NET is bound to the underlying network base address, as an `ipaddr';
290 ADDR is bound to the integer value of this address; and MASK is bound to
291 the netmask, again as an integer. Any (or all) of these may be nil if not
295 (let (,@(and net `((,net (ipnet-net ,tmp))))
296 ,@(and addr `((,addr (ipnet-addr ,tmp))))
297 ,@(and mask `((,mask (ipnet-mask ,tmp)))))
300 (export 'ipnet-width)
301 (defun ipnet-width (ipn)
302 "Return the underlying bit width of the addressing system."
303 (ipaddr-width (ipnet-net ipn)))
305 (export 'ipnet-string)
306 (defun ipnet-string (ipn)
307 "Convert IPN to a string."
308 (with-ipnet (net nil mask) ipn
311 (or (ipmask-cidl-slash (ipnet-width ipn) mask)
312 (ipaddr-string (make-instance (class-of net) :addr mask))))))
314 (defmethod print-object ((ipn ipnet) stream)
315 (print-unreadable-object (ipn stream :type t)
316 (write-string (ipnet-string ipn) stream)))
318 (defun parse-subnet (class width max str &key (start 0) (end nil) (slashp t))
319 "Parse a subnet description from a (substring of) STR."
320 (setf-default end (length str))
321 (let ((sl (and slashp (position #\/ str :start start :end end))))
322 (multiple-value-bind (addr lo hi)
323 (parse-partial-ipaddr class str :max max
324 :start start :end (or sl end))
325 (let* ((present (integer-netmask hi (- hi lo)))
326 (mask (cond ((not sl)
328 ((every #'digit-char-p (subseq str (1+ sl) end))
329 (let ((length (parse-integer str
332 (unless (>= length (- width max))
333 (error "Mask doesn't reach subnet boundary"))
334 (integer-netmask max (- length (- width max)))))
336 (parse-partial-ipaddr class str :max max
337 :start (1+ sl) :end end)))))
338 (unless (zerop (logandc2 mask present))
339 (error "Mask selects bits not present in base address"))
340 (values addr mask)))))
342 (defun check-subipnet (base-ipn sub-addr sub-mask)
343 "Verify that SUB-NET/SUB-MASK is an appropriate subnet of BASE-IPN.
345 The BASE-IPN is an `ipnet'; SUB-ADDR and SUB-MASK are plain integers. If
346 the subnet is invalid (i.e., the subnet disagrees with its putative parent
347 over some of the fixed address bits) then an error is signalled; otherwise
348 return the combined base address (as an `ipaddr') and mask (as an
350 (with-ipnet (base-net base-addr base-mask) base-ipn
351 (let* ((common (logand base-mask sub-mask))
352 (base-overlap (logand base-addr common))
353 (sub-overlap (logand sub-addr common))
354 (full-mask (logior base-mask sub-mask)))
355 (unless (or (zerop sub-overlap) (= sub-overlap base-overlap))
356 (error "Subnet doesn't match base network"))
357 (values (integer-ipaddr (logand full-mask (logior base-addr sub-addr))
361 (export 'string-ipnet)
362 (defun string-ipnet (str &key (start 0) (end nil))
363 "Parse an IP-network from the string STR."
364 (setf str (stringify str))
365 (setf-default end (length str))
366 (let ((addr-class (guess-address-class str :start start :end end)))
367 (multiple-value-bind (addr mask)
368 (let ((width (ipaddr-width addr-class)))
369 (parse-subnet addr-class width width str
370 :start start :end end))
371 (make-ipnet (make-instance addr-class :addr addr)
372 (make-instance addr-class :addr mask)))))
374 (defun parse-subipnet (ipn str &key (start 0) (end nil) (slashp t))
375 "Parse STR as a subnet of IPN.
377 This is mostly a convenience interface over `parse-subnet'."
378 (let* ((addr-class (extract-class-name (ipnet-net ipn)))
379 (width (ipaddr-width addr-class))
381 (or (ipmask-cidl-slash width (ipnet-mask ipn))
382 (error "Base network has complex netmask")))))
383 (multiple-value-bind (addr mask)
384 (parse-subnet addr-class width max (stringify str)
385 :start start :end end :slashp slashp)
386 (check-subipnet ipn addr mask))))
388 (export 'string-subipnet)
389 (defun string-subipnet (ipn str &key (start 0) (end nil))
390 "Parse an IP subnet from a parent net IPN and a suffix string STR."
391 (multiple-value-bind (addr mask)
392 (parse-subipnet ipn str :start start :end end)
393 (ipaddr-ipnet addr mask)))
396 "Construct an IP-network object from the given argument.
398 A number of forms are acceptable:
400 * ADDR -- a single address, equivalent to (ADDR . N).
401 * (NET . MASK|nil) -- a single-object representation.
402 * IPNET -- return an equivalent (`equal', not necessarily `eql')
406 ((or string symbol) (string-ipnet net))
407 (t (apply #'make-ipnet (pairify net nil)))))
409 (export 'ipnet-broadcast)
410 (defgeneric ipnet-broadcast (ipn)
411 (:documentation "Return the broadcast address for the network IPN.
413 Returns nil if there isn't one."))
415 (export 'ipnet-hosts)
416 (defun ipnet-hosts (ipn)
417 "Return the number of available addresses in network IPN."
418 (ash 1 (- (ipnet-width ipn) (logcount (ipnet-mask ipn)))))
421 "An internal object used by `ipnet-index-host' and `ipnet-host-index'.
423 Our objective is to be able to convert between flat host indices and a
424 possibly crazy non-flat host space. We record the underlying IPNET for
425 convenience, and a list of byte-specifications for the runs of zero bits
426 in the netmask, in ascending order."
430 (export 'ipnet-host-map)
431 (defun ipnet-host-map (ipn)
432 "Work out how to enumerate the variable portion of IPN.
434 Returns an object which can be passed to `ipnet-index-host' and
436 (let* ((mask (ipnet-mask ipn)) (bytes nil) (i 0)
437 (len (integer-length mask)) (width (ipnet-width ipn)))
438 (when (logbitp i mask) (setf i (find-first-bit-transition mask i)))
440 (unless (< i len) (return))
441 (let ((next (find-first-bit-transition mask i width)))
442 (push (byte (- next i) i) bytes)
443 (setf i (find-first-bit-transition mask next width))))
444 (when (< len width) (push (byte (- width len) len) bytes))
445 (make-host-map :ipnet ipn :bytes (nreverse bytes))))
447 (export 'ipnet-index-host)
448 (defun ipnet-index-host (map host)
449 "Convert a HOST index to its address."
450 (let* ((ipn (host-map-ipnet map))
451 (addr (logand (ipnet-addr ipn) (ipnet-mask ipn))))
452 (dolist (byte (host-map-bytes map))
453 (setf (ldb byte addr) host
454 host (ash host (- (byte-size byte)))))
456 (error "Host index out of range."))
457 (integer-ipaddr addr (ipnet-net ipn))))
459 (export 'ipnet-host-index)
460 (defun ipnet-host-index (map addr)
461 "Convert an ADDR into a host index."
462 (let ((addr (ipaddr-addr addr))
464 (dolist (byte (host-map-bytes map))
465 (setf host (logior host
466 (ash (ldb byte addr) offset))
467 offset (+ offset (byte-size byte))))
470 (export 'ipnet-index-bounds)
471 (defun ipnet-index-bounds (map start end)
472 "Return host-index bounds corresponding to the given bit-position bounds."
473 (flet ((hack (frob-map good-byte tweak-addr)
474 (dolist (byte (funcall frob-map (host-map-bytes map)))
475 (let* ((low (byte-position byte))
476 (high (+ low (byte-size byte)))
477 (good (funcall good-byte low high)))
480 (ipnet-host-index map
481 (ipaddr (funcall tweak-addr
484 (host-map-ipnet map))))))))
485 (error "No variable bits in range.")))
486 (values (hack #'identity
488 (and (< start high) (max start low)))
492 (and (>= end low) (min end high)))
496 (defun ipnet-host (ipn host)
497 "Return the address of the given HOST in network IPN.
499 This works even with a non-contiguous netmask."
500 (ipnet-index-host (ipnet-host-map ipn) host))
502 (export 'ipaddr-networkp)
503 (defun ipaddr-networkp (ip ipn)
504 "Returns true if numeric address IP is within network IPN."
505 (with-ipnet (nil addr mask) ipn
506 (= addr (logand ip mask))))
508 (export 'ipnet-subnetp)
509 (defun ipnet-subnetp (ipn subn)
510 "Returns true if SUBN is a (non-strict) subnet of IPN."
511 (with-ipnet (net addr mask) ipn
512 (with-ipnet (subnet subaddr submask) subn
513 (and (ipaddr-comparable-p net subnet)
514 (= addr (logand subaddr mask))
515 (= submask (logior mask submask))))))
517 (export 'ipnet-overlapp)
518 (defun ipnet-overlapp (ipn-a ipn-b)
519 "Returns true if IPN-A and IPN-B have any addresses in common."
520 (with-ipnet (net-a addr-a mask-a) ipn-a
521 (with-ipnet (net-b addr-b mask-b) ipn-b
523 ;; In the case of an overlap, we explicitly construct a common
524 ;; address. If this fails, we know that the networks don't overlap
526 (flet ((narrow (addr-a mask-a addr-b mask-b)
527 ;; Narrow network A towards B, by setting bits in A's base
528 ;; address towards which A is indifferent, but B is not;
529 ;; return the resulting base address. This address is still
530 ;; within network A, since we only set bits to which A is
532 (logior addr-a (logand addr-b (logandc2 mask-a mask-b)))))
534 (and (ipaddr-comparable-p net-a net-b)
535 (= (narrow addr-a mask-a addr-b mask-b)
536 (narrow addr-b mask-b addr-a mask-a)))))))
538 (export 'ipnet-changeable-bits)
539 (defun ipnet-changeable-bits (width mask)
540 "Work out the number of changeable bits in a network, given its MASK.
542 This is a conservative estimate in the case of noncontiguous masks. The
543 WIDTH is the total width of an address."
545 ;; We bisect the address. If the low-order bits are changeable then we
546 ;; recurse on them; otherwise we look at the high-order bits. A mask M of
547 ;; width W is changeable if it's not all-ones, i.e., if M /= 2^W. If the
548 ;; top half is changeable then we don't need to look at the bottom half.
549 (labels ((recurse (width mask offset)
551 (if (zerop mask) (1+ offset) offset)
552 (let* ((lowwidth (floor width 2))
553 (highwidth (- width lowwidth))
554 (highmask (ash mask (- lowwidth))))
555 (if (logbitp highwidth (1+ highmask))
557 (logand mask (mask lowwidth))
559 (recurse highwidth highmask (+ offset lowwidth)))))))
560 (recurse width mask 0)))
562 ;;;--------------------------------------------------------------------------
565 (export 'reverse-domain-component-width)
566 (defgeneric reverse-domain-component-width (ipaddr)
567 (:documentation "Return the component width for splitting IPADDR."))
569 (export 'reverse-domain-component-radix)
570 (defgeneric reverse-domain-radix (ipaddr)
571 (:documentation "Return the radix for representing IPADDR components."))
573 (export 'reverse-domain-component-suffix)
574 (defgeneric reverse-domain-suffix (ipaddr)
575 (:documentation "Return the reverse-lookup domain suffix for IPADDR."))
577 (export 'reverse-domain-fragment)
578 (defgeneric reverse-domain-fragment (ipaddr start end &key partialp)
580 "Return a portion of an IPADDR's reverse-resolution domain name.
582 Specifically, return the portion of the name which covers the bits of an
583 IPADDR between bits START (inclusive) and END (exclusive). Address
584 components which are only partially within the given bounds are included
585 unless PARTIALP is nil.")
586 (:method ((ipaddr ipaddr) start end &key (partialp t))
588 (let ((addr (ipaddr-addr ipaddr))
589 (comp-width (reverse-domain-component-width ipaddr))
590 (radix (reverse-domain-radix ipaddr)))
592 (with-output-to-string (out)
593 (do ((i (funcall (if partialp #'round-down #'round-up)
596 (limit (funcall (if partialp #'round-up #'round-down)
600 (format out "~:[~;.~]~(~vR~)"
601 sep radix (ldb (byte comp-width i) addr)))))))
603 (export 'reverse-domain)
604 (defgeneric reverse-domain (ipaddr-or-ipn &optional prefix-len)
605 (:documentation "Return a reverse-resolution domain name for IPADDR-OR-IPN.
607 If PREFIX-LEN is nil then it defaults to the length of the network's fixed
609 (:method ((ipn ipnet) &optional prefix-len)
610 (let* ((addr (ipnet-net ipn))
611 (mask (ipnet-mask ipn))
612 (width (ipaddr-width addr)))
614 (reverse-domain-fragment
618 (ipnet-changeable-bits width mask))
622 (reverse-domain-suffix addr))))
623 (:method ((addr ipaddr) &optional prefix-len)
624 (let* ((width (ipaddr-width addr)))
625 (reverse-domain (make-ipnet addr (mask width))
626 (or prefix-len width)))))
628 ;;;--------------------------------------------------------------------------
629 ;;; Network names and specifiers.
635 ((name :type string :initarg :name :reader net-name)
636 (ipnets :type list :initarg :ipnets :initform nil :accessor net-ipnets)
637 (next :type unsigned-byte :initform 1 :accessor net-next)))
639 (defmethod print-object ((net net) stream)
640 (print-unreadable-object (net stream :type t)
641 (format stream "~A~@[ = ~{~A~^, ~}~]"
643 (mapcar #'ipnet-string (net-ipnets net)))))
645 (defvar *networks* (make-hash-table :test #'equal)
646 "The table of known networks.")
649 (defun net-find (name)
650 "Find a network by NAME."
651 (gethash (string-downcase (stringify name)) *networks*))
652 (defun (setf net-find) (net name)
653 "Make NAME map to NET."
654 (setf (gethash (string-downcase (stringify name)) *networks*) net))
656 (export 'net-must-find)
657 (defun net-must-find (name)
659 (error "Unknown network ~A." name)))
661 (defun net-ipnet (net family)
662 (find family (net-ipnets net) :key #'ipnet-family))
663 (defun (setf net-ipnet) (ipnet net family)
664 (assert (eq (ipnet-family ipnet) family))
665 (let ((ipns (net-ipnets net)))
666 (if (find family ipns :key #'ipnet-family)
667 (nsubstitute ipnet family ipns :key #'ipnet-family)
668 (setf (net-ipnets net) (cons ipnet ipns)))))
670 (defun process-net-form (name addr subnets)
673 A net-form looks like (NAME ADDR [SUBNET ...]) where:
675 * NAME is the name for the network.
677 * ADDR is the subnet address (acceptable to `string-subipnet'); at
678 top-level, this is a plain network address (acceptable to
679 `string-ipnet'). Alternatively (for compatibility) the ADDR for a
680 non-top-level network can be an integer number of addresses to
681 allocate to this subnet; the subnet's base address is implicitly just
682 past the previous subnet's limit address (or, for the first subnet,
683 it's the parent network's base address). This won't work at all well
684 if your subnets have crazy netmasks.
686 * The SUBNETs are further net-forms, of the same form, whose addresses
687 are interpreted relative to the parent network's address.
689 The return value is a list of items of the form (NAME . IPNET)."
691 (labels ((process-subnets (subnets parent)
692 (let ((finger (ipnet-addr parent))
694 (dolist (subnet subnets list)
695 (destructuring-bind (name addr &rest subs) subnet
696 (let ((net (etypecase addr
698 (when (or (> (count-low-zero-bits addr)
699 (count-low-zero-bits finger))
700 (not (zerop (logand addr
702 (error "Bad subnet size for ~A." name))
704 (ipaddr finger (ipnet-net parent))
705 (ipaddr (- (ash 1 (ipnet-width parent))
707 (ipnet-net parent))))
709 (string-subipnet parent addr)))))
711 (unless (ipnet-subnetp parent net)
712 (error "Network `~A' (~A) falls outside parent ~A."
713 name (ipnet-string net) (ipnet-string parent)))
715 (dolist (entry list nil)
716 (let ((ipn (cdr entry)))
717 (when (ipnet-overlapp ipn net)
718 (error "Network `~A' (~A) overlaps `~A' (~A)."
719 name (ipnet-string net)
720 (car entry) (ipnet-string ipn)))))
725 (logxor (ipnet-mask net)
726 (1- (ash 1 (ipnet-width net)))))))
729 (push (cons name net) list))
732 (setf list (nconc (process-subnets subs net)
735 (let* ((top (string-ipnet addr))
736 (list (nreverse (process-subnets subnets top))))
737 (when name (push (cons name top) list))
741 (defun net-create (name net)
742 "Construct a new network called NAME and add it to the map.
744 The NET describes the new network, in a form acceptable to the `ipnet'
745 function. A named network may have multiple addresses with different
746 families: each `net-create' call adds a new family, or modifies the net's
747 address in an existing family."
748 (let ((ipn (ipnet net))
749 (net (net-find name)))
751 (progn (setf (net-ipnet net (ipnet-family ipn)) ipn) net)
752 (setf (net-find name)
754 :name (string-downcase (stringify name))
755 :ipnets (list ipn))))))
758 (defmacro defnet (name net &rest subnets)
759 "Main network definition macro.
761 None of the arguments is evaluated."
763 ,@(mapcar (lambda (item)
764 (let ((name (car item)) (ipn (cdr item)))
765 `(net-create ',name ',ipn)))
766 (process-net-form name net subnets))
769 (export 'net-parse-to-ipnets)
770 (defun net-parse-to-ipnets (form &optional (family t))
771 (flet ((hack (form family)
772 (let* ((form (if (and (consp form)
776 (net (net-find form))
777 (ipns (if net (net-ipnets net)
778 (list (ipnet form)))))
779 (if (eq family t) ipns
783 (let* ((ipns (if (and (listp form)
784 (every (lambda (clause)
786 (symbolp (car clause))
787 (or (eq (car clause) t)
791 (mappend (lambda (clause)
792 (hack (cdr clause) (car clause)))
795 (merged (reduce (lambda (ipns ipn)
796 (if (find (ipnet-family ipn) ipns
801 :initial-value nil)))
802 (or merged (error "No matching addresses.")))))
805 (defun net-host (net-form host &optional (family t))
806 "Return the given HOST on the NET, as an anonymous `host' object.
808 HOST may be an index (in range, of course), or one of the keywords:
810 :next next host, as by net-next-host
811 :net network base address
812 :broadcast network broadcast address
814 If FAMILY is not `t', then only return an address with that family;
815 otherwise return all available addresses."
816 (flet ((hosts (ipns host)
817 (mapcar (lambda (ipn) (ipnet-host ipn host))
818 (remove host ipns :key #'ipnet-hosts :test-not #'<))))
819 (let* ((net (and (typep net-form '(or string symbol))
820 (net-find net-form)))
821 (ipns (net-parse-to-ipnets net-form family))
825 (prog1 (hosts ipns (net-next net))
826 (incf (net-next net)))
827 (error "Can't use `:next' without a named net.")))
828 (:net (mapcar #'ipnet-net ipns))
829 (:broadcast (remove nil (mapcar #'ipnet-broadcast ipns)))
830 (t (hosts ipns host)))))
832 (error "No networks have that address."))
833 (make-instance 'host :addrs addrs))))
835 ;;;--------------------------------------------------------------------------
836 ;;; Host names and specifiers.
842 ((name :type (or string null) :initform nil
843 :initarg :name :reader host-name)
844 (addrs :type list :initarg :addrs :initform nil :accessor host-addrs)))
846 (defmethod print-object ((host host) stream)
847 (print-unreadable-object (host stream :type t)
848 (format stream "~:[<anonymous>~;~@*~A~]~@[ = ~{~A~^, ~}~]"
850 (mapcar #'ipaddr-string (host-addrs host)))))
852 (defvar *hosts* (make-hash-table :test #'equal)
853 "The table of known hostnames.")
856 (defun host-find (name)
857 "Find a host by NAME."
858 (gethash (string-downcase (stringify name)) *hosts*))
859 (defun (setf host-find) (addr name)
860 "Make NAME map to ADDR (must be an ipaddr in integer form)."
861 (setf (gethash (string-downcase (stringify name)) *hosts*) addr))
863 (defun merge-addresses (addrs-a addrs-b)
864 (append (remove-if (lambda (addr)
865 (member (ipaddr-family addr) addrs-b
866 :key #'ipaddr-family))
871 (defun host-parse (addr &optional (family t))
872 "Convert the ADDR into a (possibly anonymous) `host' object.
874 The ADDR can be one of a number of different things.
876 HOST a host name defined using `defhost'
878 (NET INDEX) a particular host in a network
880 IPADDR an address form acceptable to `ipnet'
882 ((FAMILY . ADDR) ...) the above, restricted to a particular address
883 FAMILY (i.e., one of the keywords `:ipv4',
886 (labels ((filter-addresses (addrs family)
888 :addrs (if (eq family t) addrs
892 (host-addresses (host family)
893 (if (eq family t) host
894 (filter-addresses (host-addrs host) family)))
896 (let* ((form (listify addr))
898 (host (and (null (cdr form))
901 (host-addresses host family))
902 ((and (consp (cdr form))
904 (net-host (car form) (cadr form) family))
906 (filter-addresses (list (ipaddr indic)) family))))))
911 (every (lambda (clause)
913 (symbolp (car clause))
914 (or (eq (car clause) t)
915 (family-addrclass (car clause)))))
918 :addrs (reduce #'merge-addresses
925 :initial-value nil)))
928 (unless (host-addrs host)
929 (error "No matching addresses."))
932 (export 'host-create)
933 (defun host-create (name addr)
934 "Make host NAME map to ADDR (anything acceptable to `host-parse')."
935 (let ((existing (host-find name))
936 (new (host-parse addr)))
938 (setf (host-find name)
940 :name (string-downcase (stringify name))
941 :addrs (host-addrs new)))
943 (setf (host-addrs existing)
944 (merge-addresses (host-addrs existing) (host-addrs new)))
948 (defmacro defhost (name addr)
949 "Main host definition macro. Neither NAME nor ADDR is evaluated."
951 (host-create ',name ',addr)
954 ;;;----- That's all, folks --------------------------------------------------