Listening sockets inherited from systemd or launchd
0

Configure Feed

Select the types of activity you want to include in your feed.

OCaml 68.2%
C 15.9%
Dune 5.5%
Other 10.5%
8 1 0

Clone this repository

https://git.vm.fail/gazagnaire.org/ocaml-socket-activation https://git.vm.fail/did:plc:opfcoxqhthm7ylt27b7ujjpn
ssh://git@git.recoil.org:2222/gazagnaire.org/ocaml-socket-activation ssh://git@git.recoil.org:2222/did:plc:opfcoxqhthm7ylt27b7ujjpn

For self-hosted knots, clone URLs may differ based on your setup.


README.md

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 sets LISTEN_PID, LISTEN_FDS and optionally LISTEN_FDNAMES, and passes the sockets as consecutive file descriptors starting at 3. Implemented in pure OCaml -- no libsystemd dependency;
  • on macOS, the launchd API (launch_activate_socket): sockets are declared under the Sockets key 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.