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 <sienna@sunbeam.pt>
This commit is contained in:
2026-03-10 23:38:20 +00:00
parent 45f0751e1e
commit 70781679b5

View File

@@ -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<Self> {
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::<libc::c_int>() 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,