My own corner of monopam
README.md

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.