Don't listen on a socket whose bind failed
ci / fork-checks (pull_request) Successful in 46s
ci / build (pull_request) Successful in 10m37s

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.
This commit is contained in:
2026-09-26 01:36:39 -07:00
parent b41dfa7a1d
commit b6f943a77c
+41
View File
@@ -421,6 +421,15 @@ impl Listeners {
impl TcpListener { impl TcpListener {
pub fn listen(self) -> Result<tokio::net::TcpListener, String> { pub fn listen(self) -> Result<tokio::net::TcpListener, String> {
// inbuxa: a socket whose bind failed is still unbound, and listen()
// on it makes the kernel pick a random port on every interface
if !self
.socket
.local_addr()
.is_ok_and(|bound| bound.port() != 0)
{
return Err(format!("Not listening on {}: it isn't bound", self.addr));
}
self.socket self.socket
.listen(self.backlog.unwrap_or(1024)) .listen(self.backlog.unwrap_or(1024))
.map_err(|err| format!("Failed to listen on {}: {}", self.addr, err)) .map_err(|err| format!("Failed to listen on {}: {}", self.addr, err))
@@ -485,3 +494,35 @@ impl ServerInstance {
} }
} }
} }
#[cfg(test)]
mod tests {
use crate::config::server::TcpListener;
use tokio::net::TcpSocket;
fn listener(socket: TcpSocket, addr: &str) -> TcpListener {
TcpListener {
socket,
addr: addr.parse().unwrap(),
backlog: None,
ttl: None,
nodelay: true,
}
}
#[tokio::test]
async fn an_unbound_socket_is_not_listened_on() {
// What a failed bind leaves behind: listening would pick a random port
let socket = TcpSocket::new_v4().unwrap();
let err = listener(socket, "0.0.0.0:25").listen().unwrap_err();
assert!(err.contains("isn't bound"), "{err}");
}
#[tokio::test]
async fn a_bound_socket_listens_even_on_port_zero() {
let socket = TcpSocket::new_v4().unwrap();
socket.bind("127.0.0.1:0".parse().unwrap()).unwrap();
let bound = listener(socket, "127.0.0.1:0").listen().unwrap();
assert_ne!(bound.local_addr().unwrap().port(), 0);
}
}