socket-activation #
Listening sockets inherited from the service manager.
Under systemd or launchd, a daemon does not have to open its own listening sockets. The service manager can bind them and pass them in already open. I like this for two reasons. Clients who connect during a restart sit in the kernel's backlog and never see a refused connection, and the daemon can serve port 80 without ever running as root.
The two managers hand sockets over in different ways, and this library reads
both. systemd sets LISTEN_PID, LISTEN_FDS and sometimes LISTEN_FDNAMES,
then passes the sockets as file descriptors 3, 4, 5 and so on (the
sd_listen_fds protocol). That is plain OCaml here, with no libsystemd to
link. launchd instead keeps the sockets under the job's Sockets key, and a
short C stub asks for them by name with launch_activate_socket.
Either way the result is a list of listening Unix sockets. They are all
close-on-exec, so a program the daemon starts later will not inherit them.
Installation #
Install with opam:
$ opam install socket-activation
If opam cannot find the package, add the overlay repository first:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install socket-activation
Usage #
If the process was not socket-activated (you started it from a shell, say), the lookup returns an empty list. The same code therefore runs with or without a service manager:
# Socket_activation.fds "http"
- : Unix.file_descr list = []
fds finds sockets by the name the manager gave them. In a systemd .socket
unit that is FileDescriptorName=; in a launchd job it is a key under
Sockets. This call works on both platforms:
let activated_http_sockets ~sw =
match Socket_activation.fds "http" with
| [] -> [] (* not activated: bind as usual *)
| fds ->
List.map
(Eio_unix.Net.import_socket_listening ~sw ~close_unix:true)
fds
Socket_activation.all () ignores names and returns every inherited socket in
the order the manager passed them. Many systemd units pass just one socket, and
for those this is the simplest call. launchd has no notion of socket order,
so on macOS all raises Unix.Unix_error (ENOSYS, _, _).
Both functions clear the LISTEN_* variables once they have read them, so
that a child forked later cannot claim the same descriptors. The first call to
clear them makes later Linux lookups return []. If you look up several
names, pass ~unset_env:false to every call except the last.
Eio #
You get plain Unix.file_descr values. The example above hands each one to
Eio_unix.Net.import_socket_listening to use it from Eio. The
library itself depends only on unix, and a plain Unix.accept loop works
just as well.
Security #
On Linux the protocol's safety rests on LISTEN_PID. systemd sets it to the
pid of the process it execs, and this library returns descriptors only when
it equals getpid (). A process that forked after activation inherits the
descriptors and the environment both; without the check it could claim
sockets meant for its parent. When LISTEN_PID is missing or names another
process, the result is the empty list. sd_listen_fds behaves the same way,
as do go-systemd and the Rust listenfd crate.
License #
ISC, see LICENSE.