1use socket2::{Domain, Protocol, Socket, Type};
12use std::net::{SocketAddr, TcpListener, UdpSocket};
13
14pub fn udp(addr: SocketAddr) -> std::io::Result<UdpSocket> {
16 let domain = if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
17 let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
18 make_dual_stack(&socket, addr);
19 socket.bind(&addr.into())?;
20 Ok(socket.into())
21}
22
23pub fn tcp(addr: SocketAddr) -> std::io::Result<TcpListener> {
28 let domain = if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
29 let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
30 make_dual_stack(&socket, addr);
31 #[cfg(not(windows))]
34 socket.set_reuse_address(true)?;
35 socket.bind(&addr.into())?;
36 socket.listen(1024)?;
37 let listener: TcpListener = socket.into();
38 listener.set_nonblocking(true)?;
39 Ok(listener)
40}
41
42fn make_dual_stack(socket: &Socket, addr: SocketAddr) {
46 if addr.is_ipv6()
47 && let Err(err) = socket.set_only_v6(false)
48 {
49 tracing::warn!(%err, "failed to enable dual-stack IPv6 socket; IPv4 clients may be unreachable");
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 fn skip_if_no_ipv6(err: &std::io::Error) -> bool {
63 const NO_IPV6_ERRNOS: &[i32] = &[97, 99, 93, 10047, 10049, 10043];
66 let no_ipv6 = matches!(
67 err.kind(),
68 std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::Unsupported
69 ) || err.raw_os_error().is_some_and(|code| NO_IPV6_ERRNOS.contains(&code));
70 if no_ipv6 {
71 eprintln!("skipping: host has no IPv6 support ({err})");
72 }
73 no_ipv6
74 }
75
76 #[test]
77 fn udp_ipv6_is_dual_stack() {
78 let socket = match udp("[::]:0".parse().unwrap()) {
81 Ok(socket) => socket,
82 Err(err) if skip_if_no_ipv6(&err) => return,
83 Err(err) => panic!("failed to bind IPv6 UDP socket: {err}"),
84 };
85 let socket = Socket::from(socket);
86 assert!(!socket.only_v6().unwrap(), "IPv6 socket should be dual-stack");
87 }
88
89 #[test]
90 fn udp_ipv4_still_binds() {
91 let socket = udp("127.0.0.1:0".parse().unwrap()).unwrap();
92 assert!(socket.local_addr().unwrap().is_ipv4());
93 }
94
95 #[test]
96 fn tcp_ipv6_is_dual_stack() {
97 let listener = match tcp("[::]:0".parse().unwrap()) {
98 Ok(listener) => listener,
99 Err(err) if skip_if_no_ipv6(&err) => return,
100 Err(err) => panic!("failed to bind IPv6 TCP listener: {err}"),
101 };
102 let socket = Socket::from(listener);
103 assert!(!socket.only_v6().unwrap(), "IPv6 listener should be dual-stack");
104 }
105}