Repository navigation
Listening socket: dockerd parity, Unix path only (drop fd:// and --listen-socket-mode, add single-instance lock) #45
Copy link
Copy link
Closed
Labels
Status: Break ChangeAdded to a PR or issue that would cause a breaking changeAdded to a PR or issue that would cause a breaking changeType: EnhancementAdded to issues and PRs when a change includes improvements or optimizations.Added to issues and PRs when a change includes improvements or optimizations.
Description
Is your feature request related to a problem?
The listening socket should be as simple to operate as docker.sock: always 0660, owned by a well-known group, with access granted or revoked by group membership alone. Today it isn't, in three ways.
- TCP can still reach the listener.
--listen-tcpwas removed in Listen on Unix socket only; remove TCP listener and bring TypeScript to parity #35, but--listen-socket=fd://3adopts whatever systemd passes, andListenStream=127.0.0.1:2375passes a TCP socket. Keeping TCP out depends on a separate guard in each language, and TypeScript fd://3 socket activation is broken: every valid socket is rejected at startup #44 shows one of them is wrong: TypeScript rejects every fd, valid ones included. fd://3 socket activation: not verified to be listening, and sd_listen_fds env contract unchecked in all three #29 lists further hardening thatfd://would need. --listen-socket-modeis a setting operators shouldn't have. Every mode except0660is either useless (connect(2)needs write permission) or dangerous (world-writable). dockerd has no such flag.- A second instance takes over a live socket. Measured on
main(15b8630) in all three languages: starting a second proxy on the same path unlinks the first proxy's live socket and binds its own. The first keeps running, but nothing can reach it any more. dockerd relies on its pidfile to prevent this; we have neither a pidfile nor any other check.
deploy/docker-compose.sock.yml also shows --listen-socket-group is redundant when the proxy runs as user: uid:gid, because bind(2) already gives the socket the process's group.
Describe the solution
Full design: spec/listener-design.md. In summary:
- Unix socket path only.
fd://is removed and rejected like any other non-path value. This fixes TypeScript fd://3 socket activation is broken: every valid socket is rejected at startup #44 and makes fd://3 socket activation: not verified to be listening, and sd_listen_fds env contract unchecked in all three #29 moot. - The mode is always
0660.--listen-socket-modeis removed. The existingumask(0177)→ bind → chown → chmod order stays. - Group selection works like dockerd's (
moby/daemon/listeners/listeners_linux.go), with a default group nameddocker-socket-policy:- flag not passed and the group exists → use it;
- flag not passed and the group is missing → warn and use the proxy's own group;
- an explicit group that doesn't exist → exit 2;
--listen-socket-group=""→ the proxy's own group, with no warning.
- Single-instance lock (Go and Rust). Each takes
flock(LOCK_EX|LOCK_NB)on<path>.lock(0600,O_NOFOLLOW, never unlinked) and holds it for as long as it runs. The kernel releases it on any exit,SIGKILLincluded. - "In use" check (all three). Before deleting an existing socket, the proxy
connect(2)s to it. If anything answers, it refuses with "in use" and exits 1, leaving the live socket alone. - New Quint module
spec/listener.qnt. It models three instances going through the socket setup one step at a time, interleaving and crashing at any point. It checks six invariants: no TCP, group set before mode, never world-writable, never deletes a non-socket, no live takeover, and group selection matching the table.quint testruns one test per design-table row, with same-named tests in Go, Rust and TS.make test-specis added to CI.
This is a breaking CLI change: --listen-socket-mode and fd://3 are removed.
Describe alternatives
- Harden
fd://instead (checkSO_ACCEPTCONN, validateLISTEN_PIDandLISTEN_FDS, fix the TypeScript guard): rejected. It keeps a way for TCP to reach the listener behind per-language guards, and one of those guards already shipped broken (TypeScript fd://3 socket activation is broken: every valid socket is rejected at startup #44). Removing the feature removes the whole class of bug. - Keep
--listen-socket-mode: rejected. No valid mode exists besides0660. - Drop
--listen-socket-grouptoo, always using the proxy's own group: rejected in favour of matching dockerd, so operators can use the familiargroupadd/usermod -aGsteps. - A pidfile for single-instance protection: rejected. Checking a PID doesn't work across PID namespaces (two containers sharing a socket volume), and a crash leaves a stale pidfile behind. An
flockneeds no cleanup. - The "in use" check alone, with no lock: rejected for Go and Rust. It still has a race between checking and deleting: two instances starting together can both see a stale socket and both delete it. The Quint model is required to show that race when the lock is off.
- TypeScript lock: Node has no
flock(checked on Node 22). We considered a native addon and a lock-freelink/renameprotocol. For now TypeScript keeps only the "in use" check, and the race is documented. What closing the gap needs is tracked in a follow-up issue.
Which implementation(s) would this affect?
- Go
- Rust
- TypeScript
- Quint specification
- All
Additional context
Supersedes #29 and fixes #44; both will be closed by the PR. Independent of #43.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Status: Break ChangeAdded to a PR or issue that would cause a breaking changeAdded to a PR or issue that would cause a breaking changeType: EnhancementAdded to issues and PRs when a change includes improvements or optimizations.Added to issues and PRs when a change includes improvements or optimizations.
Type
Fields
Priority
None yet
Activity
chore(ci): run Quint test-spec in ci-verify (#45)
docs: align design doc and README anchor with shipped behaviour (#45)