From 26201a4fe145e429d306c056365830f46bfa5f00 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 24 Sep 2026 14:05:33 +0000 Subject: [PATCH] Move the DNS socket helpers into the ipv6tun DNS module Binding the responder's dual-stack socket, enabling IPV6_RECVPKTINFO on it and resolving the mesh interface index are host-side socket setup for the .fips responder. They lived on Node as associated functions that touch no node state. Move them next to the responder they serve, in ipv6tun::dns. The DNS child start now calls ipv6tun::dns::bind_dns_socket directly, and Node::mesh_ifindex keeps its reading of the live TUN name and resolves it through the moved lookup. The code is unchanged, including the cfg(unix) gating of the PKTINFO setsockopt and the non-unix ifindex fallback. None of the moved functions log, so no tracing target changes. --- src/ipv6tun/dns.rs | 77 +++++++++++++++++++++++++++++++++++++- src/node/lifecycle/mod.rs | 79 ++------------------------------------- 2 files changed, 79 insertions(+), 77 deletions(-) diff --git a/src/ipv6tun/dns.rs b/src/ipv6tun/dns.rs index 2fca5e70..f59600da 100644 --- a/src/ipv6tun/dns.rs +++ b/src/ipv6tun/dns.rs @@ -164,7 +164,7 @@ fn is_mesh_interface_query(arrival_ifindex: Option, mesh_ifindex: Option Result { + use socket2::{Domain, Protocol, Socket, Type}; + let domain = if addr.is_ipv4() { + Domain::IPV4 + } else { + Domain::IPV6 + }; + let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?; + if addr.is_ipv6() { + sock.set_only_v6(false)?; + #[cfg(unix)] + set_recv_pktinfo_v6(&sock)?; + } + sock.set_nonblocking(true)?; + sock.bind(&addr.into())?; + tokio::net::UdpSocket::from_std(sock.into()) +} + +/// Enable `IPV6_RECVPKTINFO` on an IPv6 UDP socket. +/// +/// After this setsockopt, each `recvmsg()` call on the socket receives +/// an `IPV6_PKTINFO` control message containing the arrival interface +/// index, which the DNS responder uses for its mesh-interface filter. +#[cfg(unix)] +fn set_recv_pktinfo_v6(sock: &socket2::Socket) -> Result<(), std::io::Error> { + use std::os::fd::AsRawFd; + let enable: libc::c_int = 1; + let ret = unsafe { + libc::setsockopt( + sock.as_raw_fd(), + libc::IPPROTO_IPV6, + libc::IPV6_RECVPKTINFO, + &enable as *const _ as *const libc::c_void, + std::mem::size_of::() as libc::socklen_t, + ) + }; + if ret < 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) +} + +/// Resolve an interface index by name. +/// +/// Returns `None` if the interface does not exist. +pub(crate) fn lookup_mesh_ifindex(name: &str) -> Option { + #[cfg(unix)] + { + let c_name = std::ffi::CString::new(name).ok()?; + let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) }; + if idx == 0 { None } else { Some(idx) } + } + #[cfg(not(unix))] + { + let _ = name; + None + } +} + /// Receive a UDP datagram with arrival-interface info via `IPV6_PKTINFO`. /// /// Returns `(len, src, arrival_ifindex)`. The ifindex is `Some` when the @@ -819,7 +892,7 @@ mod tests { } /// Build a socket bound to `[::1]:0` with `IPV6_RECVPKTINFO` enabled, - /// mirroring the setup done in `Node::bind_dns_socket`. + /// mirroring the setup done in `bind_dns_socket`. #[cfg(unix)] fn bind_loopback_v6_with_pktinfo() -> tokio::net::UdpSocket { use socket2::{Domain, Protocol, Socket, Type}; diff --git a/src/node/lifecycle/mod.rs b/src/node/lifecycle/mod.rs index 044b0574..b8d20ec9 100644 --- a/src/node/lifecycle/mod.rs +++ b/src/node/lifecycle/mod.rs @@ -1864,7 +1864,7 @@ impl Node { match addr_str.parse::() { Ok(ip) => { let bind = std::net::SocketAddr::new(ip, self.config().dns.port()); - match Self::bind_dns_socket(bind) { + match crate::ipv6tun::dns::bind_dns_socket(bind) { Ok(socket) => { // Read the bound address back off the socket // rather than reusing `bind`: a port-0 config @@ -2072,62 +2072,6 @@ impl Node { Ok(()) } - /// Bind a UDP socket for the DNS responder. - /// - /// For IPv6 binds (including `::`), sets `IPV6_V6ONLY=0` so the socket - /// also accepts IPv4-mapped addresses. This guarantees dual-stack - /// delivery regardless of `net.ipv6.bindv6only` sysctl on the host — - /// v4 clients on 127.0.0.1 and v6 clients on the fips0 address both - /// land on the same socket. - /// - /// Also enables `IPV6_RECVPKTINFO` on IPv6 sockets so the responder - /// can learn the arrival interface per packet. The responder uses that - /// to drop queries arriving on the mesh TUN, closing the hosts-file - /// probing side-channel created by the `::` bind. - fn bind_dns_socket( - addr: std::net::SocketAddr, - ) -> Result { - use socket2::{Domain, Protocol, Socket, Type}; - let domain = if addr.is_ipv4() { - Domain::IPV4 - } else { - Domain::IPV6 - }; - let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?; - if addr.is_ipv6() { - sock.set_only_v6(false)?; - #[cfg(unix)] - Self::set_recv_pktinfo_v6(&sock)?; - } - sock.set_nonblocking(true)?; - sock.bind(&addr.into())?; - tokio::net::UdpSocket::from_std(sock.into()) - } - - /// Enable `IPV6_RECVPKTINFO` on an IPv6 UDP socket. - /// - /// After this setsockopt, each `recvmsg()` call on the socket receives - /// an `IPV6_PKTINFO` control message containing the arrival interface - /// index, which the DNS responder uses for its mesh-interface filter. - #[cfg(unix)] - fn set_recv_pktinfo_v6(sock: &socket2::Socket) -> Result<(), std::io::Error> { - use std::os::fd::AsRawFd; - let enable: libc::c_int = 1; - let ret = unsafe { - libc::setsockopt( - sock.as_raw_fd(), - libc::IPPROTO_IPV6, - libc::IPV6_RECVPKTINFO, - &enable as *const _ as *const libc::c_void, - std::mem::size_of::() as libc::socklen_t, - ) - }; - if ret < 0 { - return Err(std::io::Error::last_os_error()); - } - Ok(()) - } - /// Resolve the index of the mesh TUN device this node actually created. /// /// Reads the device name recorded when the TUN was brought up, which is @@ -2137,24 +2081,9 @@ impl Node { /// An app-owned TUN also leaves the name unset, so the filter stays off /// there even though a mesh interface exists. pub(crate) fn mesh_ifindex(&self) -> Option { - self.tun_name.as_deref().and_then(Self::lookup_mesh_ifindex) - } - - /// Resolve an interface index by name. - /// - /// Returns `None` if the interface does not exist. - fn lookup_mesh_ifindex(name: &str) -> Option { - #[cfg(unix)] - { - let c_name = std::ffi::CString::new(name).ok()?; - let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) }; - if idx == 0 { None } else { Some(idx) } - } - #[cfg(not(unix))] - { - let _ = name; - None - } + self.tun_name + .as_deref() + .and_then(crate::ipv6tun::dns::lookup_mesh_ifindex) } /// Stop the node.