1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; indent-tabs-mode: nil -*-
3 ;;; services.lisp --- Service lookup.
6 (in-package :net.sockets
)
8 (defvar *services-file
* "/etc/services")
11 ((name :initarg
:name
:reader service-name
12 :documentation
"The service name.")
13 (port :initarg
:port
:reader service-port
14 :documentation
"The service's default port.")
15 (protocol :initarg
:protocol
:reader service-protocol
16 :documentation
"The service's protocol, :TCP or :UDP."))
17 (:documentation
"Class representing a service."))
19 (defun make-service (name port protocol
)
20 "Constructor for SERVICE objects."
21 (let ((port (cond ((numberp port
) port
)
22 ((string port
) (parse-integer port
))))
23 (protocol (cond ((keywordp protocol
) protocol
)
24 ((stringp protocol
) (make-keyword
25 (string-upcase protocol
))))))
26 (make-instance 'service
:name name
:port port
:protocol protocol
)))
28 (defmethod print-object ((service service
) stream
)
29 (print-unreadable-object (service stream
:type t
:identity nil
)
30 (with-slots (name port protocol
) service
31 (format stream
"Name: ~A Port: ~A Protocol: ~A" name port protocol
))))
33 (defun split-port/proto
(port/proto
)
34 (let ((pos (position #\
/ port
/proto
)))
35 (unless pos
(error 'parse-error
))
36 (values (subseq port
/proto
0 pos
)
37 (subseq port
/proto
(1+ pos
)))))
39 (defun protocol-compatible-p (protocol thing
)
42 (:tcp
(eq :tcp
(make-keyword (string-upcase thing
))))
43 (:udp
(eq :udp
(make-keyword (string-upcase thing
))))))
45 (defun find-service-in-parsed-lines (tokens predicate
)
46 (when (< (length tokens
) 2) (error 'parse-error
))
47 (destructuring-bind (name port
/proto
&rest aliases
) tokens
48 (multiple-value-bind (port proto
) (split-port/proto port
/proto
)
49 (when (funcall predicate name port proto aliases
)
50 (make-service name port proto
)))))
52 (defun lookup-service-on-disk-by-number (file service protocol
)
53 (flet ((good-proto-p (name port proto aliases
)
54 (declare (ignore name aliases
))
55 (let ((pnum (parse-integer port
)))
56 (and (protocol-compatible-p protocol proto
)
58 (iterate ((tokens (serialize-etc-file file
)))
60 (let ((proto (find-service-in-parsed-lines tokens
#'good-proto-p
)))
61 (when proto
(return-from lookup-service-on-disk-by-number
64 (defun lookup-service-on-disk-by-name (file service protocol
)
65 (flet ((good-proto-p (name port proto aliases
)
66 (declare (ignore port
))
67 (and (protocol-compatible-p protocol proto
)
68 (or (string= service name
)
69 (member service aliases
:test
#'string
=)))))
70 (iterate ((tokens (serialize-etc-file file
)))
72 (let ((proto (find-service-in-parsed-lines tokens
#'good-proto-p
)))
73 (when proto
(return-from lookup-service-on-disk-by-name
76 (define-condition unknown-service
()
77 ((datum :initarg
:datum
:initform nil
:reader unknown-service-datum
))
78 (:report
(lambda (condition stream
)
79 (format stream
"Unknown service: ~S" (unknown-service-datum condition
))))
80 (:documentation
"Condition raised when a network service is not found."))
81 (setf (documentation 'unknown-service-datum
'function
)
82 "Return the datum that caused the signalling of an UNKNOWN-SERVICE condition.")
84 (defvar *tcp-services-cache-by-name
* (make-hash-table :test
#'equal
))
85 (defvar *tcp-services-cache-by-number
* (make-hash-table :test
#'eql
))
86 (defvar *udp-services-cache-by-name
* (make-hash-table :test
#'equal
))
87 (defvar *udp-services-cache-by-number
* (make-hash-table :test
#'eql
))
88 (defvar *services-cache-lock
* (bt:make-lock
"/etc/services cache lock"))
90 (defun find-service-name-in-cache (thing protocol
)
92 (:tcp
(gethash thing
*tcp-services-cache-by-name
*))
93 (:udp
(gethash thing
*udp-services-cache-by-name
*))
94 (:any
(or (gethash thing
*tcp-services-cache-by-name
*)
95 (gethash thing
*udp-services-cache-by-name
*)))))
97 (defun find-service-number-in-cache (thing protocol
)
99 (:tcp
(gethash thing
*tcp-services-cache-by-number
*))
100 (:udp
(gethash thing
*udp-services-cache-by-number
*))
101 (:any
(or (gethash thing
*tcp-services-cache-by-number
*)
102 (gethash thing
*udp-services-cache-by-number
*)))))
104 (defun find-service (thing protocol cache-fn disk-fn
)
105 (or (funcall cache-fn thing protocol
)
106 (let ((service (funcall disk-fn
*services-file
* thing protocol
)))
107 (flet ((get-cache (type)
109 (:name
(ecase (service-protocol service
)
110 (:tcp
*tcp-services-cache-by-name
*)
111 (:udp
*udp-services-cache-by-name
*)))
112 (:number
(ecase (service-protocol service
)
113 (:tcp
*tcp-services-cache-by-number
*)
114 (:udp
*udp-services-cache-by-number
*))))))
116 (setf (gethash (service-name service
) (get-cache :name
))
118 (setf (gethash (service-port service
) (get-cache :number
))
120 (values service
))))))
122 (defun lookup-service-by-name (thing protocol
)
123 (bt:with-lock-held
(*services-cache-lock
*)
124 (find-service thing protocol
125 #'find-service-name-in-cache
126 #'lookup-service-on-disk-by-name
)))
128 (defun lookup-service-by-number (thing protocol
)
129 (bt:with-lock-held
(*services-cache-lock
*)
130 (find-service thing protocol
131 #'find-service-number-in-cache
132 #'lookup-service-on-disk-by-number
)))
134 (defun purge-services-cache (&optional file
)
135 (declare (ignore file
))
136 (map 'nil
#'clrhash
(list *tcp-services-cache-by-name
*
137 *tcp-services-cache-by-number
*
138 *udp-services-cache-by-name
*
139 *udp-services-cache-by-number
*)))
141 (defvar *services-monitor
*
142 (make-instance 'file-monitor
143 :file
*services-file
*
144 :update-fn
'purge-services-cache
145 :lock
*services-cache-lock
*))
150 (defun lookup-service (service &optional
(protocol :tcp
))
151 "Lookup a service by port or name. PROTOCOL should be one
152 of :TCP, :UDP or :ANY."
153 (check-type service
(or tcp-port string symbol
) "a valid port number, a string or a symbol")
154 (check-type protocol
(member :tcp
:udp
:any
) "one of :TCP, :UDP or :ANY")
155 (update-monitor *services-monitor
*)
156 (let* ((parsed (ensure-string-or-unsigned-byte service
:type
'tcp-port
:errorp t
))
157 (serv (typecase parsed
158 (tcp-port (lookup-service-by-number parsed protocol
))
159 (string (lookup-service-by-name parsed protocol
)))))
161 (values (service-port serv
)
163 (service-protocol serv
))
164 (error 'unknown-service
:datum service
))))
166 (defun ensure-numerical-service (service &optional
(protocol :tcp
))
169 (t (nth-value 0 (lookup-service service protocol
)))))