From 70781679b56b813b126fb02ecc92d4da4ef878c6 Mon Sep 17 00:00:00 2001 From: Sienna Meridian Satterwhite Date: Tue, 10 Mar 2026 23:38:20 +0000 Subject: [PATCH] fix(dual_stack): set IPV6_V6ONLY on IPv6 socket to prevent EADDRINUSE On Linux with net.ipv6.bindv6only=0 (default), binding [::]:port claims both IPv4 and IPv6, causing the subsequent 0.0.0.0:port bind to fail. Set IPV6_V6ONLY=1 so each socket only handles its own address family, fixing SSH TCP proxy bind failure in containers. Signed-off-by: Sienna Meridian Satterwhite --- src/dual_stack.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/dual_stack.rs b/src/dual_stack.rs index dc4f61d..d412fdc 100644 --- a/src/dual_stack.rs +++ b/src/dual_stack.rs @@ -10,6 +10,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::task::{Context, Poll}; use tokio::net::{TcpListener, TcpStream}; +use tokio::net::TcpSocket; pin_project_lite::pin_project! { /// Future returned by [`DualStackTcpListener::accept`]. @@ -61,9 +62,41 @@ impl DualStackTcpListener { /// # Returns /// A `Result` containing the dual-stack listener if successful. pub async fn bind(ipv6_addr: &str, ipv4_addr: &str) -> Result { - let ip6 = TcpListener::bind(ipv6_addr).await?; - let ip4 = TcpListener::bind(ipv4_addr).await?; - + // Bind IPv6 with IPV6_V6ONLY=1 so it doesn't grab IPv4 too. + // Without this, [::]:port claims both stacks on Linux (default + // net.ipv6.bindv6only=0), causing the subsequent IPv4 bind to + // fail with EADDRINUSE. + let v6_sock = TcpSocket::new_v6()?; + v6_sock.set_reuseaddr(true)?; + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + let fd = v6_sock.as_raw_fd(); + let yes: libc::c_int = 1; + unsafe { + libc::setsockopt( + fd, + libc::IPPROTO_IPV6, + libc::IPV6_V6ONLY, + &yes as *const _ as *const libc::c_void, + std::mem::size_of::() as libc::socklen_t, + ); + } + } + let v6_addr: SocketAddr = ipv6_addr.parse().map_err(|e| { + Error::new(ErrorKind::InvalidInput, format!("bad v6 addr: {e}")) + })?; + v6_sock.bind(v6_addr)?; + let ip6 = v6_sock.listen(1024)?; + + let v4_sock = TcpSocket::new_v4()?; + v4_sock.set_reuseaddr(true)?; + let v4_addr: SocketAddr = ipv4_addr.parse().map_err(|e| { + Error::new(ErrorKind::InvalidInput, format!("bad v4 addr: {e}")) + })?; + v4_sock.bind(v4_addr)?; + let ip4 = v4_sock.listen(1024)?; + Ok(Self { ip6, ip4,