Don't listen on a socket whose bind failed
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:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user