socket-activation#
Listening sockets inherited from the service manager.
A service manager can create a daemon's listening sockets itself, bind them, and pass them to the daemon already open. The socket exists before the daemon starts, so connections queue instead of being refused while the daemon launches or restarts; and the manager, not the daemon, holds whatever privilege binding the socket needs. This library recovers those sockets behind one cross-platform interface:
- on Linux, the systemd protocol (
sd_listen_fds): the manager setsLISTEN_PID,LISTEN_FDSand optionallyLISTEN_FDNAMES, and passes the sockets as consecutive file descriptors starting at 3. Implemented in pure OCaml -- nolibsystemddependency; - on macOS, the launchd API (
launch_activate_socket): sockets are declared under theSocketskey of the launchd job and retrieved by name. A small C stub calls the system API.
Descriptors come back as listening Unix sockets, each set close-on-exec so
the daemon's own children do 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#
A process that was not socket-activated -- run from a shell, say -- gets an empty list rather than an error, so the same code works activated or not:
# Socket_activation.fds "http"
- : Unix.file_descr list = []
Look sockets up by the name the manager gave them (FileDescriptorName= in a
systemd .socket unit, or a key under Sockets in a launchd job). This is the
portable call -- it 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 () returns every inherited socket positionally,
ignoring names -- the right call for the common systemd unit that passes a
single socket. It is Linux-only: launchd has no positional model, so on macOS
it raises Unix.Unix_error (ENOSYS, _, _).
By default both functions then unset the LISTEN_* variables, so a child this
process later forks cannot re-claim the descriptors. When looking several names
up, pass ~unset_env:false on all but the last call.
Eio#
The descriptors are raw Unix.file_descr listening sockets; wrap one for Eio
with Eio_unix.Net.import_socket_listening (as above). The library
itself depends only on unix, so it is equally usable from a plain
Unix/accept loop.
Security#
The LISTEN_PID check is the load-bearing one. systemd sets it to the pid it
execs, and this library hands back descriptors only when it equals
getpid (). Without the check, a process that forked after activation would
inherit both the descriptors and the environment and could claim sockets meant
for its parent. Absent or mismatched LISTEN_PID therefore yields the empty
list, never the sockets. This matches sd_listen_fds; the same model is used
by go-systemd and the Rust listenfd crate.
License#
ISC, see LICENSE.