Skip to main content

seyd_nat/
socket.rs

1// Copyright 2026 Anton Gravestam
2// SPDX-License-Identifier: Apache-2.0
3//! Binding the UDP sockets the QUIC server will own.
4
5use socket2::{Domain, Protocol, Socket, Type};
6use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
7
8/// Bind the UDP sockets QUIC will use: IPv4 on `0.0.0.0:port` and, when
9/// `want_ipv6`, IPv6 on `[::]:port` with `IPV6_V6ONLY` so the two do not
10/// collide. Both are non-blocking and `SO_REUSEADDR`.
11///
12/// These are bound here, up front, because STUN has to run on the very socket
13/// that later receives QUIC. Binding a temporary socket and letting the QUIC
14/// stack rebind the port makes the advertised reflexive address a guess — on
15/// port-preserving home routers it usually held, on strict ones it silently
16/// did not. Same socket, never rebound, no guess.
17///
18/// IPv6 failure is not fatal: the agent continues IPv4-only.
19pub fn bind_sockets(port: u16, want_ipv6: bool) -> std::io::Result<Vec<UdpSocket>> {
20    let mut socks = Vec::with_capacity(2);
21
22    let s4 = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
23    s4.set_reuse_address(true)?;
24    s4.bind(&SocketAddr::from((Ipv4Addr::UNSPECIFIED, port)).into())?;
25    s4.set_nonblocking(true)?;
26    socks.push(UdpSocket::from(s4));
27
28    if want_ipv6 {
29        match bind_v6(port) {
30            Ok(s6) => socks.push(s6),
31            Err(e) => tracing::info!("IPv6 listener unavailable ({e}) — continuing IPv4-only"),
32        }
33    }
34    Ok(socks)
35}
36
37fn bind_v6(port: u16) -> std::io::Result<UdpSocket> {
38    let s6 = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
39    s6.set_reuse_address(true)?;
40    s6.set_only_v6(true)?;
41    s6.bind(&SocketAddr::from((Ipv6Addr::UNSPECIFIED, port)).into())?;
42    s6.set_nonblocking(true)?;
43    Ok(UdpSocket::from(s6))
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn binds_both_families_on_an_ephemeral_port() {
52        let socks = bind_sockets(0, true).unwrap();
53        assert!(!socks.is_empty());
54        assert!(socks[0].local_addr().unwrap().is_ipv4());
55        if socks.len() == 2 {
56            assert!(socks[1].local_addr().unwrap().is_ipv6());
57        }
58    }
59}