diff --git a/neqo-transport/Cargo.toml b/neqo-transport/Cargo.toml index afb410f13a..8a245b9ff7 100644 --- a/neqo-transport/Cargo.toml +++ b/neqo-transport/Cargo.toml @@ -19,22 +19,14 @@ workspace = true # Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 enum-map = { version = "2.7", default-features = false } indexmap = { version = "2.2", default-features = false } # See https://github.com/mozilla/neqo/issues/1858 -libc = { version = "0.2", default-features = false } log = { workspace = true } +mtu = { version = "0.1", default-features = false } neqo-common = { path = "../neqo-common" } neqo-crypto = { path = "../neqo-crypto" } qlog = { workspace = true } smallvec = { version = "1.11", default-features = false } static_assertions = { version = "1.1", default-features = false } -[target."cfg(windows)".dependencies] -# Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 -windows = { version = "0.58", default-features = false, features = [ - "Win32_NetworkManagement_IpHelper", - "Win32_NetworkManagement_Ndis", - "Win32_Networking_WinSock", -] } - [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["html_reports"] } test-fixture = { path = "../test-fixture" } diff --git a/neqo-transport/src/lib.rs b/neqo-transport/src/lib.rs index d198d55075..541f851155 100644 --- a/neqo-transport/src/lib.rs +++ b/neqo-transport/src/lib.rs @@ -22,7 +22,6 @@ mod fc; pub mod frame; #[cfg(not(fuzzing))] mod frame; -mod mtu; mod pace; #[cfg(fuzzing)] pub mod packet; @@ -63,7 +62,6 @@ pub use self::{ }, events::{ConnectionEvent, ConnectionEvents}, frame::CloseError, - mtu::get_interface_mtu, packet::MIN_INITIAL_PACKET_SIZE, pmtud::Pmtud, quic_datagrams::DatagramTracking, diff --git a/neqo-transport/src/mtu.rs b/neqo-transport/src/mtu.rs deleted file mode 100644 index afe1f9b354..0000000000 --- a/neqo-transport/src/mtu.rs +++ /dev/null @@ -1,289 +0,0 @@ -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::{ - io::{Error, ErrorKind}, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, - ptr, -}; - -use neqo_common::qtrace; - -/// Prepare a default error result. -fn default_result() -> Result { - Err(Error::new( - ErrorKind::NotFound, - "Local interface MTU not found", - )) -} - -/// Return the MTU of the interface that is used to reach the given remote socket address. -/// -/// # Errors -/// -/// This function returns an error if the local interface MTU cannot be determined. -pub fn get_interface_mtu(remote: &SocketAddr) -> Result { - #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] - #[allow(unused_assignments)] // Yes, res is reassigned in the platform-specific code. - let mut res = default_result(); - - #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] - { - // Make a new socket that is connected to the remote address. We use this to learn which - // local address is chosen by routing. - let socket = UdpSocket::bind(( - if remote.is_ipv4() { - IpAddr::V4(Ipv4Addr::UNSPECIFIED) - } else { - IpAddr::V6(Ipv6Addr::UNSPECIFIED) - }, - 0, - ))?; - socket.connect(remote)?; - - #[cfg(any(target_os = "macos", target_os = "linux"))] - { - res = get_interface_mtu_linux_macos(&socket); - } - - #[cfg(target_os = "windows")] - { - res = get_interface_mtu_windows(&socket); - } - } - - qtrace!("MTU towards {:?} is {:?}", remote, res); - res -} - -#[cfg(any(target_os = "macos", target_os = "linux"))] -fn get_interface_mtu_linux_macos(socket: &UdpSocket) -> Result { - use std::ffi::{c_int, CStr}; - #[cfg(target_os = "linux")] - use std::{ffi::c_char, mem, os::fd::AsRawFd}; - - use libc::{ - freeifaddrs, getifaddrs, ifaddrs, in_addr_t, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6, - }; - #[cfg(target_os = "macos")] - use libc::{if_data, AF_LINK}; - #[cfg(target_os = "linux")] - use libc::{ifreq, ioctl}; - - // Get the interface list. - let mut ifap: *mut ifaddrs = ptr::null_mut(); // Do not modify this pointer. - if unsafe { getifaddrs(&mut ifap) } != 0 { - return Err(Error::last_os_error()); - } - - // First, find the name of the interface with the local IP address determined above. - let mut cursor = ifap; - let iface = loop { - if cursor.is_null() { - break None; - } - - let ifa = unsafe { &*cursor }; - if !ifa.ifa_addr.is_null() { - let saddr = unsafe { &*ifa.ifa_addr }; - if matches!(c_int::from(saddr.sa_family), AF_INET | AF_INET6) - && match socket.local_addr()?.ip() { - IpAddr::V4(ip) => { - let saddr: sockaddr_in = - unsafe { ptr::read_unaligned(ifa.ifa_addr.cast::()) }; - saddr.sin_addr.s_addr == in_addr_t::to_be(ip.into()) - } - IpAddr::V6(ip) => { - let saddr: sockaddr_in6 = - unsafe { ptr::read_unaligned(ifa.ifa_addr.cast::()) }; - saddr.sin6_addr.s6_addr == ip.octets() - } - } - { - break unsafe { CStr::from_ptr(ifa.ifa_name).to_str().ok() }; - } - } - cursor = ifa.ifa_next; - }; - - // If we have found the interface name we are looking for, find the MTU. - let mut res = default_result(); - if let Some(iface) = iface { - #[cfg(target_os = "macos")] - { - // On macOS, we need to loop again to find the MTU of that interface. We need to - // do two loops, because `getifaddrs` returns one entry per - // interface and link type, and the IP addresses are in the - // AF_INET/AF_INET6 entries for an interface, whereas the - // MTU is (only) in the AF_LINK entry, whose `ifa_addr` - // contains MAC address information, not IP address - // information. - let mut cursor = ifap; - while !cursor.is_null() { - let ifa = unsafe { &*cursor }; - if !ifa.ifa_addr.is_null() { - let saddr = unsafe { &*ifa.ifa_addr }; - let name = - String::from_utf8_lossy(unsafe { CStr::from_ptr(ifa.ifa_name).to_bytes() }); - if c_int::from(saddr.sa_family) == AF_LINK - && !ifa.ifa_data.is_null() - && name == iface - { - let data = unsafe { &*(ifa.ifa_data as *const if_data) }; - res = usize::try_from(data.ifi_mtu).or(res); - break; - } - } - cursor = ifa.ifa_next; - } - } - - #[cfg(target_os = "linux")] - { - // On Linux, we can get the MTU via an ioctl on the socket. - let mut ifr: ifreq = unsafe { mem::zeroed() }; - ifr.ifr_name[..iface.len()].copy_from_slice(unsafe { - &*(ptr::from_ref::<[u8]>(iface.as_bytes()) as *const [c_char]) - }); - if unsafe { ioctl(socket.as_raw_fd(), libc::SIOCGIFMTU, &ifr) } != 0 { - res = Err(Error::last_os_error()); - } else { - res = unsafe { usize::try_from(ifr.ifr_ifru.ifru_mtu).or(res) }; - } - } - } - - unsafe { freeifaddrs(ifap) }; - res -} - -#[cfg(target_os = "windows")] -fn get_interface_mtu_windows(socket: &UdpSocket) -> Result { - use std::{cmp::min, ffi::c_void, slice}; - - use windows::Win32::{ - Foundation::NO_ERROR, - NetworkManagement::IpHelper::{ - FreeMibTable, GetIpInterfaceTable, GetUnicastIpAddressTable, MIB_IPINTERFACE_ROW, - MIB_IPINTERFACE_TABLE, MIB_UNICASTIPADDRESS_ROW, MIB_UNICASTIPADDRESS_TABLE, - }, - Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC}, - }; - - let mut res = default_result(); - - // Get a list of all unicast IP addresses with associated metadata. - let mut addr_table: *mut MIB_UNICASTIPADDRESS_TABLE = ptr::null_mut(); // Do not modify this pointer. - if unsafe { GetUnicastIpAddressTable(AF_UNSPEC, &mut addr_table) } == NO_ERROR { - #[allow(clippy::disallowed_methods)] // Not empty if NO_ERROR was returned. - let addrs = unsafe { - slice::from_raw_parts::( - &(*addr_table).Table[0], - (*addr_table).NumEntries as usize, - ) - }; - - // Get a list of all interfaces with associated metadata. - let mut if_table: *mut MIB_IPINTERFACE_TABLE = ptr::null_mut(); // Do not modify this pointer. - if unsafe { GetIpInterfaceTable(AF_UNSPEC, &mut if_table) } == NO_ERROR { - #[allow(clippy::disallowed_methods)] // Not empty if NO_ERROR was returned. - let ifaces = unsafe { - slice::from_raw_parts::( - &(*if_table).Table[0], - (*if_table).NumEntries as usize, - ) - }; - - // Run through the list of addresses and find the one that matches the local IP - // address. - 'addr_loop: for addr in addrs { - let af = unsafe { addr.Address.si_family }; - let ip = socket.local_addr()?.ip(); - if (af == AF_INET && ip.is_ipv4() || af == AF_INET6 && ip.is_ipv6()) - && match ip { - IpAddr::V4(ip) => { - u32::from(ip).to_be() - == unsafe { addr.Address.Ipv4.sin_addr.S_un.S_addr } - } - IpAddr::V6(ip) => { - ip.octets() == unsafe { addr.Address.Ipv6.sin6_addr.u.Byte } - } - } - { - // For the matching address, find local interface and its MTU. - for iface in ifaces { - if iface.InterfaceIndex == addr.InterfaceIndex { - // On loopback, the MTU is 4294967295... - res = min(iface.NlMtu, 65536).try_into().or(res); - break 'addr_loop; - } - } - } - } - unsafe { FreeMibTable(if_table as *const c_void) }; - } else { - res = Err(Error::last_os_error()); - } - unsafe { FreeMibTable(addr_table as *const c_void) }; - } else { - res = Err(Error::last_os_error()); - } - res -} - -#[cfg(test)] -mod test { - use std::net::ToSocketAddrs; - - use neqo_common::qwarn; - - fn check_mtu(sockaddr: &str, ipv4: bool, expected: usize) { - let addr = sockaddr - .to_socket_addrs() - .unwrap() - .find(|a| a.is_ipv4() == ipv4); - if let Some(addr) = addr { - match super::get_interface_mtu(&addr) { - Ok(mtu) => assert_eq!(mtu, expected), - Err(e) => { - // Some GitHub runners don't have IPv6. Just warn if we can't get the MTU. - assert!(addr.is_ipv6()); - qwarn!("Error getting MTU for {}: {}", sockaddr, e); - } - } - } else { - // Some GitHub runners don't have IPv6. Just warn if we can't get an IPv6 address. - assert!(!ipv4); - qwarn!("No IPv6 address found for {}", sockaddr); - } - } - - #[test] - fn loopback_interface_mtu_v4() { - #[cfg(target_os = "macos")] - check_mtu("localhost:443", true, 16384); - #[cfg(not(target_os = "macos"))] - check_mtu("localhost:443", true, 65536); - } - - #[test] - fn loopback_interface_mtu_v6() { - #[cfg(target_os = "macos")] - check_mtu("localhost:443", false, 16384); - #[cfg(not(target_os = "macos"))] - check_mtu("localhost:443", false, 65536); - } - - #[test] - fn default_interface_mtu_v4() { - check_mtu("ietf.org:443", true, 1500); - } - - #[test] - fn default_interface_mtu_v6() { - check_mtu("ietf.org:443", false, 1500); - } -} diff --git a/neqo-transport/src/path.rs b/neqo-transport/src/path.rs index 8c17db7930..d36af46ffa 100644 --- a/neqo-transport/src/path.rs +++ b/neqo-transport/src/path.rs @@ -15,6 +15,7 @@ use std::{ time::{Duration, Instant}, }; +use mtu::get_interface_mtu; use neqo_common::{hex, qdebug, qinfo, qlog::NeqoQlog, qtrace, Datagram, Encoder, IpTos}; use neqo_crypto::random; @@ -24,7 +25,6 @@ use crate::{ cid::{ConnectionId, ConnectionIdRef, ConnectionIdStore, RemoteConnectionIdEntry}, ecn::{EcnCount, EcnInfo}, frame::{FRAME_TYPE_PATH_CHALLENGE, FRAME_TYPE_PATH_RESPONSE, FRAME_TYPE_RETIRE_CONNECTION_ID}, - get_interface_mtu, packet::PacketBuilder, pmtud::Pmtud, recovery::{RecoveryToken, SentPacket},