[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[shepherd] 21/24: service: Add #:max-connections to 'make-inetd-construc
From: |
Ludovic Courtès |
Subject: |
[shepherd] 21/24: service: Add #:max-connections to 'make-inetd-constructor'. |
Date: |
Mon, 28 Mar 2022 17:24:48 -0400 (EDT) |
civodul pushed a commit to branch wip-fibers
in repository shepherd.
commit 9d65aee5c9cb0cbd767bd201a31be64a4d941bb1
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Mon Mar 28 20:40:43 2022 +0200
service: Add #:max-connections to 'make-inetd-constructor'.
* modules/shepherd/service.scm (default-inetd-max-connections): New
variable.
(make-inetd-constructor): Add #:max-connections.
[connection-count]: New variables.
[handle-child-termination, spawn-child-service]: New procedures.
Close CONNECTION when CONNECTION-COUNT is greater than MAX-CONNECTIONS.
* tests/inetd.sh: Test it.
* doc/shepherd.texi (Service De- and Constructors): Document it.
---
doc/shepherd.texi | 8 +++-
modules/shepherd/service.scm | 100 +++++++++++++++++++++++++++++--------------
tests/inetd.sh | 26 ++++++++++-
3 files changed, 99 insertions(+), 35 deletions(-)
diff --git a/doc/shepherd.texi b/doc/shepherd.texi
index edd4dd9..edce1e9 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -1068,6 +1068,7 @@ in charge of listening.
@deffn {procedure} make-inetd-constructor @var{command} @var{address}
[#:service-name-stem _] [#:requirements '()] @
[#:socket-style SOCK_STREAM] [#:listen-backlog 10] @
+ [#:max-connections (default-inetd-max-connections)] @
[#:user #f] @
[#:group #f] @
[#:supplementary-groups '()] @
@@ -1079,8 +1080,11 @@ Return a procedure that opens a socket listening to
@var{address}, an
object as returned by @code{make-socket-address}, and accepting connections in
the background; the @var{listen-backlog} argument is passed to @var{accept}.
Upon a client connection, a transient service running @var{command} is
-spawned. The remaining arguments are as for
-@code{make-forkexec-constructor}.
+spawned. Only up to @var{max-connections} simultaneous connections are
+accepted; when that threshold is reached, new connections are immediately
+closed.
+
+The remaining arguments are as for @code{make-forkexec-constructor}.
@end deffn
@deffn {procedure} make-inetd-destructor
diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index e9f90c2..83c785a 100644
--- a/modules/shepherd/service.scm
+++ b/modules/shepherd/service.scm
@@ -91,6 +91,7 @@
read-pid-file
make-system-constructor
make-system-destructor
+ default-inetd-max-connections
make-inetd-constructor
make-inetd-destructor
@@ -1295,6 +1296,11 @@ as argument, where SIGNAL defaults to `SIGTERM'."
(number->string (sockaddr:port client))))
'())))
+(define default-inetd-max-connections
+ ;; Default maximum number of simultaneous connections for an inetd-style
+ ;; service.
+ (make-parameter 100))
+
(define* (make-inetd-constructor command address
#:key
(service-name-stem
@@ -1304,7 +1310,8 @@ as argument, where SIGNAL defaults to `SIGTERM'."
(requirements '())
(socket-style SOCK_STREAM)
(listen-backlog 10)
- ;; TODO: Add #:max-connections.
+ (max-connections
+ (default-inetd-max-connections))
(user #f)
(group #f)
(supplementary-groups '())
@@ -1318,8 +1325,11 @@ as argument, where SIGNAL defaults to `SIGTERM'."
object as returned by @code{make-socket-address}, and accepting connections in
the background; the @var{listen-backlog} argument is passed to @var{accept}.
Upon a client connection, a transient service running @var{command} is
-spawned. The remaining arguments are as for
-@code{make-forkexec-constructor}."
+spawned. Only up to @var{max-connections} simultaneous connections are
+accepted; when that threshold is reached, new connections are immediately
+closed.
+
+The remaining arguments are as for @code{make-forkexec-constructor}."
(define child-service-name
(let ((counter 1))
(lambda ()
@@ -1329,6 +1339,44 @@ spawned. The remaining arguments are as for
(set! counter (+ 1 counter))
name)))
+ (define connection-count
+ ;; Number of active connections.
+ 0)
+
+ (define (handle-child-termination service status)
+ (set! connection-count (- connection-count 1))
+ (local-output (l10n "~a connection still in use after ~a termination."
+ "~a connections still in use after ~a termination."
+ connection-count)
+ connection-count (canonical-name service))
+ (default-service-termination-handler service status))
+
+ (define (spawn-child-service connection client-address)
+ (let* ((name (child-service-name))
+ (service (make <service>
+ #:provides (list name)
+ #:requires requirements
+ #:respawn? #f
+ #:transient? #t
+ #:start (make-inetd-forkexec-constructor
+ command connection
+ #:user user
+ #:group group
+ #:supplementary-groups
+ supplementary-groups
+ #:directory directory
+ #:file-creation-mask file-creation-mask
+ #:create-session? create-session?
+ #:environment-variables
+ (append (inetd-variables address
+ client-address)
+ environment-variables)
+ #:resource-limits resource-limits)
+ #:handle-termination handle-child-termination
+ #:stop (make-kill-destructor))))
+ (register-services service)
+ (start service)))
+
(lambda args
(let ((sock (non-blocking-port
(socket (sockaddr:fam address) socket-style 0))))
@@ -1343,35 +1391,23 @@ spawned. The remaining arguments are as for
(let loop ()
(match (accept sock)
((connection . client-address)
- (local-output
- (l10n "Accepted connection on ~a from ~:[~a~;~*local
process~].")
- (socket-address->string address)
- (= AF_UNIX (sockaddr:fam client-address))
- (socket-address->string client-address))
- (letrec* ((name (child-service-name))
- (service
- (make <service>
- #:provides (list name)
- #:requires requirements
- #:respawn? #f
- #:transient? #t
- #:start (make-inetd-forkexec-constructor
- command connection
- #:user user
- #:group group
- #:supplementary-groups
- supplementary-groups
- #:directory directory
- #:file-creation-mask file-creation-mask
- #:create-session? create-session?
- #:environment-variables
- (append (inetd-variables address
- client-address)
- environment-variables)
- #:resource-limits resource-limits)
- #:stop (make-kill-destructor))))
- (register-services service)
- (start service))))
+ (if (>= connection-count max-connections)
+ (begin
+ (local-output
+ (l10n "Maximum number of ~a clients reached; \
+rejecting connection from ~:[~a~;~*local process~].")
+ (socket-address->string address)
+ (= AF_UNIX (sockaddr:fam client-address))
+ (socket-address->string client-address))
+ (close-port connection))
+ (begin
+ (set! connection-count (+ 1 connection-count))
+ (local-output
+ (l10n "Accepted connection on ~a from ~:[~a~;~*local
process~].")
+ (socket-address->string address)
+ (= AF_UNIX (sockaddr:fam client-address))
+ (socket-address->string client-address))
+ (spawn-child-service connection client-address)))))
(loop))))
sock)))
diff --git a/tests/inetd.sh b/tests/inetd.sh
index c65a049..d3a808d 100644
--- a/tests/inetd.sh
+++ b/tests/inetd.sh
@@ -50,7 +50,8 @@ cat > "$conf" <<EOF
#:provides '(test-inetd-unix)
#:start (make-inetd-constructor %command
(make-socket-address AF_UNIX
- "$service_socket"))
+ "$service_socket")
+ #:max-connections 5)
#:stop (make-inetd-destructor)))
(start 'test-inetd)
@@ -112,6 +113,29 @@ $herd stop test-inetd-unix
! converse_with_echo_server \
"(make-socket-address AF_UNIX \"$service_socket\")"
+# Check the maximum connection limit.
+$herd start test-inetd-unix
+guile -c "
+ (use-modules (ice-9 rdelim) (ice-9 match))
+ (define address (make-socket-address AF_UNIX \"$service_socket\"))
+ (let loop ((i 10)
+ (sockets '()))
+ (if (zero? i)
+ ;; shepherd should close the extra sockets immediately.
+ (unless (equal? (append (make-list 5 the-eof-object)
+ (make-list 5 \"hello\"))
+ (pk 'read (map read-line sockets)))
+ (exit 1))
+ (let ((sock (socket AF_UNIX SOCK_STREAM 0)))
+ (connect sock address)
+ (loop (- i 1) (cons sock sockets)))))"
+
+converse_with_echo_server \
+ "(make-socket-address AF_UNIX \"$service_socket\")"
+
+$herd stop test-inetd-unix
+$herd status
+
# At this point, shepherd should have INITIAL_FD_COUNT - 1 file descriptors
# opened.
test $(file_descriptor_count) -lt $initial_fd_count
- [shepherd] 15/24: service: Allow 'running' value to be a thunk., (continued)
- [shepherd] 15/24: service: Allow 'running' value to be a thunk., Ludovic Courtès, 2022/03/28
- [shepherd] 20/24: service: Add #:handle-termination slot., Ludovic Courtès, 2022/03/28
- [shepherd] 22/24: service: 'make-inetd-constructor' lets the caller specify socket ownership., Ludovic Courtès, 2022/03/28
- [shepherd] 06/24: service: 'read-pid-file' no longer blocks., Ludovic Courtès, 2022/03/28
- [shepherd] 09/24: service: 'make-forkexec-constructor' spawns a logging fiber., Ludovic Courtès, 2022/03/28
- [shepherd] 16/24: service: Add systemd constructor and destructor., Ludovic Courtès, 2022/03/28
- [shepherd] 11/24: support: 'l10n' accepts plural forms., Ludovic Courtès, 2022/03/28
- [shepherd] 01/24: shepherd: Factorize out the main loop., Ludovic Courtès, 2022/03/28
- [shepherd] 04/24: build: Capture the source and object directories of Fibers., Ludovic Courtès, 2022/03/28
- [shepherd] 07/24: service: 'read-pid-file' uses (@ (guile) sleep) when it's not suspendable., Ludovic Courtès, 2022/03/28
- [shepherd] 21/24: service: Add #:max-connections to 'make-inetd-constructor'.,
Ludovic Courtès <=
- [shepherd] 23/24: shepherd: Do not change to the client directory when executing a command., Ludovic Courtès, 2022/03/28
- [shepherd] 24/24: shepherd: Gracefully handle failure to open the socket., Ludovic Courtès, 2022/03/28