Reformat the docstrings.
[zone] / net.lisp
1 ;;; -*-lisp-*-
2 ;;;
3 ;;; $Id$
4 ;;;
5 ;;; Network (numbering) tools
6 ;;;
7 ;;; (c) 2006 Straylight/Edgeware
8 ;;;
9
10 ;;;----- Licensing notice ---------------------------------------------------
11 ;;;
12 ;;; This program is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 2 of the License, or
15 ;;; (at your option) any later version.
16 ;;;
17 ;;; This program is distributed in the hope that it will be useful,
18 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
21 ;;;
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with this program; if not, write to the Free Software Foundation,
24 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 ;;;--------------------------------------------------------------------------
27 ;;; Packaging.
28
29 (defpackage #:net
30 (:use #:common-lisp #:mdw.base #:mdw.str #:collect)
31 (:export #:ipaddr #:string-ipaddr #:ipaddr-byte #:ipaddr-string #:ipaddrp
32 #:integer-netmask #:ipmask #:ipmask-cidl-slash #:make-ipnet
33 #:string-ipnet #:ipnet #:ipnet-net #:ipnet-mask #:with-ipnet
34 #:ipnet-pretty #:ipnet-string #:ipnet-broadcast #:ipnet-hosts
35 #:ipnet-host #:ipaddr-networkp #:ipnet-subnetp
36 #:ipnet-changeable-bytes
37 #:host-find# #:host-create #:defhost #:parse-ipaddr
38 #:resolve-hostname #:canonify-hostname
39 #:net #:net-find #:net-get-as-ipnet #:net-create #:defnet
40 #:net-next-host #:net-host))
41
42 (in-package #:net)
43
44 ;;;--------------------------------------------------------------------------
45 ;;; Basic types.
46
47 (defun mask (n)
48 "Return 2^N - 1: i.e., a mask of N set bits."
49 (1- (ash 1 n)))
50
51 (deftype u32 ()
52 "The type of unsigned 32-bit values."
53 '(unsigned-byte 32))
54
55 (deftype ipaddr ()
56 "The type of IP (version 4) addresses."
57 'u32)
58
59 ;;;--------------------------------------------------------------------------
60 ;;; Various random utilities.
61
62 (defun count-low-zero-bits (n)
63 "Return the number of low-order zero bits in the integer N."
64 (if (zerop n) nil
65 (loop for i from 0
66 until (logbitp i n)
67 finally (return i))))
68
69 ;;;--------------------------------------------------------------------------
70 ;;; Simple messing with IP addresses.
71
72 (defun string-ipaddr (str &key (start 0) (end nil))
73 "Parse STR as an IP address in dotted-quad form and return the integer
74 equivalent. STR may be anything at all: it's converted as if by
75 `stringify'. The START and END arguments may be used to parse out a
76 substring."
77 (setf str (stringify str))
78 (unless end
79 (setf end (length str)))
80 (let ((addr 0) (noct 0))
81 (loop
82 (let* ((pos (position #\. str :start start :end end))
83 (i (parse-integer str :start start :end (or pos end))))
84 (unless (<= 0 i 256)
85 (error "IP address octet out of range"))
86 (setf addr (+ (* addr 256) i))
87 (incf noct)
88 (unless pos
89 (return))
90 (setf start (1+ pos))))
91 (unless (= noct 4)
92 (error "Wrong number of octets in IP address"))
93 addr))
94
95 (defun ipaddr-byte (ip n)
96 "Return byte N (from most significant downwards) of an IP address."
97 (assert (<= 0 n 3))
98 (logand #xff (ash ip (* -8 (- 3 n)))))
99
100 (defun ipaddr-string (ip)
101 "Transform the address IP into a string in dotted-quad form."
102 (check-type ip ipaddr)
103 (join-strings #\. (collecting ()
104 (dotimes (i 4)
105 (collect (ipaddr-byte ip i))))))
106
107 (defun ipaddrp (ip)
108 "Answer true if IP is a valid IP address in integer form."
109 (typep ip 'ipaddr))
110
111 (defun ipaddr (ip)
112 "Convert IP to an IP address. If it's an integer, return it unchanged;
113 otherwise convert by `string-ipaddr'."
114 (typecase ip
115 (ipaddr ip)
116 (t (string-ipaddr ip))))
117
118 ;;;--------------------------------------------------------------------------
119 ;;; Netmasks.
120
121 (defun integer-netmask (i)
122 "Given an integer I, return a netmask with its I top bits set."
123 (- (ash 1 32) (ash 1 (- 32 i))))
124
125 (defun ipmask (ip)
126 "Transform IP into a netmask. If it's a small integer then it's converted
127 by `integer-netmask'; if nil, then all-bits-set; otherwise convert using
128 `ipaddr'."
129 (typecase ip
130 (null (mask 32))
131 ((integer 0 32) (integer-netmask ip))
132 (t (ipaddr ip))))
133
134 (defun ipmask-cidl-slash (mask)
135 "Given a netmask MASK, return an integer N such that (integer-netmask N) =
136 MASK, or nil if this is impossible."
137 (dotimes (i 33)
138 (when (= mask (integer-netmask i))
139 (return i))))
140
141 ;;;--------------------------------------------------------------------------
142 ;;; Networks: pairing an address and netmask.
143
144 (defun make-ipnet (net mask)
145 "Construct an IP-network object given the NET and MASK; these are
146 transformed as though by `ipaddr' and `ipmask'."
147 (let ((net (ipaddr net))
148 (mask (ipmask mask)))
149 (cons (logand net mask) mask)))
150
151 (defun string-ipnet (str &key (start 0) (end nil))
152 "Parse an IP-network from the string STR."
153 (setf str (stringify str))
154 (unless end (setf end (length str)))
155 (let ((sl (position #\/ str :start start :end end)))
156 (if sl
157 (make-ipnet (parse-ipaddr (subseq str start sl))
158 (if (find #\. str :start (1+ sl) :end end)
159 (string-ipaddr str :start (1+ sl) :end end)
160 (integer-netmask (parse-integer str
161 :start (1+ sl)
162 :end end))))
163 (make-ipnet (parse-ipaddr (subseq str start end))
164 (integer-netmask 32)))))
165
166 (defun ipnet (net)
167 "Construct an IP-network object from the given argument. A number of forms
168 are acceptable:
169
170 * ADDR -- a single address (equivalent to ADDR 32)
171 * (NET . MASK|nil) -- a single-object representation.
172 * IPNET -- return an equivalent (`equal', not necessarily `eql')
173 version."
174 (cond ((or (stringp net) (symbolp net)) (string-ipnet net))
175 (t (apply #'make-ipnet (pairify net 32)))))
176
177 (defun ipnet-net (ipn)
178 "Return the base network address of IPN."
179 (car ipn))
180
181 (defun ipnet-mask (ipn)
182 "Return the netmask of IPN."
183 (cdr ipn))
184
185 (defmacro with-ipnet ((net mask) ipn &body body)
186 "Evaluate BODY with NET and MASK bound to the base address and netmask of
187 IPN. Either NET or MASK (or, less usefully, both) may be nil if not
188 wanted."
189 (with-gensyms tmp
190 `(let ((,tmp ,ipn))
191 (let (,@(and net `((,net (ipnet-net ,tmp))))
192 ,@(and mask `((,mask (ipnet-mask ,tmp)))))
193 ,@body))))
194
195 (defun ipnet-pretty (ipn)
196 "Convert IPN to a pretty cons-cell form."
197 (with-ipnet (net mask) ipn
198 (cons (ipaddr-string net)
199 (or (ipmask-cidl-slash mask) (ipaddr-string mask)))))
200
201 (defun ipnet-string (ipn)
202 "Convert IPN to a string."
203 (with-ipnet (net mask) ipn
204 (format nil "~A/~A"
205 (ipaddr-string net)
206 (or (ipmask-cidl-slash mask) (ipaddr-string mask)))))
207
208 (defun ipnet-broadcast (ipn)
209 "Return the broadcast address for the network IPN."
210 (with-ipnet (net mask) ipn
211 (logior net (logxor (mask 32) mask))))
212
213 (defun ipnet-hosts (ipn)
214 "Return the number of available addresses in network IPN."
215 (ash 1 (- 32 (logcount (ipnet-mask ipn)))))
216
217 (defun ipnet-host (ipn host)
218 "Return the address of the given HOST in network IPN. This works even with
219 a non-contiguous netmask."
220 (check-type host u32)
221 (with-ipnet (net mask) ipn
222 (let ((i 0) (m 1) (a net) (h host))
223 (loop
224 (when (>= i 32)
225 (error "Host index ~D out of range for network ~A"
226 host (ipnet-pretty ipn)))
227 (cond ((zerop h)
228 (return a))
229 ((logbitp i mask)
230 (setf h (ash h 1)))
231 (t
232 (setf a (logior a (logand m h)))
233 (setf h (logandc2 h m))))
234 (setf m (ash m 1))
235 (incf i)))))
236
237 (defun ipaddr-networkp (ip ipn)
238 "Returns true if address IP is within network IPN."
239 (with-ipnet (net mask) ipn
240 (= net (logand ip mask))))
241
242 (defun ipnet-subnetp (ipn subn)
243 "Returns true if SUBN is a (non-strict) subnet of IPN."
244 (with-ipnet (net mask) ipn
245 (with-ipnet (subnet submask) subn
246 (and (= net (logand subnet mask))
247 (= submask (logior mask submask))))))
248
249 (defun ipnet-changeable-bytes (mask)
250 "Answers how many low-order bytes of MASK are (entirely or partially)
251 changeable. This is used when constructing reverse zones."
252 (dotimes (i 4 4)
253 (when (/= (ipaddr-byte mask i) 255)
254 (return (- 4 i)))))
255
256 ;;;--------------------------------------------------------------------------
257 ;;; Name resolution.
258
259 #+cmu
260 (defun resolve-hostname (name)
261 "Resolve a hostname to an IP address using the DNS, or return nil."
262 (let ((he (ext:lookup-host-entry name)))
263 (and he
264 (ext:host-entry-addr he))))
265
266 #+cmu
267 (defun canonify-hostname (name)
268 "Resolve a hostname to canonical form using the DNS, or return nil."
269 (let ((he (ext:lookup-host-entry name)))
270 (and he
271 (ext:host-entry-name he))))
272
273 ;;;--------------------------------------------------------------------------
274 ;;; Host names and specifiers.
275
276 (defun parse-ipaddr (addr)
277 "Convert the string ADDR into an IP address: tries all sorts of things:
278
279 (NET [INDEX]) index a network: NET is a network name defined by
280 defnet; INDEX is an index or one of the special
281 symbols understood by net-host, and defaults to :next
282
283 INTEGER an integer IP address
284
285 IPADDR an IP address in dotted-quad form
286
287 HOST a host name defined by defhost
288
289 DNSNAME a name string to look up in the DNS"
290 (cond ((listp addr)
291 (destructuring-bind
292 (net host)
293 (pairify addr :next)
294 (net-host (or (net-find net)
295 (error "Network ~A not found" net))
296 host)))
297 ((ipaddrp addr) addr)
298 (t
299 (setf addr (string-downcase (stringify addr)))
300 (or (host-find addr)
301 (and (plusp (length addr))
302 (digit-char-p (char addr 0))
303 (string-ipaddr addr))
304 (resolve-hostname (stringify addr))
305 (error "Host name ~A unresolvable" addr)))))
306
307 (defvar *hosts* (make-hash-table :test #'equal)
308 "The table of known hostnames.")
309
310 (defun host-find (name)
311 "Find a host by NAME."
312 (gethash (string-downcase (stringify name)) *hosts*))
313
314 (defun (setf host-find) (addr name)
315 "Make NAME map to ADDR (must be an ipaddr in integer form)."
316 (setf (gethash (string-downcase (stringify name)) *hosts*) addr))
317
318 (defun host-create (name addr)
319 "Make host NAME map to ADDR (anything acceptable to parse-ipaddr)."
320 (setf (host-find name) (parse-ipaddr addr)))
321
322 (defmacro defhost (name addr)
323 "Main host definition macro. Neither NAME nor ADDR is evaluated."
324 `(progn
325 (host-create ',name ',addr)
326 ',name))
327
328 ;;;--------------------------------------------------------------------------
329 ;;; Network names and specifiers.
330
331 (defstruct (net (:predicate netp))
332 "A network structure. Slots:
333
334 NAME The network's name, as a string
335 IPNET The network base address and mask
336 HOSTS Number of hosts in the network
337 NEXT Index of the next unassigned host"
338 name
339 ipnet
340 hosts
341 next)
342
343 (defvar *networks* (make-hash-table :test #'equal)
344 "The table of known networks.")
345
346 (defun net-find (name)
347 "Find a network by NAME."
348 (gethash (string-downcase (stringify name)) *networks*))
349
350 (defun (setf net-find) (net name)
351 "Make NAME map to NET."
352 (setf (gethash (string-downcase (stringify name)) *networks*) net))
353
354 (defun net-get-as-ipnet (form)
355 "Transform FORM into an ipnet. FORM may be a network name, or something
356 acceptable to the ipnet function."
357 (let ((net (net-find form)))
358 (if net (net-ipnet net)
359 (ipnet form))))
360
361 (defun process-net-form (root addr subnets)
362 "Unpack a net-form. The return value is a list of entries, each of which
363 is a list of the form (NAME ADDR MASK). The first entry is merely repeats
364 the given ROOT and ADDR arguments (unpacking ADDR into separate network
365 address and mask). The SUBNETS are then processed: they are a list of
366 items of the form (NAME NUM-HOSTS . SUBNETS), where NAME names the subnet,
367 NUM-HOSTS is the number of hosts in it, and SUBNETS are its sub-subnets in
368 the same form. An error is signalled if a net's subnets use up more hosts
369 than the net has to start with."
370 (labels ((frob (subnets limit finger)
371 (when subnets
372 (destructuring-bind (name size &rest subs) (car subnets)
373 (when (> (count-low-zero-bits size)
374 (count-low-zero-bits finger))
375 (error "Bad subnet size for ~A." name))
376 (when (> (+ finger size) limit)
377 (error "Subnet ~A out of range." name))
378 (append (and name
379 (list (list name finger (- (ash 1 32) size))))
380 (frob subs (+ finger size) finger)
381 (frob (cdr subnets) limit (+ finger size)))))))
382 (let ((ipn (ipnet addr)))
383 (with-ipnet (net mask) ipn
384 (unless (ipmask-cidl-slash mask)
385 (error "Bad mask for subnet form."))
386 (cons (list root net mask)
387 (frob subnets (+ net (ipnet-hosts ipn) 1) net))))))
388
389 (defun net-create (name net)
390 "Construct a new network called NAME and add it to the map. The ARGS
391 describe the new network, in a form acceptable to the ipnet function."
392 (let ((ipn (ipnet net)))
393 (setf (net-find name)
394 (make-net :name (string-downcase (stringify name))
395 :ipnet ipn
396 :hosts (ipnet-hosts ipn)
397 :next 1))))
398
399 (defmacro defnet (name net &rest subnets)
400 "Main network definition macro. None of the arguments is evaluated."
401 `(progn
402 ,@(loop for (name addr mask) in (process-net-form name net subnets)
403 collect `(net-create ',name '(,addr . ,mask)))
404 ',name))
405
406 (defun net-next-host (net)
407 "Given a NET, return the IP address (as integer) of the next available
408 address in the network."
409 (unless (< (net-next net) (net-hosts net))
410 (error "No more hosts left in network ~A" (net-name net)))
411 (let ((next (net-next net)))
412 (incf (net-next net))
413 (net-host net next)))
414
415 (defun net-host (net host)
416 "Return the given HOST on the NEXT. HOST may be an index (in range, of
417 course), or one of the keywords:
418
419 :NEXT next host, as by net-next-host
420 :NET network base address
421 :BROADCAST network broadcast address"
422 (case host
423 (:next (net-next-host net))
424 (:net (ipnet-net (net-ipnet net)))
425 (:broadcast (ipnet-broadcast (net-ipnet net)))
426 (t (ipnet-host (net-ipnet net) host))))
427
428 ;;;----- That's all, folks --------------------------------------------------