use socket2::{Domain, Protocol, Socket, Type};
use std::net::{SocketAddr, TcpListener, UdpSocket};
pub fn udp(addr: SocketAddr) -> std::io::Result<UdpSocket> {
let domain = if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
make_dual_stack(&socket, addr);
socket.bind(&addr.into())?;
Ok(socket.into())
}
pub fn tcp(addr: SocketAddr) -> std::io::Result<TcpListener> {
let domain = if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
make_dual_stack(&socket, addr);
#[cfg(not(windows))]
socket.set_reuse_address(true)?;
socket.bind(&addr.into())?;
socket.listen(1024)?;
let listener: TcpListener = socket.into();
listener.set_nonblocking(true)?;
Ok(listener)
}
fn make_dual_stack(socket: &Socket, addr: SocketAddr) {
if addr.is_ipv6()
&& let Err(err) = socket.set_only_v6(false)
{
tracing::warn!(%err, "failed to enable dual-stack IPv6 socket; IPv4 clients may be unreachable");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn udp_ipv6_is_dual_stack() {
let socket = udp("[::]:0".parse().unwrap()).unwrap();
let socket = Socket::from(socket);
assert!(!socket.only_v6().unwrap(), "IPv6 socket should be dual-stack");
}
#[test]
fn udp_ipv4_still_binds() {
let socket = udp("127.0.0.1:0".parse().unwrap()).unwrap();
assert!(socket.local_addr().unwrap().is_ipv4());
}
#[test]
fn tcp_ipv6_is_dual_stack() {
let listener = tcp("[::]:0".parse().unwrap()).unwrap();
let socket = Socket::from(listener);
assert!(!socket.only_v6().unwrap(), "IPv6 listener should be dual-stack");
}
}