//! Best-effort DSCP marking of the endpoint's UDP sockets. //! //! Background transfer traffic is marked CS1 (low priority) so routers //! that honor DSCP deprioritize it. iroh does not expose its socket fds, //! so this walks `/proc/self/fd` for the UDP sockets bound to the //! endpoint's ports — Linux-only by construction, like the daemon. //! Failures are logged at debug and ignored: some environments strip or //! forbid DSCP (may require CAP_NET_ADMIN), and that's fine. use std::net::SocketAddr; use std::os::fd::RawFd; use tracing::debug; /// DSCP CS1 (001000) in the TOS byte. const TOS_CS1: u32 = 0x20; extern "C" { fn setsockopt(fd: i32, level: i32, name: i32, value: *const u32, len: u32) -> i32; } const IPPROTO_IP: i32 = 0; const IP_TOS: i32 = 1; const IPPROTO_IPV6: i32 = 41; const IPV6_TCLASS: i32 = 67; /// Mark every UDP socket bound to one of `addrs` with DSCP CS1. pub fn mark_endpoint_sockets(addrs: &[SocketAddr]) { let ports: Vec = addrs.iter().map(|a| a.port()).collect(); let inodes = udp_socket_inodes(&ports); if inodes.is_empty() { debug!("dscp: no matching udp sockets found"); return; } let mut marked = 0; for fd in socket_fds(&inodes) { // Set both levels; one of them will apply depending on family. let v4 = unsafe { setsockopt(fd, IPPROTO_IP, IP_TOS, &TOS_CS1, 4) }; let v6 = unsafe { setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &TOS_CS1, 4) }; if v4 == 0 || v6 == 0 { marked += 1; } } debug!(marked, "dscp: marked endpoint sockets CS1"); } /// Inodes of UDP sockets locally bound to one of `ports`, from /// /proc/net/udp{,6}. Format: whitespace columns, local_address is /// `HEXIP:HEXPORT` in column 1, inode in column 9. fn udp_socket_inodes(ports: &[u16]) -> Vec { let mut inodes = Vec::new(); for table in ["/proc/net/udp", "/proc/net/udp6"] { let Ok(text) = std::fs::read_to_string(table) else { continue; }; for line in text.lines().skip(1) { let fields: Vec<&str> = line.split_whitespace().collect(); let (Some(local), Some(inode)) = (fields.get(1), fields.get(9)) else { continue; }; let Some((_, port_hex)) = local.rsplit_once(':') else { continue; }; let Ok(port) = u16::from_str_radix(port_hex, 16) else { continue; }; if ports.contains(&port) { if let Ok(inode) = inode.parse() { inodes.push(inode); } } } } inodes } /// Our process's fds that are sockets with one of the given inodes. fn socket_fds(inodes: &[u64]) -> Vec { let mut fds = Vec::new(); let Ok(entries) = std::fs::read_dir("/proc/self/fd") else { return fds; }; for entry in entries.flatten() { let Ok(target) = std::fs::read_link(entry.path()) else { continue; }; let target = target.to_string_lossy(); let Some(inode) = target .strip_prefix("socket:[") .and_then(|s| s.strip_suffix(']')) .and_then(|s| s.parse::().ok()) else { continue; }; if inodes.contains(&inode) { if let Ok(fd) = entry.file_name().to_string_lossy().parse::() { fds.push(fd); } } } fds }