Don't listen on a socket whose bind failed #57

Merged
jcoffey-dev merged 1 commits from fix/unbound-listener into main 2026-09-26 08:48:04 +00:00
Owner

When a listener can't bind its address, the bind error was reported but the socket still went to listen(), and the kernel bound it to a random port on every interface. The server then logged "Network listener started" with the configured port.

It happens whenever a bind fails: a port below 1024 without root, a port already taken, or the legacy-protocols switch (LP-5) putting a listener back after privileges are dropped. Production binds as root, so its normal start isn't affected.

TcpListener::listen() now refuses a socket that isn't bound (local port 0), so the listener gets a network.listen-error and is skipped. A socket bound to port 0 on purpose still listens.

Checked: two unit tests in network::listen. A non-root run on demo data used to open five random ports, one for each privileged listener. Now it opens none, and logs e.g. Not listening on [::]:25: it isn't bound.

When a listener can't bind its address, the bind error was reported but the socket still went to `listen()`, and the kernel bound it to a **random port on every interface**. The server then logged "Network listener started" with the configured port. It happens whenever a bind fails: a port below 1024 without root, a port already taken, or the legacy-protocols switch (LP-5) putting a listener back after privileges are dropped. Production binds as root, so its normal start isn't affected. `TcpListener::listen()` now refuses a socket that isn't bound (local port 0), so the listener gets a `network.listen-error` and is skipped. A socket bound to port 0 on purpose still listens. **Checked:** two unit tests in `network::listen`. A non-root run on demo data used to open five random ports, one for each privileged listener. Now it opens none, and logs e.g. `Not listening on [::]:25: it isn't bound`.
jcoffey-dev added 1 commit 2026-09-26 08:36:57 +00:00
Don't listen on a socket whose bind failed
ci / fork-checks (pull_request) Successful in 46s
ci / build (pull_request) Successful in 10m37s
b6f943a77c
When a listener couldn't bind its address (a port below 1024 without
root, a port already in use, or the legacy-protocols switch putting a
listener back after privileges were dropped), the bind error was
reported but the socket was still passed to listen(). The kernel then
bound it itself, to a random port on every interface, and the server
logged the listener as started on the port it was configured with.

listen() now refuses a socket that isn't bound, so the listener is
reported with a listen error and skipped, and nothing opens anywhere
unexpected.
jcoffey-dev merged commit 0fdd11aa27 into main 2026-09-26 08:48:04 +00:00
jcoffey-dev deleted branch fix/unbound-listener 2026-09-26 08:48:04 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: inbuxa/inbuxa-server#57