1use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type};
12use std::net::{SocketAddr, TcpListener, UdpSocket};
13use std::time::Duration;
14
15const KEEPALIVE_IDLE: Duration = Duration::from_secs(30);
23const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(10);
24
25pub fn udp(addr: SocketAddr) -> std::io::Result<UdpSocket> {
27 let domain = if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
28 let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
29 make_dual_stack(&socket, addr);
30 socket.bind(&addr.into())?;
31 Ok(socket.into())
32}
33
34pub fn tcp(addr: SocketAddr) -> std::io::Result<TcpListener> {
39 let domain = if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
40 let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
41 make_dual_stack(&socket, addr);
42 #[cfg(not(windows))]
45 socket.set_reuse_address(true)?;
46 let keepalive = TcpKeepalive::new()
52 .with_time(KEEPALIVE_IDLE)
53 .with_interval(KEEPALIVE_INTERVAL);
54 if let Err(err) = socket.set_tcp_keepalive(&keepalive) {
55 tracing::warn!(%err, "failed to enable TCP keepalive; dead peers may linger");
56 }
57 socket.bind(&addr.into())?;
58 socket.listen(1024)?;
59 let listener: TcpListener = socket.into();
60 listener.set_nonblocking(true)?;
61 Ok(listener)
62}
63
64fn make_dual_stack(socket: &Socket, addr: SocketAddr) {
68 if addr.is_ipv6()
69 && let Err(err) = socket.set_only_v6(false)
70 {
71 tracing::warn!(%err, "failed to enable dual-stack IPv6 socket; IPv4 clients may be unreachable");
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn skip_if_no_ipv6(err: &std::io::Error) -> bool {
85 const NO_IPV6_ERRNOS: &[i32] = &[97, 99, 93, 10047, 10049, 10043];
88 let no_ipv6 = matches!(
89 err.kind(),
90 std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::Unsupported
91 ) || err.raw_os_error().is_some_and(|code| NO_IPV6_ERRNOS.contains(&code));
92 if no_ipv6 {
93 eprintln!("skipping: host has no IPv6 support ({err})");
94 }
95 no_ipv6
96 }
97
98 #[test]
99 fn udp_ipv6_is_dual_stack() {
100 let socket = match udp("[::]:0".parse().unwrap()) {
103 Ok(socket) => socket,
104 Err(err) if skip_if_no_ipv6(&err) => return,
105 Err(err) => panic!("failed to bind IPv6 UDP socket: {err}"),
106 };
107 let socket = Socket::from(socket);
108 assert!(!socket.only_v6().unwrap(), "IPv6 socket should be dual-stack");
109 }
110
111 #[test]
112 fn udp_ipv4_still_binds() {
113 let socket = udp("127.0.0.1:0".parse().unwrap()).unwrap();
114 assert!(socket.local_addr().unwrap().is_ipv4());
115 }
116
117 #[test]
118 fn tcp_ipv6_is_dual_stack() {
119 let listener = match tcp("[::]:0".parse().unwrap()) {
120 Ok(listener) => listener,
121 Err(err) if skip_if_no_ipv6(&err) => return,
122 Err(err) => panic!("failed to bind IPv6 TCP listener: {err}"),
123 };
124 let socket = Socket::from(listener);
125 assert!(!socket.only_v6().unwrap(), "IPv6 listener should be dual-stack");
126 }
127}