Re: rcd(8) - new service manager daemon
Baptiste Daroussin <[email protected]> Tue, 16 Jun 2026 10:23:54 +0200
| Newsgroups | gmane.os.freebsd.devel.hackers |
|---|---|
| Message-ID | <[email protected]> |
On Mon 15 Jun 23:06, Philipp wrote: > [2026-06-15 20:10] Baptiste Daroussin <[email protected]> > > On Mon 15 Jun 19:31, Philipp wrote: > > > [2026-06-15 08:47] Baptiste Daroussin <[email protected]> > > > > On Sun 14 Jun 16:15, Alan Somers wrote: > > > > > On Sun, Jun 14, 2026 at 1:26 PM Baptiste Daroussin <[email protected]> wrote: > > > > > > [...] > > > > > > > > > > > > Key features: > > > > > > > > > > > > [...] > > > > > > - Socket activation (pre-bound sockets passed via fd inheritance) > > > > > > > > > > [...] > > > > > > > > > > This sounds turboawesome. Parallel boot, pdfork, procctl, and UCL, all sound > > > > > very exciting to me. I can't wait to beta-test. And > > > > > backwards-compatibility, too, is super good. IIRC, the lack of > > > > > backwards-compatibility is why the NextBSD experiment failed. Since > > > > > you're the expert, could you tell us how else this rcd differs from > > > > > NextBSD/launchd? > > > > > > > > TL;DR rcd is less ambitious and fully design for FreeBSD (and probably FreeBSD > > > > only). > > > > > > > > launchd replaces cron(8) and inetd(8): it handles periodic jobs (StartInterval, > > > > StartCalendarInterval) and socket-activated services (inetdCompatibility, > > > > Sockets). rcd focuses solely on service management and does not attempt to > > > > replace cron or inetd. In the future, tighter integration with cron and inetd > > > > (similar to Solaris SMF) could be considered, but this is not planned today. > > > > > > I'm a bit confused about this statement. Because socket activation > > > sounds for me like a replacement for inetd. Of course not a drop in > > > replacement and it only works when the service supports it. > > > > > > It is already possible to either listen direct on a socket or use > > > inetd. What is the benefit of socket activation? In general wouldn't > > > it be better to add socket activation to inetd or an inetd replacement? > > > > > > > Adding socket activation to inetd wouldn't make sense (imho). they're opposite models. > > inetd multiplexes connections to lightweight programs; socket activation defers > > startup of a heavyweight daemon while guaranteeing port availability. They > > complement each other. On Mon 15 Jun 21:06, Philipp wrote: > [2026-06-15 20:10] Baptiste Daroussin <[email protected]> > > Adding socket activation to inetd wouldn't make sense (imho). they're opposite models. > > inetd multiplexes connections to lightweight programs; socket activation defers > > startup of a heavyweight daemon while guaranteeing port availability. They > > complement each other. > Thank you for your questions: they helped me realise that I forgot to implement the READY_SOCKET readiness notification. This is now fixed. > I don't understand this argument. inetd listen on the socket and on > a connection it passes it to stdin/stdout of fresh programm. For me it > sounds like it would be a good match to add a way to pass the connection > via fd inheritance. Yes socket activation and (current) inetd have > a different interface to the service but do more or less the same: > Wait for connections and pass them to a service. So why having socket > activation in rcd but not a classic inetd interface? Or the other way > around: Why having the classic inetd interface in inetd but not socket > activation? The key difference is who owns the accept() loop. inetd accepts the connection itself, then hands the connected fd to a freshly spawned process via stdin/stdout. The spawned process never calls accept(), it just reads/writes on fd 0/1. This works well for services that are stateless per-connection and cheap to start (e.g., echo, time, finger, tftp). Socket activation (as in rcd, systemd, launchd) works at a different layer: the service manager binds and listens, but the service itself calls accept() on the inherited fd. The service owns the accept loop. This is the model used by real-world network daemons like sshd, nginx, httpd, and postfix, they manage their own connection lifecycle, including keep-alive, TLS renegotiation, and request multiplexing. These two models are not interchangeable. You cannot take sshd and run it under inetd's model, sshd expects to call accept() itself, manage its own process pool, and handle connection affinity. Conversely, you cannot take a simple inetd-style service and have it call accept(), it doesn't know how. So the question "why not add socket activation to inetd" is like asking "why not add fork+exec to a library that already does dlopen()", they solve different problems and the interface contract is fundamentally different. > You mentioned that socket activation is for "heavyweight daemon while > guaranteeing port availability". Can you explain this a bit more or > give a concrete example for a service? Concrete example: sshd. During boot, sshd has a non-trivial startup time: it parses ssh_config, generates/loads host keys, initialises its crypto subsystem, etc. With a traditional rc.d boot sequence, the boot script must: 1. Wait for network to be configured 2. Start sshd 3. Wait for sshd to bind port 22 4. Only then can the system declare the service "up" With rcd socket activation: 1. rcd binds port 22 very early (as soon as the network stack is up) 2. sshd can start later, potentially in parallel with other services 3. The port is guaranteed to be owned by rcd, no other process can steal it 4. If sshd crashes, rcd keeps the socket open and can restart sshd without losing the port binding The "port availability" guarantee means: from the moment rcd binds the socket, port 22 is always owned by the intended service. No race condition, no window where the port is unbound, no accidental takeover by another process. > For high availibility I would prefere that the service itself manage the > socket and connection, because then it might move connections to in which > are partial handled to another instance or even (with firewall support) > to another server. That's a valid concern for active connection migration, but it's a different problem domain. Socket activation doesn't prevent the service from also managing its own sockets, many services (nginx, postfix) support both modes. The socket activation path is for the common case: normal boot, normal restart, normal operation. For true zero-downtime upgrades with connection migration, you'd use SO_REUSEPORT (which rcd already sets) and have the new instance bind alongside the old one before the old one exits. This works regardless of who did the initial bind. > For servers which have a long startup time socket activation would only > add a delay during startup for connections. When a service would like to > have this, it would be quite simple to implement: bind early and run > a thread accepting all connections. Two points here: 1. The delay is absorbed by the kernel's listen backlog. The kernel buffers incoming connections (up to the backlog limit, typically 128 or more). By the time the service is ready to accept(), the connections are already waiting in the queue. For most services, startup completes before the backlog fills up. 2. "Just bind early" is exactly what socket activation does, but centralised in the service manager rather than duplicated in every service. The benefit of centralisation is: - Services don't need to implement early-binding logic - The socket survives service restarts (rcd keeps it open) - Socket options (TCP_DEFER_ACCEPT, TCP_FASTOPEN, SO_REUSEPORT) can be configured in one place - The service manager can sequence socket binding with network readiness (e.g., wait for a particular interface to be configured) - Permissions for unix sockets can be set before the service runs > But the delay would (imho) lead to bad user expirience during startup > and maybe to timeouts. An alternativ would be to accept early but send > an application error (i.e. http 503). But with this aproach the socket > activation doesn't help, because the service would need to accept > during the startup. This is a valid concern for services with very long startup times (e.g., Java application servers). For those, you'd typically use READY_SOCKET mode combined with a health check, rcd waits for the service to signal readiness before registering the socket in kqueue. The socket is bound but not "activated" (no accept happens) until the service is ready. This is the model used by systemd's Type=notify + socket activation. > Or go the simple way and bind after the startup. Then the port is > closed a bit longer. But when port availibilty is a problem then you > better look at some sort of high availibity, because a reboot also > cause the port to be closed. A reboot is a planned event. A service crash is not. The difference is: - Without socket activation: service crashes -> port is released -> another process could bind it -> service restarts and fails to bind -> service is dead. This is a real problem in practice. - With socket activation: service crashes -> rcd still holds the port -> rcd restarts the service -> service inherits the fd -> no port contention. The port was never released. This is not about high availability across machines; it's about robustness on a single machine during unplanned restarts. > Some other feature might come in mind is binding to priviled ports, but > there is already mac_portacl(4) which is imho the better solution for > this problem. mac_portacl(4) is a MAC policy that allows non-root processes to bind privileged ports. It's a useful tool, but it solves a different problem: "how do I let process X bind port Y without being root?" Socket activation solves: "how do I ensure port Y is bound before any service needs it, and stays bound across service restarts?" They can be used together, rcd can bind privileged ports (running as root) and pass them to services that drop privileges. This is actually cleaner than mac_portacl because the service never needs to bind a privileged port at all, it just inherits an already-bound fd. > I just want to understand what the gain of this feature is and in which > situation it would be a option to consider. Fair question. Here's a summary of when socket activation is useful: 1. Parallel boot: Services can start in any order; the port is claimed early. No need to sequence sshd after network, after syslog, after everything else. 2. On-demand startup: A service that's rarely used (e.g., a management interface) can be started only when a connection arrives. The port is always listening, but the service process only runs when needed. I'll grant that this aspect does resemble inetd's model, though the interface contract is different (the service calls accept(), not reads from stdin). 3. Crash resilience: The socket survives service restarts. No port contention, no race window. 4. Centralised socket configuration: Socket options, permissions, and addresses are configured in one place (the unit file), not scattered across service code and rc scripts. 5. Privilege separation: rcd (running as root) binds the socket; the service (running as an unprivileged user) inherits it. No need for MAC policies like mac_portacl(4). These are all complementary to inetd, not competing with it. inetd remains the right tool for lightweight, per-connection services. rcd's socket activation is for heavyweight daemons that manage their own connections. Best regards, Bapt