Various code cleanups in SOCKET-SEND, SOCKET-RECEIVE, etc...
[iolib.git] / sockets / resolv.lisp
blob9654b0b6f4d8906ab20689e75b622b43bc3b48f4
1 ;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp -*-
3 ;; Copyright (C) 2006, 2007 Stelian Ionescu
4 ;;
5 ;; This code is free software; you can redistribute it and/or
6 ;; modify it under the terms of the version 2.1 of
7 ;; the GNU Lesser General Public License as published by
8 ;; the Free Software Foundation, as clarified by the
9 ;; preamble found here:
10 ;; http://opensource.franz.com/preamble.html
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU Lesser General
18 ;; Public License along with this library; if not, write to the
19 ;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 ;; Boston, MA 02110-1301, USA
22 (in-package :net.sockets)
24 ;;;;;;;;;;;;;;;;;;;;;;;;;;;
25 ;;; ;;;
26 ;;; RESOLVER CONDITIONS ;;;
27 ;;; ;;;
28 ;;;;;;;;;;;;;;;;;;;;;;;;;;;
30 (define-constant +resolver-error-map+
31 `((:eai-again . resolver-again-error)
32 (:eai-fail . resolver-fail-error)
33 (:eai-noname . resolver-no-name-error)
34 (:eai-nodata . resolver-no-name-error)
35 (:eai-addrfamily . resolver-no-name-error)
36 (:eai-service . resolver-no-service-error)))
38 (defun resolver-error-condition (id)
39 (cdr (assoc id +resolver-error-map+)))
41 (defmacro resolver-error-code (id)
42 `(addrerr-value ,id))
44 (define-condition resolver-error (system-error)
45 ((data :initarg :data :reader resolver-error-data))
46 (:documentation "Signaled when an error occurs while trying to resolve an address."))
48 (defmacro define-resolver-error (name code identifier format-string &optional documentation)
49 `(progn
50 (export ',name)
51 (define-condition ,name (resolver-error)
52 ((code :initform ,code)
53 (identifier :initform ,identifier))
54 (:report (lambda (condition stream)
55 (format stream ,format-string (resolver-error-data condition))
56 (print-message-if-not-null condition stream)))
57 (:documentation ,documentation))))
59 (define-resolver-error resolver-again-error (resolver-error-code :eai-again) :resolver-again
60 "Temporary failure occurred while resolving: ~S"
61 "Condition signaled when a temporary failure occurred.")
63 (define-resolver-error resolver-fail-error (resolver-error-code :eai-fail) :resolver-fail
64 "Non recoverable error occurred while resolving: ~S"
65 "Condition signaled when a non-recoverable error occurred.")
67 (define-resolver-error resolver-no-name-error (resolver-error-code :eai-noname) :resolver-no-name
68 "Host or service not found: ~S"
69 "Condition signaled when a host or service was not found.")
71 (define-resolver-error resolver-no-service-error (resolver-error-code :eai-service) :resolver-no-service
72 "Service not found for specific socket type: ~S"
73 "Condition signaled when a service was not found for the socket type requested.")
75 (define-resolver-error resolver-unknown-error 0 :resolver-unknown
76 "Unknown error while resolving: ~S"
77 "Condition signaled when an unknown error is signaled while resolving an address.")
79 (defun resolver-error (identifier &key data message)
80 (let ((condition-class
81 (resolver-error-condition identifier)))
82 (if condition-class
83 (error condition-class
84 :code (resolver-error-code identifier)
85 :identifier identifier
86 :data data
87 :message message)
88 (error 'resolver-unknown-error
89 :code (or (ignore-errors
90 (resolver-error-code identifier))
92 :identifier identifier
93 :data data
94 :message message))))
96 (define-constant +max-ipv4-value+ (1- (expt 2 32)))
99 ;;;;;;;;;;;;;;;;;;;;
100 ;;;;;;;;;;;;;;;;;;;;
101 ;;; ;;;
102 ;;; HOST LOOKUP ;;;
103 ;;; ;;;
104 ;;;;;;;;;;;;;;;;;;;;
105 ;;;;;;;;;;;;;;;;;;;;
107 (defun get-address-info (&key (node (null-pointer)) (service (null-pointer))
108 (hint-flags 0) (hint-family 0)
109 (hint-type 0) (hint-protocol 0))
110 (with-foreign-objects ((hints 'et:addrinfo)
111 (res :pointer))
112 (et:bzero hints et:size-of-addrinfo)
113 (with-foreign-slots ((et:flags et:family et:socktype et:protocol)
114 hints et:addrinfo)
115 (setf et:flags hint-flags)
116 (setf et:family hint-family)
117 (setf et:socktype hint-type)
118 (setf et:protocol hint-protocol)
119 (et:getaddrinfo node service hints res)
120 (make-pointer (pointer-address (mem-ref res :pointer))))))
122 (defun get-name-info (sockaddr &key (want-host t) want-service (flags 0))
123 (assert (or want-host want-service))
124 (let ((salen et:size-of-sockaddr-storage))
125 (with-foreign-objects ((host :char et:ni-maxhost)
126 (service :char et:ni-maxserv))
127 (et:getnameinfo sockaddr salen
128 host (if want-host et:ni-maxhost 0)
129 service (if want-service et:ni-maxserv 0)
130 flags)
131 (values (and want-host (foreign-string-to-lisp host et:ni-maxhost))
132 (and want-service (foreign-string-to-lisp service et:ni-maxserv))))))
134 (defclass host ()
135 ((truename :initarg :truename :reader host-truename)
136 (aliases :initarg :aliases :reader host-aliases)
137 (addresses :initarg :addresses :reader host-addresses)))
139 (defmethod initialize-instance :after ((host host) &key)
140 (with-slots (addresses) host
141 (unless (consp addresses)
142 (setf addresses (list addresses)))))
144 (defgeneric random-address (host))
145 (defmethod random-address ((host host))
146 (with-slots (addresses) host
147 (nth (random (length addresses))
148 addresses)))
150 (defun make-host (truename addresses &optional aliases)
151 (make-instance 'host
152 :truename truename
153 :aliases aliases
154 :addresses addresses))
156 (defmethod print-object ((host host) stream)
157 (print-unreadable-object (host stream :type t :identity nil)
158 (with-slots (truename aliases addresses) host
159 (format stream "Cannonical name: ~S. Aliases: ~:[None~;~:*~{~S~^, ~}~].~%Addresses: ~{~A~^, ~}"
160 truename aliases addresses))))
164 ;; Error management
167 (defun lookup-host-u8-vector-4 (host ipv6)
168 (coercef host 'ub8-sarray)
169 (handler-case
170 (ecase ipv6
171 ((nil)
172 (with-foreign-object (sin 'et:sockaddr-storage)
173 (make-sockaddr-in sin host)
174 (return-from lookup-host-u8-vector-4
175 (make-host (get-name-info sin :flags et:ni-namereqd)
176 (list (make-address (copy-seq host)))))))
177 ((t)
178 (with-foreign-object (sin6 'et:sockaddr-storage)
179 (let ((ipv6addr (map-ipv4-vector-to-ipv6 host)))
180 (make-sockaddr-in6 sin6 ipv6addr)
181 (return-from lookup-host-u8-vector-4
182 (make-host (get-name-info sin6 :flags et:ni-namereqd)
183 (list (make-address ipv6addr)))))))
184 ((:ipv6)
185 (resolver-error :eai-fail
186 :data host
187 :message "Received IPv4 address but IPv6-only was requested.")))
188 (et:resolv-error (err)
189 (resolver-error (et:system-error-identifier err) :data host))))
191 (defun lookup-host-u16-vector-8 (host ipv6)
192 (coercef host 'ub16-sarray)
193 (handler-case
194 (ecase ipv6
195 ((nil)
196 (resolver-error :eai-fail
197 :data host
198 :message "Received IPv6 address but IPv4-only was requested."))
199 ((:ipv6 t)
200 (with-foreign-object (sin6 'et:sockaddr-storage)
201 (make-sockaddr-in6 sin6 host)
202 (return-from lookup-host-u16-vector-8
203 (make-host (get-name-info sin6 :flags et:ni-namereqd)
204 (list (make-address (copy-seq host))))))))
205 (et:resolv-error (err)
206 (resolver-error (et:system-error-identifier err) :data host))))
208 (defun make-host-from-addrinfo (addrinfo)
209 (let ((canonname (foreign-slot-value addrinfo 'et:addrinfo 'et:canonname))
210 (addrlist
211 (loop
212 :for addrptr := addrinfo
213 :then (foreign-slot-value addrptr 'et:addrinfo 'et:next)
214 :while (not (null-pointer-p addrptr))
215 :collect (sockaddr-storage->sockaddr
216 (foreign-slot-value addrptr 'et:addrinfo 'et:addr)))))
217 (make-host (if (null-pointer-p canonname)
219 (foreign-string-to-lisp canonname))
220 addrlist)))
222 (defun map-host-ipv4-addresses-to-ipv6 (hostobj)
223 (declare (type host hostobj))
224 (with-slots (addresses) hostobj
225 (setf addresses
226 (mapcar #'(lambda (address)
227 (if (ipv4-address-p address)
228 (make-address (map-ipv4-vector-to-ipv6 (name address)))
229 address))
230 addresses)))
231 hostobj)
233 (defgeneric lookup-host (host &key &allow-other-keys))
235 (defmethod lookup-host :before (host &key (ipv6 *ipv6*))
236 (declare (ignore host))
237 (check-type ipv6 (member nil :ipv6 t) "valid IPv6 configuration"))
239 (defmethod lookup-host ((host string) &key (ipv6 *ipv6*))
240 (flet ((decide-family-and-flags ()
241 (ecase ipv6
242 ((nil) (values et:af-inet 0))
243 ;; the freebsd I use rejects AI_V4MAPPED and AI_ALL(weird thing)
244 ;; therefore I'll use AF_UNSPEC and do the mappings myself
245 ((t) (values
246 #-freebsd et:af-inet6
247 #+freebsd et:af-unspec
248 #+freebsd 0
249 #-freebsd
250 (logior et:ai-v4mapped
251 #+freebsd et:ai-v4mapped-cfg
252 et:ai-all)))
253 (:ipv6 (values et:af-inet6 0)))))
254 (let (parsed)
255 (cond
256 ((setf parsed (dotted-to-vector host :errorp nil))
257 (return-from lookup-host
258 (lookup-host-u8-vector-4 parsed ipv6)))
259 ((setf parsed (colon-separated-to-vector host :errorp nil))
260 (return-from lookup-host
261 (lookup-host-u16-vector-8 parsed ipv6)))
262 ;; FIXME: check for ASCII-only strings or implement IDN
264 (multiple-value-bind (family flags)
265 (decide-family-and-flags)
266 (setf flags (logior flags et:ai-canonname et:ai-addrconfig))
267 (handler-case
268 (let* ((addrinfo
269 (get-address-info :node host
270 :hint-flags flags
271 :hint-family family
272 :hint-type et:sock-stream
273 :hint-protocol et:ipproto-ip))
274 (hostobj (make-host-from-addrinfo addrinfo)))
275 (when (string-not-equal (host-truename hostobj)
276 host)
277 (setf (slot-value hostobj 'aliases) (list host)))
278 (et:freeaddrinfo addrinfo)
279 ;; mapping IPv4 addresses onto IPv6
280 #+freebsd
281 (when (eql ipv6 t)
282 (map-host-ipv4-addresses-to-ipv6 hostobj))
283 (return-from lookup-host hostobj))
284 (et:resolv-error (err)
285 (resolver-error (et:system-error-identifier err) :data host)))))))))
287 (defmethod lookup-host ((host ipv4addr) &key (ipv6 *ipv6*))
288 (lookup-host-u8-vector-4 (name host) ipv6))
290 (defmethod lookup-host ((host ipv6addr) &key (ipv6 *ipv6*))
291 (lookup-host-u16-vector-8 (name host) ipv6))
293 (defmethod lookup-host (host &key (ipv6 *ipv6*))
294 (etypecase host
295 ((vector * 4) ; IPv4 address
296 (lookup-host-u8-vector-4 host ipv6))
297 ((vector * 8) ; IPv6 address
298 (lookup-host-u16-vector-8 host ipv6))))
302 ;;;;;;;;;;;;;;;;;;;;;;;
303 ;;;;;;;;;;;;;;;;;;;;;;;
304 ;;; ;;;
305 ;;; SERVICE LOOKUP ;;;
306 ;;; ;;;
307 ;;;;;;;;;;;;;;;;;;;;;;;
308 ;;;;;;;;;;;;;;;;;;;;;;;
310 (defclass service ()
311 ((name :initarg :name :reader service-name)
312 (port :initarg :port :reader service-port)
313 (protocol :initarg :protocol :reader service-protocol)))
315 (defun make-service (name port protocol)
316 (make-instance 'service
317 :name name
318 :port port
319 :protocol protocol))
321 (defmethod print-object ((service service) stream)
322 (print-unreadable-object (service stream :type t :identity nil)
323 (with-slots (name port protocol) service
324 (format stream "Name: ~A. Port: ~A. Protocol: ~A" name port protocol))))
326 (defun socket-type-from-int (alien-val)
327 (case alien-val
328 (#.et:sock-stream :tcp)
329 (#.et:sock-dgram :udp)
330 (#.et:sock-seqpacket :sctp)
331 (#.et:sock-raw :raw)
332 (t :unknown)))
334 (defun lookup-service-number (port-number protocol &key name-required)
335 (declare (type ub32 port-number))
336 (with-foreign-object (sin 'et:sockaddr-in)
337 (let ((service
338 (nth-value 1
339 (progn
340 (et:bzero sin et:size-of-sockaddr-in)
341 (with-foreign-slots ((et:family et:port) sin et:sockaddr-in)
342 (setf et:family et:af-inet
343 et:port (htons port-number)))
344 (get-name-info sin
345 :flags (logior
346 (case protocol
347 (:udp et:ni-dgram)
348 (t 0))
349 (if name-required
350 et:ni-namereqd 0))
351 :want-host nil :want-service t)))))
352 (make-service service port-number protocol))))
354 (defun protocol-type-from-int (protocol)
355 (case protocol
356 (:tcp et:sock-stream)
357 (:udp et:sock-dgram)
358 (:any 0)))
360 (defun lookup-service-name (port protocol)
361 (let* ((addrinfo
362 (get-address-info :service port
363 :hint-type (protocol-type-from-int protocol)))
364 (port-number
365 (ntohs (foreign-slot-value (foreign-slot-value addrinfo 'et:addrinfo 'et:addr)
366 'et:sockaddr-in 'et:port)))
367 (true-protocol
368 (socket-type-from-int (foreign-slot-value addrinfo 'et:addrinfo 'et:socktype))))
369 (et:freeaddrinfo addrinfo)
370 (return-from lookup-service-name
371 (make-service port port-number true-protocol))))
373 (defun lookup-service (port &key (protocol :tcp) (name-required nil))
374 (check-type protocol (member :tcp :udp :any))
376 (let ((parsed-number (parse-number-or-nil port :ub16)))
377 (handler-case
378 (if parsed-number
379 (lookup-service-number parsed-number protocol
380 :name-required name-required)
381 (lookup-service-name port protocol))
382 (et:resolv-error (err)
383 (resolver-error (et:system-error-identifier err) :data port)))))
387 ;;;;;;;;;;;;;;;;;;;;;;;;
388 ;;;;;;;;;;;;;;;;;;;;;;;;
389 ;;; ;;;
390 ;;; PROTOCOL LOOKUP ;;;
391 ;;; ;;;
392 ;;;;;;;;;;;;;;;;;;;;;;;;
393 ;;;;;;;;;;;;;;;;;;;;;;;;
395 (defclass protocol ()
396 ((name :initarg :name :reader protocol-name)
397 (aliases :initarg :aliases :reader protocol-aliases)
398 (protonum :initarg :protonum :reader protocol-number)))
400 (defun make-protocol (name protonum &optional aliases)
401 (make-instance 'protocol
402 :name name
403 :protonum protonum
404 :aliases aliases))
406 (defmethod print-object ((protocol protocol) stream)
407 (print-unreadable-object (protocol stream :type t :identity nil)
408 (with-slots (name aliases protonum) protocol
409 (format stream "Name: ~S. Protocol number: ~A. Aliases: ~{~S~^, ~}"
410 name protonum aliases))))
412 (define-condition unknown-protocol (system-error)
413 ((name :initarg :name :initform nil :reader protocol-name))
414 (:report (lambda (condition stream)
415 (format stream "Unknown protocol: ~S"
416 (protocol-name condition))))
417 (:documentation "Condition raised when a network protocol is not found."))
419 (defun make-protocol-from-protoent (protoent)
420 (with-foreign-slots ((et:name et:proto et:aliases) protoent et:protoent)
421 (let ((alias-strings
422 (loop
423 :for i :from 0
424 :for alias := (mem-aref et:aliases :string i)
425 :while alias :collect alias)))
426 (make-protocol et:name et:proto alias-strings))))
428 (defun get-protocol-by-number (protonum)
429 (make-protocol-from-protoent (et:getprotobynumber protonum)))
431 (defun get-protocol-by-name (protoname)
432 (make-protocol-from-protoent (et:getprotobyname protoname)))
434 (defun lookup-protocol (proto)
435 (let ((parsed-number (parse-number-or-nil proto)))
436 (handler-case
437 (if parsed-number
438 (get-protocol-by-number parsed-number)
439 (get-protocol-by-name proto))
440 (unix-error (err)
441 (declare (ignore err))
442 (error 'unknown-protocol :name proto)))))