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:
@@ -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,8 +62,40 @@ 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,
|
||||
|
||||
Reference in New Issue
Block a user