From cba785bfdb9bfa4049e44f5465efce0e53b531e2 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 09:56:10 +0300 Subject: [PATCH 001/128] feat: Obtain info via routing sockets WIP This avoids the need to mess around with sockets. --- Cargo.toml | 3 - src/bsd.rs | 135 ++++++++++++++++ src/lib.rs | 457 ++++++----------------------------------------------- 3 files changed, 181 insertions(+), 414 deletions(-) create mode 100644 src/bsd.rs diff --git a/Cargo.toml b/Cargo.toml index 9f597a5f..39f67ef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,6 @@ rust-version = "1.76.0" # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock libc = { version = "0.2", default-features = false } -[dev-dependencies] -rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } - [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock windows-core = "0.58" diff --git a/src/bsd.rs b/src/bsd.rs new file mode 100644 index 00000000..d9628d74 --- /dev/null +++ b/src/bsd.rs @@ -0,0 +1,135 @@ +// 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 core::str; +use std::{ffi::c_void, io::Error, mem, net::IpAddr, ptr}; + +use libc::{ + close, getpid, read, rt_msghdr, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, + socket, write, AF_INET, AF_INET6, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, + RTM_VERSION, SOCK_RAW, +}; + +use crate::default_err; + +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + // Open route socket. + let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; + if fd == -1 { + return Err(Error::last_os_error()); + } + + // Prepare buffer with destination `sockaddr`. + #[allow(clippy::cast_possible_truncation)] + let (remote, len) = match remote { + IpAddr::V4(ip) => { + let mut sin: sockaddr_in = unsafe { mem::zeroed() }; + sin.sin_len = size_of::() as u8; + sin.sin_family = AF_INET as u8; + sin.sin_addr.s_addr = u32::from_ne_bytes(ip.octets()); + ( + ptr::from_ref::(&sin).cast::(), + size_of::(), + ) + } + IpAddr::V6(ip) => { + let mut sin6: sockaddr_in6 = unsafe { mem::zeroed() }; + sin6.sin6_len = size_of::() as u8; + sin6.sin6_family = AF_INET6 as u8; + sin6.sin6_addr.s6_addr = ip.octets(); + ( + ptr::from_ref::(&sin6).cast::(), + size_of::(), + ) + } + }; + + // Prepare route message structure. + let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; + #[allow(clippy::cast_possible_truncation)] + { + rtm.rtm_msglen = (size_of::() + len) as u16; // Length includes sockaddr + rtm.rtm_version = RTM_VERSION as u8; + rtm.rtm_type = RTM_GET as u8; + } + rtm.rtm_seq = fd; // Abuse file descriptor as sequence number, since it's unique + rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info + + // Copy route message and destination `sockaddr` into message buffer. + let mut msg: Vec = vec![0; rtm.rtm_msglen as usize]; + unsafe { + ptr::copy_nonoverlapping( + ptr::from_ref::(&rtm).cast::(), + msg.as_mut_ptr(), + size_of::(), + ); + ptr::copy_nonoverlapping(remote, msg.as_mut_ptr().add(size_of::()), len); + } + + // Send route message. + let res = unsafe { write(fd, msg.as_ptr().cast::(), msg.len()) }; + if res == -1 { + unsafe { close(fd) }; + return Err(Error::last_os_error()); + } + + // Read route messages. + let mut buf = vec![ + 0u8; + size_of::() + + // There will never be `RTAX_MAX` sockaddrs, but it's a safe upper bound + (RTAX_MAX as usize * size_of::()) + ]; + let rtm = loop { + let len = unsafe { read(fd, buf.as_mut_ptr().cast::(), buf.len()) }; + if len <= 0 { + unsafe { close(fd) }; + return Err(Error::last_os_error()); + } + let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; + #[allow(clippy::cast_possible_truncation)] + if rtm.rtm_type == RTM_GET as u8 && rtm.rtm_pid == unsafe { getpid() } && rtm.rtm_seq == fd + { + // This is the response we are looking for. + break rtm; + } + }; + + // Close the route socket. + unsafe { close(fd) }; + + // Parse the route message for the interface name. + let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; + for i in 0..RTAX_MAX { + let sdl = unsafe { ptr::read_unaligned(sa.cast::()) }; + // Check if the address is present in the message + if rtm.rtm_addrs & (1 << i) != 0 { + // Check if the address is the interface address + if i == RTAX_IFP { + if let Ok(name) = unsafe { + str::from_utf8(std::slice::from_raw_parts( + sdl.sdl_data.as_ptr().cast::(), + sdl.sdl_nlen as usize, + )) + } { + // We have our interface name. + return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); + } + } + // Advance to the next address. The length is always a multiple of 4, so we need to do + // some awkward manipulation. + sa = unsafe { + sa.add(if sdl.sdl_len == 0 { + 4 + } else { + ((sdl.sdl_len - 1) | 3) + 1 + } as usize) + }; + } + } + + Err(default_err()) +} diff --git a/src/lib.rs b/src/lib.rs index 578d323d..46927447 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,56 +6,39 @@ use std::{ io::{Error, ErrorKind}, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, + net::IpAddr, }; -// Though the module includes `allow(clippy::all)`, that doesn't seem to affect some lints -#[allow(clippy::semicolon_if_nothing_returned, clippy::struct_field_names)] -#[cfg(windows)] -mod win_bindings; - -/// Prepare a default error result. -fn default_result() -> Result<(String, T), Error> { - Err(Error::new( - ErrorKind::NotFound, - "Local interface MTU not found", - )) -} - -#[derive(Debug)] -pub enum SocketAddrs { - Local(IpAddr), - Remote(SocketAddr), - Both((IpAddr, SocketAddr)), -} +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", +))] +use bsd::interface_and_mtu_impl; -impl From<&(IpAddr, SocketAddr)> for SocketAddrs { - fn from((local, remote): &(IpAddr, SocketAddr)) -> Self { - Self::Both((*local, *remote)) - } -} +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", +))] +mod bsd; -impl From<&(Option, SocketAddr)> for SocketAddrs { - fn from((local, remote): &(Option, SocketAddr)) -> Self { - local.map_or(Self::Remote(*remote), |local| Self::Both((local, *remote))) - } -} +#[cfg(windows)] +mod win_bindings; -impl From<&(IpAddr, Option)> for SocketAddrs { - fn from((local, remote): &(IpAddr, Option)) -> Self { - remote.map_or(Self::Local(*local), |remote| Self::Both((*local, remote))) - } +/// Prepare a default error. +fn default_err() -> Error { + Error::new(ErrorKind::NotFound, "Local interface MTU not found") } /// Return the name and maximum transmission unit (MTU) of a local network interface. /// -/// Given a pair of local and remote [`SocketAddr`]s, return the name and maximum -/// transmission unit (MTU) of the local network interface used by a socket bound to the local -/// address and connected towards the remote destination. -/// -/// If the local address is `None`, the function will let the operating system choose the local -/// address based on the given remote address. If the remote address is `None`, the function will -/// return the MTU of the local network interface with the given local address. +/// Given a remote [`IpAddr`], return the name and maximum transmission unit (MTU) of the local +/// network interface towards the remote destination. /// /// The returned MTU may exceed the maximum IP packet size of 65,535 bytes on some /// platforms for some remote destinations. (For example, loopback destinations on @@ -66,269 +49,22 @@ impl From<&(IpAddr, Option)> for SocketAddrs { /// # Examples /// /// ``` -/// let saddr = "127.0.0.1:443".parse().unwrap(); -/// let (name, mtu) = mtu::interface_and_mtu(&(None, saddr)).unwrap(); -/// println!("MTU towards {saddr:?} is {mtu} on {name}"); +/// let remote = "127.0.0.1".parse().unwrap(); +/// let (name, mtu) = mtu::interface_and_mtu(remote).unwrap(); +/// println!("MTU towards {remote:?} is {mtu} on {name}"); /// ``` /// /// # Errors /// /// This function returns an error if the local interface MTU cannot be determined. -pub fn interface_and_mtu(addrs: A) -> Result<(String, usize), Error> -where - SocketAddrs: From, -{ - let addrs = SocketAddrs::from(addrs); - let local = match addrs { - SocketAddrs::Local(local) | SocketAddrs::Both((local, _)) => local, - SocketAddrs::Remote(remote) => { - if remote.is_ipv4() { - IpAddr::V4(Ipv4Addr::UNSPECIFIED) - } else { - IpAddr::V6(Ipv6Addr::UNSPECIFIED) - } - } - }; - // Let the OS choose an unused local port. - let socket = UdpSocket::bind(SocketAddr::new(local, 0))?; - match addrs { - SocketAddrs::Local(_) => {} - SocketAddrs::Remote(remote) | SocketAddrs::Both((_, remote)) => { - socket.connect(remote)?; - } - } - interface_and_mtu_impl(&socket) -} - -#[cfg(not(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "linux", - target_os = "windows" -)))] -fn interface_and_mtu_impl(_socket: &UdpSocket) -> Result<(String, usize), Error> { - default_result() -} - -#[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "linux" -))] -fn interface_and_mtu_impl(socket: &UdpSocket) -> Result<(String, usize), Error> { - #[cfg(target_os = "linux")] - use std::{ffi::c_char, mem, os::fd::AsRawFd}; - use std::{ - ffi::{c_int, CStr}, - ptr, - }; - - use libc::{ - freeifaddrs, getifaddrs, ifaddrs, in_addr_t, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6, - }; - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - 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(); - if unsafe { getifaddrs(&mut ifap) } != 0 { - return Err(Error::last_os_error()); - } - let ifap = ifap; // Do not modify this pointer. - - // 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(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - // 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) }; - if let Ok(mtu) = usize::try_from(data.ifi_mtu) { - res = Ok((iface.to_string(), mtu)); - } - 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 if let Ok(mtu) = usize::try_from(unsafe { ifr.ifr_ifru.ifru_mtu }) { - res = Ok((iface.to_string(), mtu)); - } - } - } - - unsafe { freeifaddrs(ifap) }; - res -} - -#[cfg(target_os = "windows")] -fn interface_and_mtu_impl(socket: &UdpSocket) -> Result<(String, usize), Error> { - use std::{ - ffi::{c_void, CStr}, - ptr, slice, - }; - - use win_bindings::{ - if_indextoname, FreeMibTable, GetIpInterfaceTable, GetUnicastIpAddressTable, AF_INET, - AF_INET6, AF_UNSPEC, MIB_IPINTERFACE_ROW, MIB_IPINTERFACE_TABLE, MIB_UNICASTIPADDRESS_ROW, - MIB_UNICASTIPADDRESS_TABLE, NO_ERROR, - }; - - 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(); - if unsafe { GetUnicastIpAddressTable(AF_UNSPEC, &mut addr_table) } != NO_ERROR { - return Err(Error::last_os_error()); - } - let addr_table = addr_table; // Do not modify this pointer. - - 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(); - if unsafe { GetIpInterfaceTable(AF_UNSPEC, &mut if_table) } != NO_ERROR { - let error = Error::last_os_error(); - unsafe { FreeMibTable(addr_table as *const c_void) }; - return Err(error); - } - let if_table = if_table; // Do not modify this pointer. - - 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 { - if let Ok(mtu) = iface.NlMtu.try_into() { - let mut name = [0u8; 256]; // IF_NAMESIZE not available? - if unsafe { !if_indextoname(iface.InterfaceIndex, &mut name).is_null() } { - if let Ok(name) = CStr::from_bytes_until_nul(&name) { - if let Ok(name) = name.to_str() { - res = Ok((name.to_string(), mtu)); - } - } - } else { - res = Err(Error::last_os_error()); - } - } - break 'addr_loop; - } - } - } - } - - unsafe { FreeMibTable(if_table as *const c_void) }; - unsafe { FreeMibTable(addr_table as *const c_void) }; - - res +// #[allow(clippy::missing_panics_doc, clippy::too_many_lines)] +pub fn interface_and_mtu(remote: IpAddr) -> Result<(String, usize), Error> { + interface_and_mtu_impl(remote) } #[cfg(test)] mod test { - use std::{ - env, - io::ErrorKind, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, - }; - - use rand::Rng; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::interface_and_mtu; @@ -355,141 +91,40 @@ mod test { // Non-loopback interface names are unpredictable, so we only check the MTU. const INET: NameMtu = NameMtu(None, 1_500); - // The tests can run in parallel, so try and find unused ports for all the tests. - fn socket_with_addr(local_ip: IpAddr) -> SocketAddr { - loop { - let port = rand::thread_rng().gen_range(1024..65535); - let saddr = SocketAddr::new(local_ip, port); - let socket = UdpSocket::bind(saddr); - match socket { - // We found an unused port. - Ok(socket) => return socket.local_addr().unwrap(), - Err(e) => match e.kind() { - ErrorKind::AddrInUse | ErrorKind::PermissionDenied => { - // We hit a used or priviledged port, try again. - continue; - } - _ => { - // We hit another error. Pretend that worked by returning the socket - // address, so the actual code can hit the same error. - return saddr; - } - }, - } - } - } - - fn local_v4() -> SocketAddr { - socket_with_addr(IpAddr::V4(Ipv4Addr::LOCALHOST)) - } - - fn local_v6() -> SocketAddr { - socket_with_addr(IpAddr::V6(Ipv6Addr::LOCALHOST)) - } - - fn inet_v4() -> SocketAddr { - // cloudflare.com - socket_with_addr(IpAddr::V4(Ipv4Addr::new(104, 16, 132, 229))) - } - - fn inet_v6() -> SocketAddr { - // cloudflare.com - socket_with_addr(IpAddr::V6(Ipv6Addr::new( - 0x26, 0x06, 0x47, 0x00, 0x68, 0x10, 0x84, 0xe5, - ))) - } - #[test] - fn loopback_v4_loopback_v4() { + fn loopback_v4() { assert_eq!( - interface_and_mtu(&(local_v4().ip(), local_v4())).unwrap(), + interface_and_mtu(IpAddr::V4(Ipv4Addr::LOCALHOST)).unwrap(), LOOPBACK ); } #[test] - fn loopback_v4_loopback_v6() { - assert!(interface_and_mtu(&(local_v4().ip(), local_v6())).is_err()); - } - - #[test] - fn loopback_v6_loopback_v4() { - assert!(interface_and_mtu(&(local_v6().ip(), local_v4())).is_err()); - } - - #[test] - fn loopback_v6_loopback_v6() { + fn loopback_v6() { assert_eq!( - interface_and_mtu(&(local_v6().ip(), local_v6())).unwrap(), + interface_and_mtu(IpAddr::V6(Ipv6Addr::LOCALHOST)).unwrap(), LOOPBACK ); } - #[test] - fn none_loopback_v4() { - assert_eq!(interface_and_mtu(&(None, local_v4())).unwrap(), LOOPBACK); - } #[test] - fn none_loopback_v6() { - assert_eq!(interface_and_mtu(&(None, local_v6())).unwrap(), LOOPBACK); - } - - #[test] - fn loopback_v4_none() { + fn inet_v4() { + // cloudflare.com assert_eq!( - interface_and_mtu(&(local_v4().ip(), None)).unwrap(), - LOOPBACK + interface_and_mtu(IpAddr::V4(Ipv4Addr::new(104, 16, 132, 229))).unwrap(), + INET ); } #[test] - fn loopback_v6_none() { + fn inet_v6() { + // cloudflare.com assert_eq!( - interface_and_mtu(&(local_v6().ip(), None)).unwrap(), - LOOPBACK + interface_and_mtu(IpAddr::V6(Ipv6Addr::new( + 0x26, 0x06, 0x47, 0x00, 0x68, 0x10, 0x84, 0xe5, + ))) + .unwrap(), + INET ); } - - #[test] - fn inet_v4_inet_v4() { - assert!(interface_and_mtu(&(inet_v4().ip(), inet_v4())).is_err()); - } - - #[test] - fn inet_v4_inet_v6() { - assert!(interface_and_mtu(&(inet_v4().ip(), inet_v6())).is_err()); - } - - #[test] - fn inet_v6_inet_v4() { - assert!(interface_and_mtu(&(inet_v6().ip(), inet_v4())).is_err()); - } - - #[test] - fn inet_v6_inet_v6() { - assert!(interface_and_mtu(&(inet_v6().ip(), inet_v6())).is_err()); - } - #[test] - fn none_inet_v4() { - assert_eq!(interface_and_mtu(&(None, inet_v4())).unwrap(), INET); - } - - #[test] - fn none_inet_v6() { - if env::var("GITHUB_ACTIONS").is_ok() { - // The GitHub CI environment does not have IPv6 connectivity. - return; - } - assert_eq!(interface_and_mtu(&(None, inet_v6())).unwrap(), INET); - } - - #[test] - fn inet_v4_none() { - assert!(interface_and_mtu(&(inet_v4().ip(), None)).is_err()); - } - - #[test] - fn inet_v6_none() { - assert!(interface_and_mtu(&(inet_v6().ip(), None)).is_err()); - } } From f3bd16f3f8a238c427ccef0b12ce6f9da4504b11 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 10:08:37 +0300 Subject: [PATCH 002/128] Fixes --- src/bsd.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index d9628d74..65084666 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -5,7 +5,7 @@ // except according to those terms. use core::str; -use std::{ffi::c_void, io::Error, mem, net::IpAddr, ptr}; +use std::{ffi::c_void, io::Error, mem, mem::size_of, net::IpAddr, ptr}; use libc::{ close, getpid, read, rt_msghdr, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, @@ -14,11 +14,11 @@ use libc::{ }; use crate::default_err; - pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; if fd == -1 { + eprintln!("socket error: {}", Error::last_os_error()); return Err(Error::last_os_error()); } @@ -72,6 +72,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Send route message. let res = unsafe { write(fd, msg.as_ptr().cast::(), msg.len()) }; if res == -1 { + eprintln!("write error: {}", Error::last_os_error()); unsafe { close(fd) }; return Err(Error::last_os_error()); } @@ -86,6 +87,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let rtm = loop { let len = unsafe { read(fd, buf.as_mut_ptr().cast::(), buf.len()) }; if len <= 0 { + eprintln!("read error: {}", Error::last_os_error()); unsafe { close(fd) }; return Err(Error::last_os_error()); } From 36d2c4981c105106c122070dd22fe827f73da7e6 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 10:11:56 +0300 Subject: [PATCH 003/128] Fixes --- src/bsd.rs | 1 + src/lib.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bsd.rs b/src/bsd.rs index 65084666..ecdf6a13 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -14,6 +14,7 @@ use libc::{ }; use crate::default_err; + pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; diff --git a/src/lib.rs b/src/lib.rs index 46927447..c1d7802b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,10 @@ pub fn interface_and_mtu(remote: IpAddr) -> Result<(String, usize), Error> { #[cfg(test)] mod test { - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + use std::{ + env, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + }; use crate::interface_and_mtu; @@ -118,6 +121,10 @@ mod test { #[test] fn inet_v6() { + if env::var("GITHUB_ACTIONS").is_ok() { + // The GitHub CI environment does not have IPv6 connectivity. + return; + } // cloudflare.com assert_eq!( interface_and_mtu(IpAddr::V6(Ipv6Addr::new( From 3d3833cca37be85e1c33fdbae196d0aec403617f Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 14:23:53 +0300 Subject: [PATCH 004/128] Linux --- Cargo.toml | 3 ++ src/bsd.rs | 3 -- src/lib.rs | 4 ++ src/linux.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/linux.rs diff --git a/Cargo.toml b/Cargo.toml index 39f67ef5..e4f6abce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ rust-version = "1.76.0" # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock libc = { version = "0.2", default-features = false } +[target.'cfg(target_os = "linux")'.dependencies] +neli = { version = "0.6", default-features = false } + [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock windows-core = "0.58" diff --git a/src/bsd.rs b/src/bsd.rs index ecdf6a13..5afff32f 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -19,7 +19,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; if fd == -1 { - eprintln!("socket error: {}", Error::last_os_error()); return Err(Error::last_os_error()); } @@ -73,7 +72,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Send route message. let res = unsafe { write(fd, msg.as_ptr().cast::(), msg.len()) }; if res == -1 { - eprintln!("write error: {}", Error::last_os_error()); unsafe { close(fd) }; return Err(Error::last_os_error()); } @@ -88,7 +86,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let rtm = loop { let len = unsafe { read(fd, buf.as_mut_ptr().cast::(), buf.len()) }; if len <= 0 { - eprintln!("read error: {}", Error::last_os_error()); unsafe { close(fd) }; return Err(Error::last_os_error()); } diff --git a/src/lib.rs b/src/lib.rs index c1d7802b..43cbca95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,8 @@ use std::{ target_os = "openbsd", ))] use bsd::interface_and_mtu_impl; +#[cfg(target_os = "linux")] +use linux::interface_and_mtu_impl; #[cfg(any( target_os = "macos", @@ -26,6 +28,8 @@ use bsd::interface_and_mtu_impl; target_os = "openbsd", ))] mod bsd; +#[cfg(target_os = "linux")] +mod linux; #[cfg(windows)] mod win_bindings; diff --git a/src/linux.rs b/src/linux.rs new file mode 100644 index 00000000..b83f4eb9 --- /dev/null +++ b/src/linux.rs @@ -0,0 +1,142 @@ +// 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, net::IpAddr}; + +use neli::{ + attr::Attribute, + consts::{ + nl::{NlmF, NlmFFlags}, + rtnl::{ + Arphrd, IffFlags, Ifla, RtAddrFamily, RtScope, RtTable, Rta, Rtm, RtmFFlags, Rtn, + Rtprot, + }, + socket::NlFamily, + }, + nl::{NlPayload, Nlmsghdr}, + rtnl::{Ifinfomsg, Rtattr, Rtmsg}, + socket::NlSocketHandle, + types::RtBuffer, +}; + +use crate::default_err; + +const fn family(remote: &IpAddr) -> RtAddrFamily { + if remote.is_ipv4() { + RtAddrFamily::Inet + } else { + RtAddrFamily::Inet6 + } +} + +const fn addr_len(remote: &IpAddr) -> u8 { + if remote.is_ipv4() { + 32 + } else { + 128 + } +} + +fn addr_bytes(remote: &IpAddr) -> Vec { + match remote { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + } +} + +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + // Create a netlink socket + let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[])?; + + // Send RTM_GETROUTE message to retrieve the route for the destination IP + let mut route_msg = Rtmsg { + rtm_family: family(&remote), + rtm_dst_len: addr_len(&remote), + rtm_src_len: 0, + rtm_tos: 0, + rtm_table: RtTable::Default, + rtm_protocol: Rtprot::Unspec, + rtm_scope: RtScope::Universe, + rtm_type: Rtn::Unicast, + rtm_flags: RtmFFlags::empty(), + rtattrs: RtBuffer::new(), + }; + let rta = Rtattr::new(None, Rta::Dst, addr_bytes(&remote)).map_err(|_| default_err())?; + route_msg.rtattrs.push(rta); + let route_request = Nlmsghdr::new( + None, + Rtm::Getroute, + NlmFFlags::new(&[NlmF::Request, NlmF::Match]), + None, + None, + NlPayload::Payload(route_msg), + ); + socket.send(route_request).or(Err(default_err()))?; + + // Receive the response to RTM_GETROUTE + let response: Nlmsghdr = socket + .recv() + .map_err(|_| default_err())? + .ok_or_else(default_err)?; + let route_attrs = &response.get_payload().map_err(|_| default_err())?.rtattrs; + let mut ifindex = None; + for attr in route_attrs.iter() { + if let Ok(index) = attr.get_payload_as::() { + if attr.rta_type == Rta::Oif { + ifindex = Some(index); + break; + } + } + } + let ifindex = ifindex.ok_or_else(default_err)?; + println!("Interface Index: {ifindex}"); + + // Send RTM_GETLINK message to retrieve the interface details + let link_msg = Ifinfomsg::new( + family(&remote), + Arphrd::None, + ifindex as libc::c_int, + IffFlags::empty(), + IffFlags::empty(), + RtBuffer::new(), + ); + let link_request = Nlmsghdr::new( + None, + Rtm::Getlink, + NlmFFlags::new(&[NlmF::Dump, NlmF::Ack]), + None, + None, + NlPayload::Payload(link_msg), + ); + socket.send(link_request).or(Err(default_err()))?; + + // Receive the response to RTM_GETLINK + let link_response: Nlmsghdr = socket + .recv() + .map_err(|_| default_err())? + .ok_or_else(default_err)?; + let link_attrs = &link_response + .get_payload() + .map_err(|_| default_err())? + .rtattrs; + let mut ifname = None; + let mut mtu = None; + for attr in link_attrs.iter() { + if attr.rta_type == Ifla::Ifname { + if let Ok(name) = attr.get_payload_as_with_len::() { + ifname = Some(name); + } + } else if attr.rta_type == Ifla::Mtu { + if let Ok(mtu_val) = attr.get_payload_as::() { + mtu = Some(mtu_val as usize); + } + } + } + let ifname = ifname.ok_or_else(default_err)?; + let mtu = mtu.ok_or_else(default_err)?; + + Ok((ifname, mtu)) +} From b4914002f131a375d4543c2ff4edba7eac41fe0a Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 17:49:44 +0300 Subject: [PATCH 005/128] Windows --- src/lib.rs | 7 +++- src/win_bindings.rs | 98 +++++++++---------------------------------- src/windows.rs | 97 ++++++++++++++++++++++++++++++++++++++++++ tests/win_bindings.rs | 13 +++--- 4 files changed, 127 insertions(+), 88 deletions(-) create mode 100644 src/windows.rs diff --git a/src/lib.rs b/src/lib.rs index 43cbca95..050adc94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ use std::{ use bsd::interface_and_mtu_impl; #[cfg(target_os = "linux")] use linux::interface_and_mtu_impl; +#[cfg(target_os = "windows")] +use windows::interface_and_mtu_impl; #[cfg(any( target_os = "macos", @@ -30,8 +32,9 @@ use linux::interface_and_mtu_impl; mod bsd; #[cfg(target_os = "linux")] mod linux; - -#[cfg(windows)] +#[cfg(target_os = "windows")] +mod windows; +#[cfg(target_os = "windows")] mod win_bindings; /// Prepare a default error. diff --git a/src/win_bindings.rs b/src/win_bindings.rs index 107b5c67..1cd2af83 100644 --- a/src/win_bindings.rs +++ b/src/win_bindings.rs @@ -13,6 +13,11 @@ pub unsafe fn FreeMibTable(memory: *const core::ffi::c_void) { FreeMibTable(memory) } #[inline] +pub unsafe fn GetBestInterfaceEx(pdestaddr: *const SOCKADDR, pdwbestifindex: *mut u32) -> u32 { + windows_targets::link!("iphlpapi.dll" "system" fn GetBestInterfaceEx(pdestaddr : *const SOCKADDR, pdwbestifindex : *mut u32) -> u32); + GetBestInterfaceEx(pdestaddr, pdwbestifindex) +} +#[inline] pub unsafe fn GetIpInterfaceTable( family: ADDRESS_FAMILY, table: *mut *mut MIB_IPINTERFACE_TABLE, @@ -21,14 +26,6 @@ pub unsafe fn GetIpInterfaceTable( GetIpInterfaceTable(family, table) } #[inline] -pub unsafe fn GetUnicastIpAddressTable( - family: ADDRESS_FAMILY, - table: *mut *mut MIB_UNICASTIPADDRESS_TABLE, -) -> WIN32_ERROR { - windows_targets::link!("iphlpapi.dll" "system" fn GetUnicastIpAddressTable(family : ADDRESS_FAMILY, table : *mut *mut MIB_UNICASTIPADDRESS_TABLE) -> WIN32_ERROR); - GetUnicastIpAddressTable(family, table) -} -#[inline] pub unsafe fn if_indextoname( interfaceindex: u32, interfacename: &mut [u8; 256], @@ -209,44 +206,6 @@ impl Default for MIB_IPINTERFACE_TABLE { } #[repr(C)] #[derive(Clone, Copy)] -pub struct MIB_UNICASTIPADDRESS_ROW { - pub Address: SOCKADDR_INET, - pub InterfaceLuid: NET_LUID_LH, - pub InterfaceIndex: u32, - pub PrefixOrigin: NL_PREFIX_ORIGIN, - pub SuffixOrigin: NL_SUFFIX_ORIGIN, - pub ValidLifetime: u32, - pub PreferredLifetime: u32, - pub OnLinkPrefixLength: u8, - pub SkipAsSource: BOOLEAN, - pub DadState: NL_DAD_STATE, - pub ScopeId: SCOPE_ID, - pub CreationTimeStamp: i64, -} -impl windows_core::TypeKind for MIB_UNICASTIPADDRESS_ROW { - type TypeKind = windows_core::CopyType; -} -impl Default for MIB_UNICASTIPADDRESS_ROW { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct MIB_UNICASTIPADDRESS_TABLE { - pub NumEntries: u32, - pub Table: [MIB_UNICASTIPADDRESS_ROW; 1], -} -impl windows_core::TypeKind for MIB_UNICASTIPADDRESS_TABLE { - type TypeKind = windows_core::CopyType; -} -impl Default for MIB_UNICASTIPADDRESS_TABLE { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] pub union NET_LUID_LH { pub Value: u64, pub Info: NET_LUID_LH_0, @@ -272,17 +231,6 @@ impl Default for NET_LUID_LH_0 { unsafe { core::mem::zeroed() } } } -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct NL_DAD_STATE(pub i32); -impl windows_core::TypeKind for NL_DAD_STATE { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for NL_DAD_STATE { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NL_DAD_STATE").field(&self.0).finish() - } -} #[repr(C)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct NL_INTERFACE_OFFLOAD_ROD { @@ -311,17 +259,6 @@ impl core::fmt::Debug for NL_LINK_LOCAL_ADDRESS_BEHAVIOR { } #[repr(transparent)] #[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct NL_PREFIX_ORIGIN(pub i32); -impl windows_core::TypeKind for NL_PREFIX_ORIGIN { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for NL_PREFIX_ORIGIN { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NL_PREFIX_ORIGIN").field(&self.0).finish() - } -} -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] pub struct NL_ROUTER_DISCOVERY_BEHAVIOR(pub i32); impl windows_core::TypeKind for NL_ROUTER_DISCOVERY_BEHAVIOR { type TypeKind = windows_core::CopyType; @@ -333,17 +270,6 @@ impl core::fmt::Debug for NL_ROUTER_DISCOVERY_BEHAVIOR { .finish() } } -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct NL_SUFFIX_ORIGIN(pub i32); -impl windows_core::TypeKind for NL_SUFFIX_ORIGIN { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for NL_SUFFIX_ORIGIN { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NL_SUFFIX_ORIGIN").field(&self.0).finish() - } -} pub const NO_ERROR: WIN32_ERROR = WIN32_ERROR(0u32); #[repr(C)] #[derive(Clone, Copy)] @@ -386,6 +312,20 @@ impl Default for SCOPE_ID_0_0 { } } #[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SOCKADDR { + pub sa_family: ADDRESS_FAMILY, + pub sa_data: [i8; 14], +} +impl windows_core::TypeKind for SOCKADDR { + type TypeKind = windows_core::CopyType; +} +impl Default for SOCKADDR { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} +#[repr(C)] #[derive(Clone, Copy)] pub struct SOCKADDR_IN { pub sin_family: ADDRESS_FAMILY, diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 00000000..13270e15 --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,97 @@ +// 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::{ + ffi::{c_void, CStr}, + io::Error, + net::IpAddr, + ptr, slice, +}; + +use crate::{ + default_err, + win_bindings::{ + if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, + AF_UNSPEC, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, + MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_IN6_0, + SOCKADDR_INET, + }, +}; + +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + // Convert remote to Windows SOCKADDR_INET format + let saddr = match remote { + IpAddr::V4(ip) => SOCKADDR_INET { + Ipv4: SOCKADDR_IN { + sin_family: AF_INET, + sin_port: 0, + sin_addr: IN_ADDR { + S_un: IN_ADDR_0 { + S_addr: u32::to_be(ip.into()), + }, + }, + sin_zero: [0; 8], + }, + }, + IpAddr::V6(ip) => SOCKADDR_INET { + Ipv6: SOCKADDR_IN6 { + sin6_family: AF_INET6, + sin6_port: 0, + sin6_flowinfo: 0, + sin6_addr: IN6_ADDR { + u: IN6_ADDR_0 { Byte: ip.octets() }, + }, + Anonymous: SOCKADDR_IN6_0::default(), + }, + }, + }; + + let mut idx = 0; + if unsafe { + GetBestInterfaceEx( + ptr::from_ref(&saddr).cast::(), + ptr::from_mut(&mut idx), + ) + } != 0 + { + return Err(Error::last_os_error()); + } + + // Get a list of all interfaces with associated metadata. + let mut if_table: *mut MIB_IPINTERFACE_TABLE = ptr::null_mut(); + if unsafe { GetIpInterfaceTable(AF_UNSPEC, &mut if_table) } != NO_ERROR { + return Err(Error::last_os_error()); + } + let if_table = if_table; // Do not modify this pointer. + let ifaces = unsafe { + slice::from_raw_parts::( + &(*if_table).Table[0], + (*if_table).NumEntries as usize, + ) + }; + + // Find the local interface matching `idx`. + for iface in ifaces { + if iface.InterfaceIndex == idx { + if let Ok(mtu) = iface.NlMtu.try_into() { + let mut name = [0u8; 256]; // IF_NAMESIZE not available? + if unsafe { !if_indextoname(iface.InterfaceIndex, &mut name).is_null() } { + if let Ok(name) = CStr::from_bytes_until_nul(&name) { + if let Ok(name) = name.to_str() { + // We found our interface information. + unsafe { FreeMibTable(if_table as *const c_void) }; + return Ok((name.to_string(), mtu)); + } + } + } + } + break; + } + } + + unsafe { FreeMibTable(if_table as *const c_void) }; + Err(default_err()) +} diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index 1367397e..29d0c419 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -12,17 +12,16 @@ fn codegen_windows_bindings() { "flatten", "--filter", "Windows.Win32.Foundation.NO_ERROR", + "Windows.Win32.Networking.WinSock.AF_INET", + "Windows.Win32.Networking.WinSock.AF_INET6", + "Windows.Win32.Networking.WinSock.AF_UNSPEC", + "Windows.Win32.Networking.WinSock.SOCKADDR_INET", "Windows.Win32.NetworkManagement.IpHelper.FreeMibTable", + "Windows.Win32.NetworkManagement.IpHelper.GetBestInterfaceEx", "Windows.Win32.NetworkManagement.IpHelper.GetIpInterfaceTable", - "Windows.Win32.NetworkManagement.IpHelper.GetUnicastIpAddressTable", + "Windows.Win32.NetworkManagement.IpHelper.if_indextoname", "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_ROW", "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_TABLE", - "Windows.Win32.NetworkManagement.IpHelper.MIB_UNICASTIPADDRESS_ROW", - "Windows.Win32.NetworkManagement.IpHelper.MIB_UNICASTIPADDRESS_TABLE", - "Windows.Win32.NetworkManagement.IpHelper.if_indextoname", - "Windows.Win32.Networking.WinSock.AF_INET", - "Windows.Win32.Networking.WinSock.AF_INET6", - "Windows.Win32.Networking.WinSock.AF_UNSPEC", ]) .unwrap(); From 44a1163e0052ecd1f201fbef36e749c8340bab3f Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 18:20:41 +0300 Subject: [PATCH 006/128] Fixes --- src/bsd.rs | 33 +++++++++++++++++++++------------ src/lib.rs | 4 ++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 5afff32f..999db87e 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -4,7 +4,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::str; +use core::{slice, str}; use std::{ffi::c_void, io::Error, mem, mem::size_of, net::IpAddr, ptr}; use libc::{ @@ -15,6 +15,7 @@ use libc::{ use crate::default_err; +#[allow(clippy::too_many_lines)] pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; @@ -24,26 +25,30 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Prepare buffer with destination `sockaddr`. #[allow(clippy::cast_possible_truncation)] - let (remote, len) = match remote { + let dst: &[u8] = match remote { IpAddr::V4(ip) => { let mut sin: sockaddr_in = unsafe { mem::zeroed() }; sin.sin_len = size_of::() as u8; sin.sin_family = AF_INET as u8; sin.sin_addr.s_addr = u32::from_ne_bytes(ip.octets()); - ( - ptr::from_ref::(&sin).cast::(), - size_of::(), - ) + unsafe { + slice::from_raw_parts( + ptr::from_ref::(&sin).cast::(), + size_of::(), + ) + } } IpAddr::V6(ip) => { let mut sin6: sockaddr_in6 = unsafe { mem::zeroed() }; sin6.sin6_len = size_of::() as u8; sin6.sin6_family = AF_INET6 as u8; sin6.sin6_addr.s6_addr = ip.octets(); - ( - ptr::from_ref::(&sin6).cast::(), - size_of::(), - ) + unsafe { + slice::from_raw_parts( + ptr::from_ref::(&sin6).cast::(), + size_of::(), + ) + } } }; @@ -51,7 +56,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; #[allow(clippy::cast_possible_truncation)] { - rtm.rtm_msglen = (size_of::() + len) as u16; // Length includes sockaddr + rtm.rtm_msglen = (size_of::() + dst.len()) as u16; // Length includes sockaddr rtm.rtm_version = RTM_VERSION as u8; rtm.rtm_type = RTM_GET as u8; } @@ -66,7 +71,11 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> msg.as_mut_ptr(), size_of::(), ); - ptr::copy_nonoverlapping(remote, msg.as_mut_ptr().add(size_of::()), len); + ptr::copy_nonoverlapping( + dst.as_ptr(), + msg.as_mut_ptr().add(size_of::()), + dst.len(), + ); } // Send route message. diff --git a/src/lib.rs b/src/lib.rs index 050adc94..979b9909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,9 +33,9 @@ mod bsd; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "windows")] -mod windows; -#[cfg(target_os = "windows")] mod win_bindings; +#[cfg(target_os = "windows")] +mod windows; /// Prepare a default error. fn default_err() -> Error { From 89d1fe751e05f9689719956808fccb60556040aa Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 18:42:18 +0300 Subject: [PATCH 007/128] Clippy --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 979b9909..4e9e7474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +// TODO: Remove this after `neli-proc-macros` starts using `syn@2`. +#![allow(clippy::multiple_crate_versions)] + use std::{ io::{Error, ErrorKind}, net::IpAddr, From ed1795fd9d6140e7286cb86d8ead4bf437e687d0 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 1 Oct 2024 18:52:18 +0300 Subject: [PATCH 008/128] Clippy --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4e9e7474..45ae618c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,10 +33,15 @@ use windows::interface_and_mtu_impl; target_os = "openbsd", ))] mod bsd; + #[cfg(target_os = "linux")] mod linux; + #[cfg(target_os = "windows")] +// Though the module includes `allow(clippy::all)`, that doesn't seem to affect some lints +#[allow(clippy::semicolon_if_nothing_returned, clippy::struct_field_names)] mod win_bindings; + #[cfg(target_os = "windows")] mod windows; From e95c38f453143f48e51382c37102ed5b971f1c6e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 09:10:58 +0300 Subject: [PATCH 009/128] Update src/bsd.rs Co-authored-by: Martin Thomson --- src/bsd.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 999db87e..7a9802e3 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -130,13 +130,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Advance to the next address. The length is always a multiple of 4, so we need to do // some awkward manipulation. - sa = unsafe { - sa.add(if sdl.sdl_len == 0 { - 4 - } else { - ((sdl.sdl_len - 1) | 3) + 1 - } as usize) + let incr = if sdl.sdl_len == 0 { + 4 + } else { + ((sdl.sdl_len - 1) | 3) + 1 }; + sa = unsafe { sa.add(incr as usize) }; } } From f2444ce376bb1ac49f9ae514a4c3a81dc0fae542 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 09:37:39 +0300 Subject: [PATCH 010/128] Try and fix sanitizer issue --- src/bsd.rs | 80 ++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 7a9802e3..58501ee8 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -4,8 +4,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::{slice, str}; -use std::{ffi::c_void, io::Error, mem, mem::size_of, net::IpAddr, ptr}; +use std::{ + ffi::c_void, + io::Error, + mem::{self, size_of}, + net::IpAddr, + ptr, slice, str, +}; use libc::{ close, getpid, read, rt_msghdr, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, @@ -15,7 +20,6 @@ use libc::{ use crate::default_err; -#[allow(clippy::too_many_lines)] pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; @@ -24,42 +28,33 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Prepare buffer with destination `sockaddr`. - #[allow(clippy::cast_possible_truncation)] - let dst: &[u8] = match remote { + let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; + match remote { IpAddr::V4(ip) => { - let mut sin: sockaddr_in = unsafe { mem::zeroed() }; - sin.sin_len = size_of::() as u8; - sin.sin_family = AF_INET as u8; + let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; + sin.sin_len = size_of::() + .try_into() + .map_err(|_| default_err())?; + sin.sin_family = AF_INET.try_into().map_err(|_| default_err())?; sin.sin_addr.s_addr = u32::from_ne_bytes(ip.octets()); - unsafe { - slice::from_raw_parts( - ptr::from_ref::(&sin).cast::(), - size_of::(), - ) - } } IpAddr::V6(ip) => { - let mut sin6: sockaddr_in6 = unsafe { mem::zeroed() }; - sin6.sin6_len = size_of::() as u8; - sin6.sin6_family = AF_INET6 as u8; + let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; + sin6.sin6_len = size_of::() + .try_into() + .map_err(|_| default_err())?; + sin6.sin6_family = AF_INET6.try_into().map_err(|_| default_err())?; sin6.sin6_addr.s6_addr = ip.octets(); - unsafe { - slice::from_raw_parts( - ptr::from_ref::(&sin6).cast::(), - size_of::(), - ) - } } }; // Prepare route message structure. let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; - #[allow(clippy::cast_possible_truncation)] - { - rtm.rtm_msglen = (size_of::() + dst.len()) as u16; // Length includes sockaddr - rtm.rtm_version = RTM_VERSION as u8; - rtm.rtm_type = RTM_GET as u8; - } + rtm.rtm_msglen = (size_of::() + dst.ss_len as usize) + .try_into() + .map_err(|_| default_err())?; // Length includes sockaddr + rtm.rtm_version = RTM_VERSION.try_into().map_err(|_| default_err())?; + rtm.rtm_type = RTM_GET.try_into().map_err(|_| default_err())?; rtm.rtm_seq = fd; // Abuse file descriptor as sequence number, since it's unique rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info @@ -72,35 +67,38 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> size_of::(), ); ptr::copy_nonoverlapping( - dst.as_ptr(), + ptr::from_ref::(&dst).cast::(), msg.as_mut_ptr().add(size_of::()), - dst.len(), + dst.ss_len as usize, ); } // Send route message. let res = unsafe { write(fd, msg.as_ptr().cast::(), msg.len()) }; if res == -1 { + let err = Error::last_os_error(); unsafe { close(fd) }; - return Err(Error::last_os_error()); + return Err(err); } // Read route messages. let mut buf = vec![ 0u8; size_of::() + - // There will never be `RTAX_MAX` sockaddrs, but it's a safe upper bound + // There will never be `RTAX_MAX` sockaddrs attached, but it's a safe upper bound. (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { let len = unsafe { read(fd, buf.as_mut_ptr().cast::(), buf.len()) }; if len <= 0 { + let err = Error::last_os_error(); unsafe { close(fd) }; - return Err(Error::last_os_error()); + return Err(err); } let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; - #[allow(clippy::cast_possible_truncation)] - if rtm.rtm_type == RTM_GET as u8 && rtm.rtm_pid == unsafe { getpid() } && rtm.rtm_seq == fd + if rtm.rtm_type == RTM_GET.try_into().map_err(|_| default_err())? + && rtm.rtm_pid == unsafe { getpid() } + && rtm.rtm_seq == fd { // This is the response we are looking for. break rtm; @@ -118,12 +116,10 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> if rtm.rtm_addrs & (1 << i) != 0 { // Check if the address is the interface address if i == RTAX_IFP { - if let Ok(name) = unsafe { - str::from_utf8(std::slice::from_raw_parts( - sdl.sdl_data.as_ptr().cast::(), - sdl.sdl_nlen as usize, - )) - } { + let name = unsafe { + slice::from_raw_parts(sdl.sdl_data.as_ptr().cast::(), sdl.sdl_nlen as usize) + }; + if let Ok(name) = str::from_utf8(name) { // We have our interface name. return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } From ee08c782b64e2bf98007ad2a8340f96067cd5096 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 09:53:22 +0300 Subject: [PATCH 011/128] Rearrange Windows code --- src/lib.rs | 5 ----- src/{windows.rs => windows/mod.rs} | 4 ++++ src/{ => windows}/win_bindings.rs | 0 tests/win_bindings.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) rename src/{windows.rs => windows/mod.rs} (94%) rename src/{ => windows}/win_bindings.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 45ae618c..acbffeb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,11 +37,6 @@ mod bsd; #[cfg(target_os = "linux")] mod linux; -#[cfg(target_os = "windows")] -// Though the module includes `allow(clippy::all)`, that doesn't seem to affect some lints -#[allow(clippy::semicolon_if_nothing_returned, clippy::struct_field_names)] -mod win_bindings; - #[cfg(target_os = "windows")] mod windows; diff --git a/src/windows.rs b/src/windows/mod.rs similarity index 94% rename from src/windows.rs rename to src/windows/mod.rs index 13270e15..8e6efac6 100644 --- a/src/windows.rs +++ b/src/windows/mod.rs @@ -21,6 +21,10 @@ use crate::{ }, }; +// Though the module includes `allow(clippy::all)`, that doesn't seem to affect some lints +#[allow(clippy::semicolon_if_nothing_returned, clippy::struct_field_names)] +mod win_bindings; + pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Convert remote to Windows SOCKADDR_INET format let saddr = match remote { diff --git a/src/win_bindings.rs b/src/windows/win_bindings.rs similarity index 100% rename from src/win_bindings.rs rename to src/windows/win_bindings.rs diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index 29d0c419..17881e48 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -35,4 +35,4 @@ fn codegen_windows_bindings() { } } -const TARGET: &str = "src/win_bindings.rs"; +const TARGET: &str = "src/windows/win_bindings.rs"; From ff7fb829d39dc63d890e62202468a9394963b706 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 10:03:56 +0300 Subject: [PATCH 012/128] Fixes --- src/windows/mod.rs | 57 +++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 8e6efac6..12611722 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -7,17 +7,17 @@ use std::{ ffi::{c_void, CStr}, io::Error, + mem, net::IpAddr, ptr, slice, }; use crate::{ default_err, - win_bindings::{ + windows::win_bindings::{ if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, AF_UNSPEC, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, - MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_IN6_0, - SOCKADDR_INET, + MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_INET, }, }; @@ -27,40 +27,35 @@ mod win_bindings; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Convert remote to Windows SOCKADDR_INET format - let saddr = match remote { - IpAddr::V4(ip) => SOCKADDR_INET { - Ipv4: SOCKADDR_IN { - sin_family: AF_INET, - sin_port: 0, - sin_addr: IN_ADDR { - S_un: IN_ADDR_0 { - S_addr: u32::to_be(ip.into()), - }, + let mut dst: SOCKADDR_INET = unsafe { mem::zeroed() }; + match remote { + IpAddr::V4(ip) => { + let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; + sin.sin_family = AF_INET; + sin.sin_addr = IN_ADDR { + S_un: IN_ADDR_0 { + S_addr: u32::to_be(ip.into()), }, - sin_zero: [0; 8], - }, - }, - IpAddr::V6(ip) => SOCKADDR_INET { - Ipv6: SOCKADDR_IN6 { - sin6_family: AF_INET6, - sin6_port: 0, - sin6_flowinfo: 0, - sin6_addr: IN6_ADDR { - u: IN6_ADDR_0 { Byte: ip.octets() }, - }, - Anonymous: SOCKADDR_IN6_0::default(), - }, - }, - }; + } + } + IpAddr::V6(ip) => { + let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = IN6_ADDR { + u: IN6_ADDR_0 { Byte: ip.octets() }, + }; + } + } + // Get the interface index of the best outbound interface towards `dst`. let mut idx = 0; - if unsafe { + let res = unsafe { GetBestInterfaceEx( - ptr::from_ref(&saddr).cast::(), + ptr::from_ref(&dst).cast::(), ptr::from_mut(&mut idx), ) - } != 0 - { + }; + if res != 0 { return Err(Error::last_os_error()); } From 356c669b0bd9d5c94010c14b08d2e459859904fd Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 14:22:10 +0300 Subject: [PATCH 013/128] Fixes --- src/linux.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index b83f4eb9..9ed2a972 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -48,10 +48,10 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { - // Create a netlink socket + // Create a netlink socket. let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[])?; - // Send RTM_GETROUTE message to retrieve the route for the destination IP + // Send RTM_GETROUTE message to retrieve the route for the destination IP. let mut route_msg = Rtmsg { rtm_family: family(&remote), rtm_dst_len: addr_len(&remote), @@ -69,7 +69,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let route_request = Nlmsghdr::new( None, Rtm::Getroute, - NlmFFlags::new(&[NlmF::Request, NlmF::Match]), + NlmFFlags::new(&[NlmF::Request, NlmF::Ack]), None, None, NlPayload::Payload(route_msg), @@ -92,9 +92,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } } let ifindex = ifindex.ok_or_else(default_err)?; - println!("Interface Index: {ifindex}"); - // Send RTM_GETLINK message to retrieve the interface details + // Create another netlink socket. + // TODO: Use the same socket for both requests. It's not clear why this is failing. + let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[])?; + + // Send RTM_GETLINK message to retrieve the interface details. let link_msg = Ifinfomsg::new( family(&remote), Arphrd::None, @@ -106,7 +109,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let link_request = Nlmsghdr::new( None, Rtm::Getlink, - NlmFFlags::new(&[NlmF::Dump, NlmF::Ack]), + NlmFFlags::new(&[NlmF::Request, NlmF::Ack]), None, None, NlPayload::Payload(link_msg), From 66fdaab4c6047bb2e233ccec852fbc503807b25b Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 14:31:30 +0300 Subject: [PATCH 014/128] Omit win_bindings.rs from codecov (again) --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index f9c5935a..eb2806ac 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,5 +1,5 @@ ignore: - - "src/win_bindings.rs" + - "src/windows/win_bindings.rs" # Do not notify until at least three results have been uploaded from the CI pipeline. # (This corresponds to the three main platforms we support: Linux, macOS, and Windows.) From 4d0e27436e0fff2ca8927ddaa4074e104fe9be93 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 2 Oct 2024 14:40:54 +0300 Subject: [PATCH 015/128] Update docs --- README.md | 10 ++++------ src/lib.rs | 12 ++++-------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 83ff5920..7b843b9f 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,14 @@ A crate to return the name and maximum transmission unit (MTU) of the local netw ## Usage -This crate exports a single function `interface_and_mtu` that, given a pair of local and remote `SocketAddr`s, returns the name and [maximum transmission unit (MTU)](https://en.wikipedia.org/wiki/Maximum_transmission_unit) of the local network interface used by a socket bound to the local address and connected towards the remote destination. - -If the local address is `None`, the function will let the operating system choose the local address based on the given remote address. If the remote address is `None`, the function will return the name and MTU of the local network interface with the given local address. +This crate exports a single function `interface_and_mtu` that returns the name and [maximum transmission unit (MTU)](https://en.wikipedia.org/wiki/Maximum_transmission_unit) of the outgoing network interface towards a remote destination identified by an `IpAddr`, ## Example ```rust -let saddr = "127.0.0.1:443".parse().unwrap(); -let (name, mtu) = mtu::interface_and_mtu(&(None, saddr)).unwrap(); -println!("MTU for {saddr:?} is {mtu} on {name}"); +let remote = "127.0.0.1".parse().unwrap(); +let (name, mtu) = mtu::interface_and_mtu(remote).unwrap(); +println!("MTU towards {remote:?} is {mtu} on {name}"); ``` ## Supported Platforms diff --git a/src/lib.rs b/src/lib.rs index acbffeb4..057bac0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,14 +45,11 @@ fn default_err() -> Error { Error::new(ErrorKind::NotFound, "Local interface MTU not found") } -/// Return the name and maximum transmission unit (MTU) of a local network interface. +/// Return the name and maximum transmission unit (MTU) of the outgoing network interface towards a +/// remote destination identified by an [`IpAddr`], /// -/// Given a remote [`IpAddr`], return the name and maximum transmission unit (MTU) of the local -/// network interface towards the remote destination. -/// -/// The returned MTU may exceed the maximum IP packet size of 65,535 bytes on some -/// platforms for some remote destinations. (For example, loopback destinations on -/// Windows.) +/// The returned MTU may exceed the maximum IP packet size of 65,535 bytes on some platforms for +/// some remote destinations. (For example, loopback destinations on Windows.) /// /// The returned interface name is obtained from the operating system. /// @@ -67,7 +64,6 @@ fn default_err() -> Error { /// # Errors /// /// This function returns an error if the local interface MTU cannot be determined. -// #[allow(clippy::missing_panics_doc, clippy::too_many_lines)] pub fn interface_and_mtu(remote: IpAddr) -> Result<(String, usize), Error> { interface_and_mtu_impl(remote) } From 8e35eea18d4ba3a1cf14a0f89ea243dc261d5fc9 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 3 Oct 2024 10:06:12 +0300 Subject: [PATCH 016/128] Read all socket responses to allow socket reuse --- src/linux.rs | 62 +++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 9ed2a972..f50c201f 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -76,27 +76,26 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> ); socket.send(route_request).or(Err(default_err()))?; - // Receive the response to RTM_GETROUTE - let response: Nlmsghdr = socket - .recv() - .map_err(|_| default_err())? - .ok_or_else(default_err)?; - let route_attrs = &response.get_payload().map_err(|_| default_err())?.rtattrs; + // Receive all response to RTM_GETROUTE. (If we don't consume all responses, it seems like we + // cannot reuse it for another request.) let mut ifindex = None; - for attr in route_attrs.iter() { - if let Ok(index) = attr.get_payload_as::() { - if attr.rta_type == Rta::Oif { - ifindex = Some(index); - break; + for response in socket.iter(false) { + let header: Nlmsghdr = response.map_err(|_| default_err())?; + if header.nl_type != Rtm::Newroute { + continue; + } + let route_attrs = &header.get_payload().map_err(|_| default_err())?.rtattrs; + for attr in route_attrs.iter() { + if let Ok(index) = attr.get_payload_as::() { + if attr.rta_type == Rta::Oif { + ifindex = Some(index); + break; + } } } } let ifindex = ifindex.ok_or_else(default_err)?; - // Create another netlink socket. - // TODO: Use the same socket for both requests. It's not clear why this is failing. - let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[])?; - // Send RTM_GETLINK message to retrieve the interface details. let link_msg = Ifinfomsg::new( family(&remote), @@ -116,25 +115,24 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> ); socket.send(link_request).or(Err(default_err()))?; - // Receive the response to RTM_GETLINK - let link_response: Nlmsghdr = socket - .recv() - .map_err(|_| default_err())? - .ok_or_else(default_err)?; - let link_attrs = &link_response - .get_payload() - .map_err(|_| default_err())? - .rtattrs; + // Receive the responses to RTM_GETLINK. let mut ifname = None; let mut mtu = None; - for attr in link_attrs.iter() { - if attr.rta_type == Ifla::Ifname { - if let Ok(name) = attr.get_payload_as_with_len::() { - ifname = Some(name); - } - } else if attr.rta_type == Ifla::Mtu { - if let Ok(mtu_val) = attr.get_payload_as::() { - mtu = Some(mtu_val as usize); + for response in socket.iter(false) { + let header: Nlmsghdr = response.map_err(|_| default_err())?; + if header.nl_type != Rtm::Newlink { + continue; + } + let link_attrs = &header.get_payload().map_err(|_| default_err())?.rtattrs; + for attr in link_attrs.iter() { + if attr.rta_type == Ifla::Ifname { + if let Ok(name) = attr.get_payload_as_with_len::() { + ifname = Some(name); + } + } else if attr.rta_type == Ifla::Mtu { + if let Ok(mtu_val) = attr.get_payload_as::() { + mtu = Some(mtu_val as usize); + } } } } From e8060db67f0c86ba348454484fc35f90e1e983c7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 3 Oct 2024 10:10:46 +0300 Subject: [PATCH 017/128] Update README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b843b9f..3043792b 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,8 @@ println!("MTU towards {remote:?} is {mtu} on {name}"); * Linux * macOS * Windows -* FreeBSD -* NetBSD -* OpenBSD + +FreeBSD, NetBSD and OpenBSD support is waiting for [rust/libc#3714](https://github.com/rust-lang/libc/pull/3714). ## Notes From 71aa8def934101f03306f40310c5165db072919a Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 3 Oct 2024 10:28:48 +0300 Subject: [PATCH 018/128] Add .clippy.toml --- .clippy.toml | 2 ++ src/lib.rs | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 .clippy.toml diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 00000000..4b59b71b --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,2 @@ +# TODO: Remove this after `neli-proc-macros` starts using `syn@2`. +allowed-duplicate-crates = ["syn"] diff --git a/src/lib.rs b/src/lib.rs index 057bac0b..f3191481 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// TODO: Remove this after `neli-proc-macros` starts using `syn@2`. -#![allow(clippy::multiple_crate_versions)] - use std::{ io::{Error, ErrorKind}, net::IpAddr, From a58dc6518aa7cd5f9d0b3808d9b479d6fc3a7982 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 13:01:46 +0300 Subject: [PATCH 019/128] Update src/windows/mod.rs Co-authored-by: Max Inden --- src/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 12611722..4326fc04 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -26,7 +26,7 @@ use crate::{ mod win_bindings; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { - // Convert remote to Windows SOCKADDR_INET format + // Convert remote to Windows SOCKADDR_INET format. let mut dst: SOCKADDR_INET = unsafe { mem::zeroed() }; match remote { IpAddr::V4(ip) => { From 9c892c78790a05c33b454cd4abfa945ea78db34c Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 17:58:33 +0300 Subject: [PATCH 020/128] Drop neli --- Cargo.toml | 3 - src/linux.rs | 373 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 274 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4f6abce..39f67ef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,6 @@ rust-version = "1.76.0" # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock libc = { version = "0.2", default-features = false } -[target.'cfg(target_os = "linux")'.dependencies] -neli = { version = "0.6", default-features = false } - [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock windows-core = "0.58" diff --git a/src/linux.rs b/src/linux.rs index f50c201f..cc47b3fa 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -4,39 +4,53 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::{io::Error, net::IpAddr}; - -use neli::{ - attr::Attribute, - consts::{ - nl::{NlmF, NlmFFlags}, - rtnl::{ - Arphrd, IffFlags, Ifla, RtAddrFamily, RtScope, RtTable, Rta, Rtm, RtmFFlags, Rtn, - Rtprot, - }, - socket::NlFamily, - }, - nl::{NlPayload, Nlmsghdr}, - rtnl::{Ifinfomsg, Rtattr, Rtmsg}, - socket::NlSocketHandle, - types::RtBuffer, +use core::str; +use std::{io::Error, mem, net::IpAddr, ptr, slice}; + +use libc::{ + c_int, c_uchar, c_uint, c_ushort, close, nlmsghdr, recv, socket, write, AF_INET, AF_INET6, + AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, MSG_DONTWAIT, NETLINK_ROUTE, + NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, + RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; use crate::default_err; -const fn family(remote: &IpAddr) -> RtAddrFamily { - if remote.is_ipv4() { - RtAddrFamily::Inet - } else { - RtAddrFamily::Inet6 - } +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +struct ifinfomsg { + ifi_family: c_uchar, // AF_UNSPEC + ifi_type: c_ushort, // Device type + ifi_index: c_int, // Interface index + ifi_flags: c_uint, // Device flags + ifi_change: c_uint, // change mask } +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +struct rtmsg { + rtm_family: c_uchar, // Address family of route + rtm_dst_len: c_uchar, // Length of destination + rtm_src_len: c_uchar, // Length of source + rtm_tos: c_uchar, // TOS filter + rtm_table: c_uchar, // Routing table ID; see RTA_TABLE below + rtm_protocol: c_uchar, // Routing protocol; see below + rtm_scope: c_uchar, // See below + rtm_type: c_uchar, // See below + rtm_flags: c_uint, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +struct rtattr { + rta_len: c_ushort, // Length of option + rta_type: c_ushort, // Type of option +} // Data follows + const fn addr_len(remote: &IpAddr) -> u8 { - if remote.is_ipv4() { - 32 - } else { - 128 + match remote { + IpAddr::V4(_) => 32, + IpAddr::V6(_) => 128, } } @@ -47,97 +61,258 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } } -pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { - // Create a netlink socket. - let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[])?; +#[allow(clippy::too_many_lines)] +fn if_index(remote: IpAddr, fd: i32) -> Result { + // Prepare RTM_GETROUTE message. + let nl_msglen = mem::size_of::() + + mem::size_of::() + + mem::size_of::() + + addr_bytes(&remote).len(); + let nl_hdr = nlmsghdr { + nlmsg_len: nl_msglen.try_into().map_err(|_| default_err())?, + nlmsg_type: RTM_GETROUTE, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) + .try_into() + .map_err(|_| default_err())?, + nlmsg_seq: 0, + nlmsg_pid: 0, + }; - // Send RTM_GETROUTE message to retrieve the route for the destination IP. - let mut route_msg = Rtmsg { - rtm_family: family(&remote), + let rt_msg = rtmsg { + rtm_family: match remote { + IpAddr::V4(_) => AF_INET.try_into().map_err(|_| default_err())?, + IpAddr::V6(_) => AF_INET6.try_into().map_err(|_| default_err())?, + }, rtm_dst_len: addr_len(&remote), rtm_src_len: 0, rtm_tos: 0, - rtm_table: RtTable::Default, - rtm_protocol: Rtprot::Unspec, - rtm_scope: RtScope::Universe, - rtm_type: Rtn::Unicast, - rtm_flags: RtmFFlags::empty(), - rtattrs: RtBuffer::new(), + rtm_table: RT_TABLE_MAIN, + rtm_protocol: 0, + rtm_scope: RT_SCOPE_UNIVERSE, + rtm_type: RTN_UNICAST, + rtm_flags: 0, }; - let rta = Rtattr::new(None, Rta::Dst, addr_bytes(&remote)).map_err(|_| default_err())?; - route_msg.rtattrs.push(rta); - let route_request = Nlmsghdr::new( - None, - Rtm::Getroute, - NlmFFlags::new(&[NlmF::Request, NlmF::Ack]), - None, - None, - NlPayload::Payload(route_msg), - ); - socket.send(route_request).or(Err(default_err()))?; - - // Receive all response to RTM_GETROUTE. (If we don't consume all responses, it seems like we - // cannot reuse it for another request.) - let mut ifindex = None; - for response in socket.iter(false) { - let header: Nlmsghdr = response.map_err(|_| default_err())?; - if header.nl_type != Rtm::Newroute { - continue; + + let rt_attr = rtattr { + rta_len: (mem::size_of::() + addr_bytes(&remote).len()) + .try_into() + .map_err(|_| default_err())?, + rta_type: RTA_DST, + }; + + let mut buffer = vec![0u8; nl_msglen]; + unsafe { + ptr::copy_nonoverlapping( + ptr::from_ref(&nl_hdr).cast(), + buffer.as_mut_ptr(), + mem::size_of::(), + ); + ptr::copy_nonoverlapping( + ptr::from_ref(&rt_msg).cast(), + buffer.as_mut_ptr().add(mem::size_of::()), + mem::size_of::(), + ); + ptr::copy_nonoverlapping( + ptr::from_ref(&rt_attr).cast(), + buffer + .as_mut_ptr() + .add(mem::size_of::() + mem::size_of::()), + mem::size_of::(), + ); + ptr::copy_nonoverlapping( + addr_bytes(&remote).as_ptr(), + buffer.as_mut_ptr().add( + mem::size_of::() + mem::size_of::() + mem::size_of::(), + ), + addr_bytes(&remote).len(), + ); + }; + + // Send RTM_GETROUTE message to get the interfce index associated with the destination. + if unsafe { write(fd, buffer.as_ptr().cast(), buffer.len()) } < 0 { + let err = Error::last_os_error(); + unsafe { close(fd) }; + return Err(err); + } + + // Receive RTM_GETROUTE response. + loop { + let mut recv_buf = vec![0u8; 4096]; + let recv_len = unsafe { + recv( + fd, + recv_buf.as_mut_ptr().cast(), + recv_buf.len(), + MSG_DONTWAIT, + ) + }; + if recv_len <= 0 { + return Err(Error::last_os_error()); } - let route_attrs = &header.get_payload().map_err(|_| default_err())?.rtattrs; - for attr in route_attrs.iter() { - if let Ok(index) = attr.get_payload_as::() { - if attr.rta_type == Rta::Oif { - ifindex = Some(index); - break; + + let mut offset = 0; + while offset < recv_len.try_into().map_err(|_| default_err())? { + let hdr = + unsafe { ptr::read_unaligned(recv_buf.as_ptr().add(offset).cast::()) }; + if hdr.nlmsg_seq == 0 && hdr.nlmsg_type == RTM_NEWROUTE { + let mut attr_ptr = unsafe { + recv_buf + .as_ptr() + .add(offset + mem::size_of::() + mem::size_of::()) + }; + let attr_end = unsafe { recv_buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; + while attr_ptr < attr_end { + let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; + if attr.rta_type == RTA_OIF { + let idx = unsafe { + ptr::read_unaligned( + attr_ptr.add(mem::size_of::()).cast::(), + ) + }; + return Ok(idx); + } + attr_ptr = unsafe { attr_ptr.add(attr.rta_len as usize) }; } } + offset += hdr.nlmsg_len as usize; } } - let ifindex = ifindex.ok_or_else(default_err)?; - - // Send RTM_GETLINK message to retrieve the interface details. - let link_msg = Ifinfomsg::new( - family(&remote), - Arphrd::None, - ifindex as libc::c_int, - IffFlags::empty(), - IffFlags::empty(), - RtBuffer::new(), - ); - let link_request = Nlmsghdr::new( - None, - Rtm::Getlink, - NlmFFlags::new(&[NlmF::Request, NlmF::Ack]), - None, - None, - NlPayload::Payload(link_msg), - ); - socket.send(link_request).or(Err(default_err()))?; - - // Receive the responses to RTM_GETLINK. +} + +#[allow(clippy::too_many_lines)] +fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { + // Prepare RTM_GETLINK message to get the interface name and MTU for the interface with the + // obtained index. + + let nl_msglen = mem::size_of::() + mem::size_of::(); + let nl_hdr = nlmsghdr { + nlmsg_len: nl_msglen.try_into().map_err(|_| default_err())?, + nlmsg_type: RTM_GETLINK, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) + .try_into() + .map_err(|_| default_err())?, + nlmsg_seq: 1, + nlmsg_pid: 0, + }; + + let if_info_msg = ifinfomsg { + ifi_family: AF_UNSPEC.try_into().map_err(|_| default_err())?, + ifi_type: ARPHRD_NONE, + ifi_index: if_index, + ifi_flags: 0, + ifi_change: 0, + }; + + let mut buffer = vec![0u8; nl_msglen]; + unsafe { + ptr::copy_nonoverlapping( + ptr::from_ref(&nl_hdr).cast(), + buffer.as_mut_ptr(), + mem::size_of::(), + ); + ptr::copy_nonoverlapping( + std::ptr::from_ref(&if_info_msg).cast(), + buffer.as_mut_ptr().add(mem::size_of::()), + mem::size_of::(), + ); + } + + // Send RTM_GETLINK message. + if unsafe { write(fd, buffer.as_ptr().cast(), buffer.len()) } < 0 { + let err = Error::last_os_error(); + unsafe { close(fd) }; + return Err(err); + } + + // Receive RTM_GETLINK response. let mut ifname = None; let mut mtu = None; - for response in socket.iter(false) { - let header: Nlmsghdr = response.map_err(|_| default_err())?; - if header.nl_type != Rtm::Newlink { - continue; + 'recv: loop { + let mut recv_buf = vec![0u8; 4096]; + let recv_len = unsafe { + recv( + fd, + recv_buf.as_mut_ptr().cast(), + recv_buf.len(), + MSG_DONTWAIT, + ) + }; + if recv_len <= 0 { + return Err(Error::last_os_error()); } - let link_attrs = &header.get_payload().map_err(|_| default_err())?.rtattrs; - for attr in link_attrs.iter() { - if attr.rta_type == Ifla::Ifname { - if let Ok(name) = attr.get_payload_as_with_len::() { - ifname = Some(name); + + let mut offset = 0; + while offset < recv_len.try_into().map_err(|_| default_err())? { + let hdr = + unsafe { ptr::read_unaligned(recv_buf.as_ptr().add(offset).cast::()) }; + if hdr.nlmsg_seq == 1 && hdr.nlmsg_type == RTM_NEWLINK { + let mut attr_ptr = unsafe { + recv_buf + .as_ptr() + .add(offset + mem::size_of::() + mem::size_of::()) + }; + let attr_end = unsafe { recv_buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; + + while attr_ptr < attr_end { + let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; + if attr.rta_type == IFLA_IFNAME { + let name = unsafe { + slice::from_raw_parts( + attr_ptr.add(mem::size_of::()), + attr.rta_len as usize - mem::size_of::() - 1, + ) + }; + if let Ok(name) = str::from_utf8(name) { + // We have our interface name. + ifname = Some(name.to_string()); + } + } else if attr.rta_type == IFLA_MTU { + mtu = Some( + unsafe { + ptr::read_unaligned( + attr_ptr.add(mem::size_of::()).cast::(), + ) + } + .try_into() + .map_err(|_| default_err())?, + ); + } + + // Advance to the next address. The length is always a multiple of 4, so we need + // to do some awkward manipulation. + let incr = if attr.rta_len == 0 { + 4 + } else { + ((attr.rta_len - 1) | 3) + 1 + }; + // sa = unsafe { sa.add(incr as usize) }; + + attr_ptr = unsafe { attr_ptr.add(incr as usize) }; } - } else if attr.rta_type == Ifla::Mtu { - if let Ok(mtu_val) = attr.get_payload_as::() { - mtu = Some(mtu_val as usize); + if ifname.is_some() && mtu.is_some() { + break 'recv; } } + offset += hdr.nlmsg_len as usize; } } - let ifname = ifname.ok_or_else(default_err)?; - let mtu = mtu.ok_or_else(default_err)?; + + let ifname = + ifname.ok_or_else(|| Error::new(std::io::ErrorKind::Other, "Interface name not found"))?; + let mtu = mtu.ok_or_else(|| Error::new(std::io::ErrorKind::Other, "MTU not found"))?; Ok((ifname, mtu)) } + +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + // Create a netlink socket. + let fd = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) }; + if fd < 0 { + return Err(Error::last_os_error()); + } + + let if_index = if_index(remote, fd)?; + let res = if_name_mtu(if_index, fd); + unsafe { close(fd) }; + res +} From 774d6ffaff0635ba644f8278a3dba5dc494a65d3 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 18:12:51 +0300 Subject: [PATCH 021/128] Cleanups --- src/bsd.rs | 10 ++-------- src/lib.rs | 9 +++++++++ src/linux.rs | 47 +++++++++++++++++------------------------------ 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 58501ee8..e57162fd 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -18,7 +18,7 @@ use libc::{ RTM_VERSION, SOCK_RAW, }; -use crate::default_err; +use crate::{default_err, next_item_aligned_by_four}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. @@ -124,13 +124,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } } - // Advance to the next address. The length is always a multiple of 4, so we need to do - // some awkward manipulation. - let incr = if sdl.sdl_len == 0 { - 4 - } else { - ((sdl.sdl_len - 1) | 3) + 1 - }; + let incr = next_item_aligned_by_four(sdl.sdl_len as usize); sa = unsafe { sa.add(incr as usize) }; } } diff --git a/src/lib.rs b/src/lib.rs index f3191481..b4b4d74c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,15 @@ fn default_err() -> Error { Error::new(ErrorKind::NotFound, "Local interface MTU not found") } +/// Align a size to the next multiple of four. +const fn next_item_aligned_by_four(size: usize) -> usize { + if size == 0 { + 4 + } else { + (size + 3) & !3 + } +} + /// Return the name and maximum transmission unit (MTU) of the outgoing network interface towards a /// remote destination identified by an [`IpAddr`], /// diff --git a/src/linux.rs b/src/linux.rs index cc47b3fa..f130e7a0 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -14,7 +14,7 @@ use libc::{ RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; -use crate::default_err; +use crate::{default_err, next_item_aligned_by_four}; #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] @@ -61,22 +61,26 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } } -#[allow(clippy::too_many_lines)] +fn prepare_nlmsg(nl_type: c_ushort, nl_len: usize, nl_seq: u32) -> nlmsghdr { + nlmsghdr { + nlmsg_len: nl_len.try_into().map_err(|_| default_err()).unwrap(), + nlmsg_type: nl_type, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) + .try_into() + .map_err(|_| default_err()) + .unwrap(), + nlmsg_seq: nl_seq, + nlmsg_pid: 0, + } +} + fn if_index(remote: IpAddr, fd: i32) -> Result { // Prepare RTM_GETROUTE message. let nl_msglen = mem::size_of::() + mem::size_of::() + mem::size_of::() + addr_bytes(&remote).len(); - let nl_hdr = nlmsghdr { - nlmsg_len: nl_msglen.try_into().map_err(|_| default_err())?, - nlmsg_type: RTM_GETROUTE, - nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) - .try_into() - .map_err(|_| default_err())?, - nlmsg_seq: 0, - nlmsg_pid: 0, - }; + let nl_hdr = prepare_nlmsg(RTM_GETROUTE, nl_msglen, 0); let rt_msg = rtmsg { rtm_family: match remote { @@ -179,21 +183,12 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { } } -#[allow(clippy::too_many_lines)] fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { // Prepare RTM_GETLINK message to get the interface name and MTU for the interface with the // obtained index. let nl_msglen = mem::size_of::() + mem::size_of::(); - let nl_hdr = nlmsghdr { - nlmsg_len: nl_msglen.try_into().map_err(|_| default_err())?, - nlmsg_type: RTM_GETLINK, - nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) - .try_into() - .map_err(|_| default_err())?, - nlmsg_seq: 1, - nlmsg_pid: 0, - }; + let nl_hdr = prepare_nlmsg(RTM_GETLINK, nl_msglen, 1); let if_info_msg = ifinfomsg { ifi_family: AF_UNSPEC.try_into().map_err(|_| default_err())?, @@ -278,15 +273,7 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { ); } - // Advance to the next address. The length is always a multiple of 4, so we need - // to do some awkward manipulation. - let incr = if attr.rta_len == 0 { - 4 - } else { - ((attr.rta_len - 1) | 3) + 1 - }; - // sa = unsafe { sa.add(incr as usize) }; - + let incr = next_item_aligned_by_four(attr.rta_len as usize); attr_ptr = unsafe { attr_ptr.add(incr as usize) }; } if ifname.is_some() && mtu.is_some() { From 856d12b1355d3a0400ece158ab74a830a1af2b43 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 18:13:40 +0300 Subject: [PATCH 022/128] Nit --- .clippy.toml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .clippy.toml diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index 4b59b71b..00000000 --- a/.clippy.toml +++ /dev/null @@ -1,2 +0,0 @@ -# TODO: Remove this after `neli-proc-macros` starts using `syn@2`. -allowed-duplicate-crates = ["syn"] From 2cfa562f47123ac1df1803bf6f3476fe9fdecc6a Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 18:21:34 +0300 Subject: [PATCH 023/128] Fixes --- src/bsd.rs | 2 +- src/lib.rs | 1 + src/linux.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index e57162fd..53f081b9 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -124,7 +124,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } } - let incr = next_item_aligned_by_four(sdl.sdl_len as usize); + let incr = next_item_aligned_by_four(sdl.sdl_len); sa = unsafe { sa.add(incr as usize) }; } } diff --git a/src/lib.rs b/src/lib.rs index b4b4d74c..54b68a2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ fn default_err() -> Error { } /// Align a size to the next multiple of four. +#[cfg(not(target_os = "windows"))] const fn next_item_aligned_by_four(size: usize) -> usize { if size == 0 { 4 diff --git a/src/linux.rs b/src/linux.rs index f130e7a0..d7dd0beb 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -274,7 +274,7 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { } let incr = next_item_aligned_by_four(attr.rta_len as usize); - attr_ptr = unsafe { attr_ptr.add(incr as usize) }; + attr_ptr = unsafe { attr_ptr.add(incr) }; } if ifname.is_some() && mtu.is_some() { break 'recv; From c5f0d1f3c85ed39e8623fb2c5ae31b6e72da0d1f Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 18:23:28 +0300 Subject: [PATCH 024/128] Fix --- src/bsd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bsd.rs b/src/bsd.rs index 53f081b9..cd09c3a9 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -124,7 +124,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } } - let incr = next_item_aligned_by_four(sdl.sdl_len); + let incr = next_item_aligned_by_four(sdl.sdl_len.into()); sa = unsafe { sa.add(incr as usize) }; } } From 62de28c01fee0b9be833806e1a05f8792fd78af0 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 18:25:49 +0300 Subject: [PATCH 025/128] Fix --- src/bsd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bsd.rs b/src/bsd.rs index cd09c3a9..47a7cc6f 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -125,7 +125,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } } let incr = next_item_aligned_by_four(sdl.sdl_len.into()); - sa = unsafe { sa.add(incr as usize) }; + sa = unsafe { sa.add(incr) }; } } From 4fda8552e8d1382b31235de835c36f8fa7cadfd4 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 7 Oct 2024 18:38:14 +0300 Subject: [PATCH 026/128] Simplify --- src/bsd.rs | 11 ++++--- src/linux.rs | 83 ++++++++++++++++++++-------------------------------- 2 files changed, 36 insertions(+), 58 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 47a7cc6f..fad269d9 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -5,7 +5,6 @@ // except according to those terms. use std::{ - ffi::c_void, io::Error, mem::{self, size_of}, net::IpAddr, @@ -62,19 +61,19 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let mut msg: Vec = vec![0; rtm.rtm_msglen as usize]; unsafe { ptr::copy_nonoverlapping( - ptr::from_ref::(&rtm).cast::(), + ptr::from_ref::(&rtm).cast(), msg.as_mut_ptr(), size_of::(), ); ptr::copy_nonoverlapping( - ptr::from_ref::(&dst).cast::(), + ptr::from_ref::(&dst).cast(), msg.as_mut_ptr().add(size_of::()), dst.ss_len as usize, ); } // Send route message. - let res = unsafe { write(fd, msg.as_ptr().cast::(), msg.len()) }; + let res = unsafe { write(fd, msg.as_ptr().cast(), msg.len()) }; if res == -1 { let err = Error::last_os_error(); unsafe { close(fd) }; @@ -89,7 +88,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { - let len = unsafe { read(fd, buf.as_mut_ptr().cast::(), buf.len()) }; + let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { let err = Error::last_os_error(); unsafe { close(fd) }; @@ -117,7 +116,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Check if the address is the interface address if i == RTAX_IFP { let name = unsafe { - slice::from_raw_parts(sdl.sdl_data.as_ptr().cast::(), sdl.sdl_nlen as usize) + slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen as usize) }; if let Ok(name) = str::from_utf8(name) { // We have our interface name. diff --git a/src/linux.rs b/src/linux.rs index d7dd0beb..98fc922e 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -8,10 +8,10 @@ use core::str; use std::{io::Error, mem, net::IpAddr, ptr, slice}; use libc::{ - c_int, c_uchar, c_uint, c_ushort, close, nlmsghdr, recv, socket, write, AF_INET, AF_INET6, - AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, MSG_DONTWAIT, NETLINK_ROUTE, - NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, - RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, + c_int, c_uchar, c_uint, c_ushort, close, nlmsghdr, read, socket, write, AF_INET, AF_INET6, + AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLM_F_ACK, + NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, + RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; use crate::{default_err, next_item_aligned_by_four}; @@ -104,28 +104,27 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { rta_type: RTA_DST, }; - let mut buffer = vec![0u8; nl_msglen]; + let mut buf = vec![0u8; nl_msglen]; unsafe { ptr::copy_nonoverlapping( ptr::from_ref(&nl_hdr).cast(), - buffer.as_mut_ptr(), + buf.as_mut_ptr(), mem::size_of::(), ); ptr::copy_nonoverlapping( ptr::from_ref(&rt_msg).cast(), - buffer.as_mut_ptr().add(mem::size_of::()), + buf.as_mut_ptr().add(mem::size_of::()), mem::size_of::(), ); ptr::copy_nonoverlapping( ptr::from_ref(&rt_attr).cast(), - buffer - .as_mut_ptr() + buf.as_mut_ptr() .add(mem::size_of::() + mem::size_of::()), mem::size_of::(), ); ptr::copy_nonoverlapping( addr_bytes(&remote).as_ptr(), - buffer.as_mut_ptr().add( + buf.as_mut_ptr().add( mem::size_of::() + mem::size_of::() + mem::size_of::(), ), addr_bytes(&remote).len(), @@ -133,7 +132,7 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { }; // Send RTM_GETROUTE message to get the interfce index associated with the destination. - if unsafe { write(fd, buffer.as_ptr().cast(), buffer.len()) } < 0 { + if unsafe { write(fd, buf.as_ptr().cast(), buf.len()) } < 0 { let err = Error::last_os_error(); unsafe { close(fd) }; return Err(err); @@ -141,37 +140,26 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { // Receive RTM_GETROUTE response. loop { - let mut recv_buf = vec![0u8; 4096]; - let recv_len = unsafe { - recv( - fd, - recv_buf.as_mut_ptr().cast(), - recv_buf.len(), - MSG_DONTWAIT, - ) - }; - if recv_len <= 0 { + let mut buf = vec![0u8; 4096]; + let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + if len < 0 { return Err(Error::last_os_error()); } let mut offset = 0; - while offset < recv_len.try_into().map_err(|_| default_err())? { - let hdr = - unsafe { ptr::read_unaligned(recv_buf.as_ptr().add(offset).cast::()) }; + while offset < len.try_into().map_err(|_| default_err())? { + let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == 0 && hdr.nlmsg_type == RTM_NEWROUTE { let mut attr_ptr = unsafe { - recv_buf - .as_ptr() + buf.as_ptr() .add(offset + mem::size_of::() + mem::size_of::()) }; - let attr_end = unsafe { recv_buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; + let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; while attr_ptr < attr_end { let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; if attr.rta_type == RTA_OIF { let idx = unsafe { - ptr::read_unaligned( - attr_ptr.add(mem::size_of::()).cast::(), - ) + ptr::read_unaligned(attr_ptr.add(mem::size_of::()).cast()) }; return Ok(idx); } @@ -198,22 +186,22 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { ifi_change: 0, }; - let mut buffer = vec![0u8; nl_msglen]; + let mut buf = vec![0u8; nl_msglen]; unsafe { ptr::copy_nonoverlapping( ptr::from_ref(&nl_hdr).cast(), - buffer.as_mut_ptr(), + buf.as_mut_ptr(), mem::size_of::(), ); ptr::copy_nonoverlapping( std::ptr::from_ref(&if_info_msg).cast(), - buffer.as_mut_ptr().add(mem::size_of::()), + buf.as_mut_ptr().add(mem::size_of::()), mem::size_of::(), ); } // Send RTM_GETLINK message. - if unsafe { write(fd, buffer.as_ptr().cast(), buffer.len()) } < 0 { + if unsafe { write(fd, buf.as_ptr().cast(), buf.len()) } < 0 { let err = Error::last_os_error(); unsafe { close(fd) }; return Err(err); @@ -223,30 +211,21 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { let mut ifname = None; let mut mtu = None; 'recv: loop { - let mut recv_buf = vec![0u8; 4096]; - let recv_len = unsafe { - recv( - fd, - recv_buf.as_mut_ptr().cast(), - recv_buf.len(), - MSG_DONTWAIT, - ) - }; - if recv_len <= 0 { + let mut buf = vec![0u8; 4096]; + let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + if len < 0 { return Err(Error::last_os_error()); } let mut offset = 0; - while offset < recv_len.try_into().map_err(|_| default_err())? { - let hdr = - unsafe { ptr::read_unaligned(recv_buf.as_ptr().add(offset).cast::()) }; + while offset < len.try_into().map_err(|_| default_err())? { + let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == 1 && hdr.nlmsg_type == RTM_NEWLINK { let mut attr_ptr = unsafe { - recv_buf - .as_ptr() + buf.as_ptr() .add(offset + mem::size_of::() + mem::size_of::()) }; - let attr_end = unsafe { recv_buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; + let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; while attr_ptr < attr_end { let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; @@ -284,11 +263,11 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { } } - let ifname = + let name = ifname.ok_or_else(|| Error::new(std::io::ErrorKind::Other, "Interface name not found"))?; let mtu = mtu.ok_or_else(|| Error::new(std::io::ErrorKind::Other, "MTU not found"))?; - Ok((ifname, mtu)) + Ok((name, mtu)) } pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { From f3336c50e691d468049e9b1548c9d9dfeb139439 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 8 Oct 2024 09:03:48 +0300 Subject: [PATCH 027/128] Also install llvm-symbolizer for the sanitizer runs --- .github/workflows/check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4b0a1288..6cd835c1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -105,9 +105,13 @@ jobs: RUST_LOG: trace run: | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + sudo apt-get update + sudo apt-get install -y --no-install-recommends llvm TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then + brew update + brew install llvm TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" From a0816e6c21033fbeaa2318b90f742c7bbed4cedd Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 8 Oct 2024 09:40:07 +0300 Subject: [PATCH 028/128] Fixes --- .github/workflows/check.yml | 3 +- src/bsd.rs | 4 +- src/linux.rs | 85 +++++++++++++++++-------------------- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6cd835c1..eb13c4bb 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -110,8 +110,7 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - brew update - brew install llvm + # llvm-symbolizer (as part of llvm) is installed by default on macOS runners TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" diff --git a/src/bsd.rs b/src/bsd.rs index fad269d9..a23884ba 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -61,12 +61,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let mut msg: Vec = vec![0; rtm.rtm_msglen as usize]; unsafe { ptr::copy_nonoverlapping( - ptr::from_ref::(&rtm).cast(), + ptr::from_ref(&rtm).cast(), msg.as_mut_ptr(), size_of::(), ); ptr::copy_nonoverlapping( - ptr::from_ref::(&dst).cast(), + ptr::from_ref(&dst).cast(), msg.as_mut_ptr().add(size_of::()), dst.ss_len as usize, ); diff --git a/src/linux.rs b/src/linux.rs index 98fc922e..6349b40a 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -5,7 +5,12 @@ // except according to those terms. use core::str; -use std::{io::Error, mem, net::IpAddr, ptr, slice}; +use std::{ + io::{Error, ErrorKind}, + mem, + net::IpAddr, + ptr, slice, +}; use libc::{ c_int, c_uchar, c_uint, c_ushort, close, nlmsghdr, read, socket, write, AF_INET, AF_INET6, @@ -61,17 +66,15 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } } -fn prepare_nlmsg(nl_type: c_ushort, nl_len: usize, nl_seq: u32) -> nlmsghdr { - nlmsghdr { - nlmsg_len: nl_len.try_into().map_err(|_| default_err()).unwrap(), - nlmsg_type: nl_type, - nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) - .try_into() - .map_err(|_| default_err()) - .unwrap(), - nlmsg_seq: nl_seq, - nlmsg_pid: 0, - } +fn prepare_nlmsg(nl_type: c_ushort, nl_len: usize, nl_seq: u32) -> Result { + let mut nlm = unsafe { mem::zeroed::() }; + nlm.nlmsg_len = nl_len.try_into().map_err(|_| default_err())?; + nlm.nlmsg_type = nl_type; + nlm.nlmsg_flags = (NLM_F_REQUEST | NLM_F_ACK) + .try_into() + .map_err(|_| default_err())?; + nlm.nlmsg_seq = nl_seq; + Ok(nlm) } fn if_index(remote: IpAddr, fd: i32) -> Result { @@ -80,29 +83,23 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { + mem::size_of::() + mem::size_of::() + addr_bytes(&remote).len(); - let nl_hdr = prepare_nlmsg(RTM_GETROUTE, nl_msglen, 0); + let nl_hdr = prepare_nlmsg(RTM_GETROUTE, nl_msglen, 0)?; - let rt_msg = rtmsg { - rtm_family: match remote { - IpAddr::V4(_) => AF_INET.try_into().map_err(|_| default_err())?, - IpAddr::V6(_) => AF_INET6.try_into().map_err(|_| default_err())?, - }, - rtm_dst_len: addr_len(&remote), - rtm_src_len: 0, - rtm_tos: 0, - rtm_table: RT_TABLE_MAIN, - rtm_protocol: 0, - rtm_scope: RT_SCOPE_UNIVERSE, - rtm_type: RTN_UNICAST, - rtm_flags: 0, + let mut rtm = unsafe { mem::zeroed::() }; + rtm.rtm_family = match remote { + IpAddr::V4(_) => AF_INET.try_into().map_err(|_| default_err())?, + IpAddr::V6(_) => AF_INET6.try_into().map_err(|_| default_err())?, }; + rtm.rtm_dst_len = addr_len(&remote); + rtm.rtm_table = RT_TABLE_MAIN; + rtm.rtm_scope = RT_SCOPE_UNIVERSE; + rtm.rtm_type = RTN_UNICAST; - let rt_attr = rtattr { - rta_len: (mem::size_of::() + addr_bytes(&remote).len()) - .try_into() - .map_err(|_| default_err())?, - rta_type: RTA_DST, - }; + let mut rta = unsafe { mem::zeroed::() }; + rta.rta_len = (mem::size_of::() + addr_bytes(&remote).len()) + .try_into() + .map_err(|_| default_err())?; + rta.rta_type = RTA_DST; let mut buf = vec![0u8; nl_msglen]; unsafe { @@ -112,12 +109,12 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { mem::size_of::(), ); ptr::copy_nonoverlapping( - ptr::from_ref(&rt_msg).cast(), + ptr::from_ref(&rtm).cast(), buf.as_mut_ptr().add(mem::size_of::()), mem::size_of::(), ); ptr::copy_nonoverlapping( - ptr::from_ref(&rt_attr).cast(), + ptr::from_ref(&rta).cast(), buf.as_mut_ptr() .add(mem::size_of::() + mem::size_of::()), mem::size_of::(), @@ -176,15 +173,12 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { // obtained index. let nl_msglen = mem::size_of::() + mem::size_of::(); - let nl_hdr = prepare_nlmsg(RTM_GETLINK, nl_msglen, 1); + let nl_hdr = prepare_nlmsg(RTM_GETLINK, nl_msglen, 1)?; - let if_info_msg = ifinfomsg { - ifi_family: AF_UNSPEC.try_into().map_err(|_| default_err())?, - ifi_type: ARPHRD_NONE, - ifi_index: if_index, - ifi_flags: 0, - ifi_change: 0, - }; + let mut ifim: ifinfomsg = unsafe { mem::zeroed() }; + ifim.ifi_family = AF_UNSPEC.try_into().map_err(|_| default_err())?; + ifim.ifi_type = ARPHRD_NONE; + ifim.ifi_index = if_index; let mut buf = vec![0u8; nl_msglen]; unsafe { @@ -194,7 +188,7 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { mem::size_of::(), ); ptr::copy_nonoverlapping( - std::ptr::from_ref(&if_info_msg).cast(), + ptr::from_ref(&ifim).cast(), buf.as_mut_ptr().add(mem::size_of::()), mem::size_of::(), ); @@ -263,9 +257,8 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { } } - let name = - ifname.ok_or_else(|| Error::new(std::io::ErrorKind::Other, "Interface name not found"))?; - let mtu = mtu.ok_or_else(|| Error::new(std::io::ErrorKind::Other, "MTU not found"))?; + let name = ifname.ok_or_else(|| Error::new(ErrorKind::Other, "Interface name not found"))?; + let mtu = mtu.ok_or_else(|| Error::new(ErrorKind::Other, "MTU not found"))?; Ok((name, mtu)) } From 8fbcbfbb7b553e240f3352423dfce31d9b7dca1e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 8 Oct 2024 13:19:18 +0300 Subject: [PATCH 029/128] Fixes --- src/linux.rs | 74 +++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 6349b40a..c1b7c1c6 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -4,12 +4,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::str; use std::{ + ffi::CStr, io::{Error, ErrorKind}, mem, net::IpAddr, - ptr, slice, + ptr, }; use libc::{ @@ -21,6 +21,8 @@ use libc::{ use crate::{default_err, next_item_aligned_by_four}; +const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. + #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] struct ifinfomsg { @@ -66,24 +68,29 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } } -fn prepare_nlmsg(nl_type: c_ushort, nl_len: usize, nl_seq: u32) -> Result { +fn prepare_nlmsg( + nlmsg_type: c_ushort, + nlmsg_len: usize, + nlmsg_seq: u32, +) -> Result { let mut nlm = unsafe { mem::zeroed::() }; - nlm.nlmsg_len = nl_len.try_into().map_err(|_| default_err())?; - nlm.nlmsg_type = nl_type; + nlm.nlmsg_len = nlmsg_len.try_into().map_err(|_| default_err())?; + nlm.nlmsg_type = nlmsg_type; nlm.nlmsg_flags = (NLM_F_REQUEST | NLM_F_ACK) .try_into() .map_err(|_| default_err())?; - nlm.nlmsg_seq = nl_seq; + nlm.nlmsg_seq = nlmsg_seq; Ok(nlm) } fn if_index(remote: IpAddr, fd: i32) -> Result { // Prepare RTM_GETROUTE message. - let nl_msglen = mem::size_of::() + let nlmsg_len = mem::size_of::() + mem::size_of::() + mem::size_of::() + addr_bytes(&remote).len(); - let nl_hdr = prepare_nlmsg(RTM_GETROUTE, nl_msglen, 0)?; + let nlmsg_seq = 1; + let hdr = prepare_nlmsg(RTM_GETROUTE, nlmsg_len, nlmsg_seq)?; let mut rtm = unsafe { mem::zeroed::() }; rtm.rtm_family = match remote { @@ -95,16 +102,16 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { rtm.rtm_scope = RT_SCOPE_UNIVERSE; rtm.rtm_type = RTN_UNICAST; - let mut rta = unsafe { mem::zeroed::() }; - rta.rta_len = (mem::size_of::() + addr_bytes(&remote).len()) + let mut attr = unsafe { mem::zeroed::() }; + attr.rta_len = (mem::size_of::() + addr_bytes(&remote).len()) .try_into() .map_err(|_| default_err())?; - rta.rta_type = RTA_DST; + attr.rta_type = RTA_DST; - let mut buf = vec![0u8; nl_msglen]; + let mut buf = vec![0u8; nlmsg_len]; unsafe { ptr::copy_nonoverlapping( - ptr::from_ref(&nl_hdr).cast(), + ptr::from_ref(&hdr).cast(), buf.as_mut_ptr(), mem::size_of::(), ); @@ -114,7 +121,7 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { mem::size_of::(), ); ptr::copy_nonoverlapping( - ptr::from_ref(&rta).cast(), + ptr::from_ref(&attr).cast(), buf.as_mut_ptr() .add(mem::size_of::() + mem::size_of::()), mem::size_of::(), @@ -137,7 +144,7 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { // Receive RTM_GETROUTE response. loop { - let mut buf = vec![0u8; 4096]; + let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; if len < 0 { return Err(Error::last_os_error()); @@ -146,7 +153,8 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { let mut offset = 0; while offset < len.try_into().map_err(|_| default_err())? { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; - if hdr.nlmsg_seq == 0 && hdr.nlmsg_type == RTM_NEWROUTE { + if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWROUTE { + // This is the response, parse through the attributes to find the interface index. let mut attr_ptr = unsafe { buf.as_ptr() .add(offset + mem::size_of::() + mem::size_of::()) @@ -155,6 +163,7 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { while attr_ptr < attr_end { let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; if attr.rta_type == RTA_OIF { + // We have our interface index. let idx = unsafe { ptr::read_unaligned(attr_ptr.add(mem::size_of::()).cast()) }; @@ -171,19 +180,19 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { // Prepare RTM_GETLINK message to get the interface name and MTU for the interface with the // obtained index. - - let nl_msglen = mem::size_of::() + mem::size_of::(); - let nl_hdr = prepare_nlmsg(RTM_GETLINK, nl_msglen, 1)?; + let nlmsg_len = mem::size_of::() + mem::size_of::(); + let nlmsg_seq = 2; + let hdr = prepare_nlmsg(RTM_GETLINK, nlmsg_len, nlmsg_seq)?; let mut ifim: ifinfomsg = unsafe { mem::zeroed() }; ifim.ifi_family = AF_UNSPEC.try_into().map_err(|_| default_err())?; ifim.ifi_type = ARPHRD_NONE; ifim.ifi_index = if_index; - let mut buf = vec![0u8; nl_msglen]; + let mut buf = vec![0u8; nlmsg_len]; unsafe { ptr::copy_nonoverlapping( - ptr::from_ref(&nl_hdr).cast(), + ptr::from_ref(&hdr).cast(), buf.as_mut_ptr(), mem::size_of::(), ); @@ -205,7 +214,7 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { let mut ifname = None; let mut mtu = None; 'recv: loop { - let mut buf = vec![0u8; 4096]; + let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; if len < 0 { return Err(Error::last_os_error()); @@ -214,23 +223,18 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { let mut offset = 0; while offset < len.try_into().map_err(|_| default_err())? { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; - if hdr.nlmsg_seq == 1 && hdr.nlmsg_type == RTM_NEWLINK { + if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWLINK { let mut attr_ptr = unsafe { buf.as_ptr() .add(offset + mem::size_of::() + mem::size_of::()) }; let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; - while attr_ptr < attr_end { let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; if attr.rta_type == IFLA_IFNAME { - let name = unsafe { - slice::from_raw_parts( - attr_ptr.add(mem::size_of::()), - attr.rta_len as usize - mem::size_of::() - 1, - ) - }; - if let Ok(name) = str::from_utf8(name) { + let name = + unsafe { CStr::from_ptr(attr_ptr.add(mem::size_of::())) }; + if let Ok(name) = name.to_str() { // We have our interface name. ifname = Some(name.to_string()); } @@ -245,13 +249,12 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { .map_err(|_| default_err())?, ); } - + if ifname.is_some() && mtu.is_some() { + break 'recv; + } let incr = next_item_aligned_by_four(attr.rta_len as usize); attr_ptr = unsafe { attr_ptr.add(incr) }; } - if ifname.is_some() && mtu.is_some() { - break 'recv; - } } offset += hdr.nlmsg_len as usize; } @@ -259,7 +262,6 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { let name = ifname.ok_or_else(|| Error::new(ErrorKind::Other, "Interface name not found"))?; let mtu = mtu.ok_or_else(|| Error::new(ErrorKind::Other, "MTU not found"))?; - Ok((name, mtu)) } From 87eb36c4965625da6ce83598925fae6e07cb58ca Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 8 Oct 2024 14:31:05 +0300 Subject: [PATCH 030/128] README --- README.md | 10 ++++++---- src/lib.rs | 19 ++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index bd09a436..6b3aa8b6 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,16 @@ towards a given destination `SocketAddr`, optionally from a given local `SocketA ## Usage -This crate exports a single function `interface_and_mtu` that returns the name and [maximum transmission unit (MTU)](https://en.wikipedia.org/wiki/Maximum_transmission_unit) of the outgoing network interface towards a remote destination identified by an `IpAddr`, +This crate exports a single function `interface_and_mtu` that returns the name and +[maximum transmission unit (MTU)](https://en.wikipedia.org/wiki/Maximum_transmission_unit) +of the outgoing network interface towards a remote destination identified by an `IpAddr`. ## Example ```rust -let remote = "127.0.0.1".parse().unwrap(); -let (name, mtu) = mtu::interface_and_mtu(remote).unwrap(); -println!("MTU towards {remote:?} is {mtu} on {name}"); +let destination = "127.0.0.1".parse().unwrap(); +let (name, mtu) = mtu::interface_and_mtu(destination).unwrap(); +println!("MTU towards {destination:?} is {mtu} on {name}"); ``` ## Supported Platforms diff --git a/src/lib.rs b/src/lib.rs index 4c28bf2b..977d2e2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,18 +9,16 @@ //! //! # Usage //! -//! This crate exports a single function `interface_and_mtu` that, given a pair of local and remote `SocketAddr`s, returns the name and [maximum transmission unit (MTU)](https://en.wikipedia.org/wiki/Maximum_transmission_unit) of the local network interface used by a socket bound to the local address and connected towards the remote destination. -//! -//! If the local address is `None`, the function will let the operating system choose the local -//! address based on the given remote address. If the remote address is `None`, the function will -//! return the name and MTU of the local network interface with the given local address. +//! This crate exports a single function `interface_and_mtu` that returns the name and +//! [maximum transmission unit (MTU)](https://en.wikipedia.org/wiki/Maximum_transmission_unit) +//! of the outgoing network interface towards a remote destination identified by an `IpAddr`. //! //! # Example //! //! ```rust -//! let saddr = "127.0.0.1:443".parse().unwrap(); -//! let (name, mtu) = mtu::interface_and_mtu(&(None, saddr)).unwrap(); -//! println!("MTU for {saddr:?} is {mtu} on {name}"); +//! let destination = "127.0.0.1".parse().unwrap(); +//! let (name, mtu) = mtu::interface_and_mtu(destination).unwrap(); +//! println!("MTU towards {destination:?} is {mtu} on {name}"); //! ``` //! //! # Supported Platforms @@ -28,9 +26,8 @@ //! * Linux //! * macOS //! * Windows -//! * FreeBSD -//! * NetBSD -//! * OpenBSD +//! +//! FreeBSD, NetBSD and OpenBSD support is waiting for [rust/libc#3714](https://github.com/rust-lang/libc/pull/3714). //! //! # Notes //! From 9a8b72d3eebf04cc5accbddbbef3e3603dcf943f Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 8 Oct 2024 15:11:16 +0300 Subject: [PATCH 031/128] clippy --- src/linux.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index c1b7c1c6..ca08c9e6 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -232,8 +232,9 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { while attr_ptr < attr_end { let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; if attr.rta_type == IFLA_IFNAME { - let name = - unsafe { CStr::from_ptr(attr_ptr.add(mem::size_of::())) }; + let name = unsafe { + CStr::from_ptr(attr_ptr.add(mem::size_of::()).cast()) + }; if let Ok(name) = name.to_str() { // We have our interface name. ifname = Some(name.to_string()); From 6aac56c5998339de027534ac86b9399853fa91c1 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 9 Oct 2024 12:57:24 +0300 Subject: [PATCH 032/128] Tweaks --- Cargo.toml | 3 +++ README.md | 7 +++---- build.rs | 29 +++++++++++++++++++++++++++++ src/lib.rs | 36 ++++++++---------------------------- 4 files changed, 43 insertions(+), 32 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 1dae034d..994b5cbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,9 @@ windows-targets = "0.52" [target."cfg(windows)".dev-dependencies] windows-bindgen = { version = "0.58" } # MSRV is 1.70 +[build-dependencies] +cfg_aliases = "0.2" + [lints.clippy] cargo = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } diff --git a/README.md b/README.md index 6b3aa8b6..451469e9 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,10 @@ This crate exports a single function `interface_and_mtu` that returns the name a of the outgoing network interface towards a remote destination identified by an `IpAddr`. ## Example - ```rust -let destination = "127.0.0.1".parse().unwrap(); -let (name, mtu) = mtu::interface_and_mtu(destination).unwrap(); -println!("MTU towards {destination:?} is {mtu} on {name}"); +let destination = IpAddr::V4(Ipv4Addr::LOCALHOST); +let (name, mtu): (String, usize) = mtu::interface_and_mtu(destination).unwrap(); +println!("MTU towards {destination} is {mtu} on {name}"); ``` ## Supported Platforms diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..30458642 --- /dev/null +++ b/build.rs @@ -0,0 +1,29 @@ +// 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 cfg_aliases::cfg_aliases; + +fn main() { + // Setup cfg aliases + cfg_aliases! { + // Platforms + apple: { + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "visionos" + ) + }, + bsd: { + any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd" + ) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 977d2e2a..636defd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,11 @@ //! of the outgoing network interface towards a remote destination identified by an `IpAddr`. //! //! # Example -//! -//! ```rust -//! let destination = "127.0.0.1".parse().unwrap(); -//! let (name, mtu) = mtu::interface_and_mtu(destination).unwrap(); -//! println!("MTU towards {destination:?} is {mtu} on {name}"); +//! ``` +//! # use std::net::{IpAddr, Ipv4Addr}; +//! let destination = IpAddr::V4(Ipv4Addr::LOCALHOST); +//! let (name, mtu): (String, usize) = mtu::interface_and_mtu(destination).unwrap(); +//! println!("MTU towards {destination} is {mtu} on {name}"); //! ``` //! //! # Supported Platforms @@ -46,26 +46,14 @@ use std::{ net::IpAddr, }; -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(any(apple, bsd))] use bsd::interface_and_mtu_impl; #[cfg(target_os = "linux")] use linux::interface_and_mtu_impl; #[cfg(target_os = "windows")] use windows::interface_and_mtu_impl; -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(any(apple, bsd))] mod bsd; #[cfg(target_os = "linux")] @@ -97,14 +85,6 @@ const fn next_item_aligned_by_four(size: usize) -> usize { /// /// The returned interface name is obtained from the operating system. /// -/// # Examples -/// -/// ``` -/// let remote = "127.0.0.1".parse().unwrap(); -/// let (name, mtu) = mtu::interface_and_mtu(remote).unwrap(); -/// println!("MTU towards {remote:?} is {mtu} on {name}"); -/// ``` -/// /// # Errors /// /// This function returns an error if the local interface MTU cannot be determined. @@ -130,7 +110,7 @@ mod test { } } - #[cfg(any(target_os = "macos", target_os = "freebsd",))] + #[cfg(any(apple, target_os = "freebsd",))] const LOOPBACK: NameMtu = NameMtu(Some("lo0"), 16_384); #[cfg(target_os = "linux")] const LOOPBACK: NameMtu = NameMtu(Some("lo"), 65_536); From a0428ca31e543633162efa6767ee0dfd32061a95 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 16:06:41 +0300 Subject: [PATCH 033/128] Misc improvements --- .github/workflows/check.yml | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3231b373..b49d31e0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -18,16 +18,34 @@ concurrency: permissions: contents: read +defaults: + run: + shell: bash + jobs: + toolchains: + runs-on: ubuntu-latest + outputs: + toolchains: ${{ steps.toolchains.outputs.toolchains }} + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + sparse-checkout: Cargo.toml + - id: toolchains + run: | + msrv="$(grep rust-version Cargo.toml | tr -d '"' | cut -f3 -d\ )" + echo "toolchains=[\"$msrv\", \"stable\", \"nightly\"]" >> "$GITHUB_OUTPUT" + check: + needs: toolchains strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - # Keep low end in sync with Cargo.toml - rust-toolchain: [1.76.0, stable, nightly] + rust-toolchain: ${{ fromJSON(needs.toolchains.outputs.toolchains) }} type: [debug] include: + # Also do some release builds on the latest OS versions. - os: ubuntu-latest rust-toolchain: stable type: release @@ -37,6 +55,7 @@ jobs: - os: windows-latest rust-toolchain: stable type: release + # Also do some debug builds on the oldest OS versions. - os: ubuntu-20.04 rust-toolchain: stable type: debug @@ -49,9 +68,6 @@ jobs: env: BUILD_TYPE: ${{ matrix.type == 'release' && '--release' || '' }} runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 @@ -103,6 +119,7 @@ jobs: if: (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest') && matrix.rust-toolchain == 'nightly' env: RUST_LOG: trace + ASAN_OPTIONS: detect_leaks=1 run: | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then sudo apt-get update @@ -126,9 +143,6 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 From cd3b4f36a21f402298737538f4f5808b93708d95 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 16:18:00 +0300 Subject: [PATCH 034/128] ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1 --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b49d31e0..81887c55 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -119,7 +119,7 @@ jobs: if: (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest') && matrix.rust-toolchain == 'nightly' env: RUST_LOG: trace - ASAN_OPTIONS: detect_leaks=1 + ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1 run: | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then sudo apt-get update From dab41d1673f5bc92ef4300ab20dd74ac3708f3d4 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 16:34:12 +0300 Subject: [PATCH 035/128] Suppress --- .github/workflows/check.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 81887c55..e573b6ce 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -131,7 +131,10 @@ jobs: TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" - fi + # Suppress leaks from dyld4::RuntimeState on macOS + echo "leak:dyld4::RuntimeState" > suppressions.txt + export ASAN_OPTIONS="$ASAN_OPTIONS:suppressions=suppressions.txt" + fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." RUSTFLAGS="-Z sanitizer=$sanitizer" RUSTDOCFLAGS="-Z sanitizer=$sanitizer" cargo +nightly test -Z build-std --target "$TARGET" From 7c51f5f36cd4fd7c58a87b7317a067ebf04ba84b Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 16:39:43 +0300 Subject: [PATCH 036/128] LSAN_OPTIONS --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e573b6ce..58556279 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -133,7 +133,7 @@ jobs: SANITIZERS="address thread" # Suppress leaks from dyld4::RuntimeState on macOS echo "leak:dyld4::RuntimeState" > suppressions.txt - export ASAN_OPTIONS="$ASAN_OPTIONS:suppressions=suppressions.txt" + export LSAN_OPTIONS="suppressions=suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." From 41d51bb940eb91e5b0d1ecfc0370aa67b9decfde Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 16:45:35 +0300 Subject: [PATCH 037/128] llvm --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 58556279..bf2dbb92 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -127,7 +127,7 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - # llvm-symbolizer (as part of llvm) is installed by default on macOS runners + brew install llvm TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" From cfa907709f1fc3aeb1ba889505c7fd6ce7183b7e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 16:49:32 +0300 Subject: [PATCH 038/128] path --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index bf2dbb92..233ae257 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -122,12 +122,13 @@ jobs: ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1 run: | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - sudo apt-get update sudo apt-get install -y --no-install-recommends llvm TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then brew install llvm + # llvm is keg-only, so we need to add it to the PATH + export PATH="/opt/homebrew/opt/llvm/bin:$PATH" TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" From 473fadc26a7477beaef02e912704fc17cdeb2408 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:00:04 +0300 Subject: [PATCH 039/128] Again --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 233ae257..a79a00fc 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -129,6 +129,7 @@ jobs: brew install llvm # llvm is keg-only, so we need to add it to the PATH export PATH="/opt/homebrew/opt/llvm/bin:$PATH" + which llvm-symbolizer TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" @@ -138,7 +139,7 @@ jobs: fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." - RUSTFLAGS="-Z sanitizer=$sanitizer" RUSTDOCFLAGS="-Z sanitizer=$sanitizer" cargo +nightly test -Z build-std --target "$TARGET" + RUSTFLAGS="-Z sanitizer=$sanitizer -Csplit-debuginfo=packed" RUSTDOCFLAGS="-Z sanitizer=$sanitizer" cargo +nightly test -Z build-std --target "$TARGET" done clippy: From 30444c340dbfc8096b42ac9e91c5727533cfdc5a Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:16:02 +0300 Subject: [PATCH 040/128] Again --- .github/workflows/check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a79a00fc..f1259922 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -130,6 +130,7 @@ jobs: # llvm is keg-only, so we need to add it to the PATH export PATH="/opt/homebrew/opt/llvm/bin:$PATH" which llvm-symbolizer + which dsymutil TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" From a69908d06d2d1b15985df833687171d3af57f14e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:19:09 +0300 Subject: [PATCH 041/128] Again --- .github/workflows/check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f1259922..0c460800 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -126,9 +126,9 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - brew install llvm + # brew install llvm # llvm is keg-only, so we need to add it to the PATH - export PATH="/opt/homebrew/opt/llvm/bin:$PATH" + # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" which llvm-symbolizer which dsymutil TARGET="aarch64-apple-darwin" @@ -140,7 +140,7 @@ jobs: fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." - RUSTFLAGS="-Z sanitizer=$sanitizer -Csplit-debuginfo=packed" RUSTDOCFLAGS="-Z sanitizer=$sanitizer" cargo +nightly test -Z build-std --target "$TARGET" + RUSTFLAGS="-Z sanitizer=$sanitizer" RUSTDOCFLAGS="-Z sanitizer=$sanitizer" cargo +nightly test -Z build-std --target "$TARGET" done clippy: From b80169db7829a153289e3b9717cf27ec7aba58d3 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:21:28 +0300 Subject: [PATCH 042/128] Again --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0c460800..29b022cc 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -129,8 +129,8 @@ jobs: # brew install llvm # llvm is keg-only, so we need to add it to the PATH # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" - which llvm-symbolizer - which dsymutil + which llvm-symbolizer || true + which dsymutil || true TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" From 75e00c0a9e9804e78163bcb2ee8e568211817102 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:25:04 +0300 Subject: [PATCH 043/128] Again --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 29b022cc..76b56c93 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -126,7 +126,8 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - # brew install llvm + brew install llvm + ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer # llvm is keg-only, so we need to add it to the PATH # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" which llvm-symbolizer || true From 20b2396127d17d52082df412f9728bf8e21201a5 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:33:37 +0300 Subject: [PATCH 044/128] Again --- .github/workflows/check.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 76b56c93..a4162a1a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -127,7 +127,7 @@ jobs: SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then brew install llvm - ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer + export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer # llvm is keg-only, so we need to add it to the PATH # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" which llvm-symbolizer || true @@ -135,8 +135,9 @@ jobs: TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" - # Suppress leaks from dyld4::RuntimeState on macOS + # Suppress leaks on macOS. TODO: Check occasionally if these are still needed. echo "leak:dyld4::RuntimeState" > suppressions.txt + # echo "leak:_fetchInitializingClassList" >> suppressions.txt export LSAN_OPTIONS="suppressions=suppressions.txt" fi for sanitizer in $SANITIZERS; do From 86a592f9873b780541d60624ee0037333c77076e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:46:17 +0300 Subject: [PATCH 045/128] Again --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a4162a1a..aee25070 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -121,6 +121,7 @@ jobs: RUST_LOG: trace ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1 run: | + cargo clean if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then sudo apt-get install -y --no-install-recommends llvm TARGET="x86_64-unknown-linux-gnu" @@ -129,7 +130,7 @@ jobs: brew install llvm export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer # llvm is keg-only, so we need to add it to the PATH - # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" + export PATH="/opt/homebrew/opt/llvm/bin:$PATH" which llvm-symbolizer || true which dsymutil || true TARGET="aarch64-apple-darwin" From 6467f8bc734ba5573405484a6db477f51d4073da Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 17:49:30 +0300 Subject: [PATCH 046/128] Again --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index aee25070..9cd489e7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,7 +123,7 @@ jobs: run: | cargo clean if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - sudo apt-get install -y --no-install-recommends llvm + sudo apt-get install -y --no-install-recommends llvm lld TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then From 9e536ce827b08093e31a63e2ba720b95229681d5 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 18:05:44 +0300 Subject: [PATCH 047/128] Again --- .github/workflows/check.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9cd489e7..6be67fe6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -131,19 +131,19 @@ jobs: export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer # llvm is keg-only, so we need to add it to the PATH export PATH="/opt/homebrew/opt/llvm/bin:$PATH" - which llvm-symbolizer || true - which dsymutil || true TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" # Suppress leaks on macOS. TODO: Check occasionally if these are still needed. - echo "leak:dyld4::RuntimeState" > suppressions.txt + # echo "leak:dyld4::RuntimeState" > suppressions.txt # echo "leak:_fetchInitializingClassList" >> suppressions.txt - export LSAN_OPTIONS="suppressions=suppressions.txt" + # export LSAN_OPTIONS="suppressions=suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." - RUSTFLAGS="-Z sanitizer=$sanitizer" RUSTDOCFLAGS="-Z sanitizer=$sanitizer" cargo +nightly test -Z build-std --target "$TARGET" + export RUSTFLAGS="-Z sanitizer=$sanitizer -Clinker=clang -Clink-arg=-fuse-ld=lld" + export RUSTDOCFLAGS="$RUSTFLAGS" + cargo +nightly test -Z build-std --target "$TARGET" done clippy: From d8d7dc1bb89ccd64119152ff836d7eb83b1d1b6e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 18:08:29 +0300 Subject: [PATCH 048/128] Again --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6be67fe6..d75d8142 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,11 +123,11 @@ jobs: run: | cargo clean if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - sudo apt-get install -y --no-install-recommends llvm lld + sudo apt-get install -y --no-install-recommends llvm TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - brew install llvm + brew install llvm lld export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer # llvm is keg-only, so we need to add it to the PATH export PATH="/opt/homebrew/opt/llvm/bin:$PATH" From 3f3fe9acc0080637effe688095adeaa9aa029060 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 18:12:07 +0300 Subject: [PATCH 049/128] Again --- .github/workflows/check.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d75d8142..e5e0d728 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -127,10 +127,10 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - brew install llvm lld - export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer + # brew install llvm lld + # export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer # llvm is keg-only, so we need to add it to the PATH - export PATH="/opt/homebrew/opt/llvm/bin:$PATH" + # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" @@ -141,7 +141,8 @@ jobs: fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." - export RUSTFLAGS="-Z sanitizer=$sanitizer -Clinker=clang -Clink-arg=-fuse-ld=lld" + # export RUSTFLAGS="-Z sanitizer=$sanitizer -Clinker=clang -Clink-arg=-fuse-ld=lld" + export RUSTFLAGS="-Z sanitizer=$sanitizer" export RUSTDOCFLAGS="$RUSTFLAGS" cargo +nightly test -Z build-std --target "$TARGET" done From a9b372cc38c7197e2a6ae432e38f8fac3dbe56dd Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 18:14:57 +0300 Subject: [PATCH 050/128] Again --- .github/workflows/check.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e5e0d728..979aaac9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -121,27 +121,21 @@ jobs: RUST_LOG: trace ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1 run: | - cargo clean if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then sudo apt-get install -y --no-install-recommends llvm TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then - # brew install llvm lld - # export ASAN_SYMBOLIZER_PATH=/opt/homebrew/opt/llvm/bin/llvm-symbolizer - # llvm is keg-only, so we need to add it to the PATH - # export PATH="/opt/homebrew/opt/llvm/bin:$PATH" - TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" # Suppress leaks on macOS. TODO: Check occasionally if these are still needed. - # echo "leak:dyld4::RuntimeState" > suppressions.txt - # echo "leak:_fetchInitializingClassList" >> suppressions.txt - # export LSAN_OPTIONS="suppressions=suppressions.txt" + echo "leak:dyld4::RuntimeState" > suppressions.txt + echo "leak:fetchInitializingClassList" >> suppressions.txt + cat suppressions.txt + export LSAN_OPTIONS="suppressions=suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." - # export RUSTFLAGS="-Z sanitizer=$sanitizer -Clinker=clang -Clink-arg=-fuse-ld=lld" export RUSTFLAGS="-Z sanitizer=$sanitizer" export RUSTDOCFLAGS="$RUSTFLAGS" cargo +nightly test -Z build-std --target "$TARGET" From ee711fc48325de8f7add76c62663dd23ce5cc2a3 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 18:17:06 +0300 Subject: [PATCH 051/128] Again --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 979aaac9..43e29854 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -126,14 +126,14 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then + TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" # Suppress leaks on macOS. TODO: Check occasionally if these are still needed. echo "leak:dyld4::RuntimeState" > suppressions.txt echo "leak:fetchInitializingClassList" >> suppressions.txt - cat suppressions.txt export LSAN_OPTIONS="suppressions=suppressions.txt" - fi + fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." export RUSTFLAGS="-Z sanitizer=$sanitizer" From 4d43a3c3f1e8e4d3f66129253855c9e4c6ae2c26 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 10 Oct 2024 18:26:44 +0300 Subject: [PATCH 052/128] Again --- .github/workflows/check.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 43e29854..765ed4b6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -126,13 +126,17 @@ jobs: TARGET="x86_64-unknown-linux-gnu" SANITIZERS="address thread leak memory" elif [ "${{ matrix.os }}" = "macos-latest" ]; then + # llvm-symbolizer (as part of llvm) is installed by default on macOS runners TARGET="aarch64-apple-darwin" # no memory and leak sanitizer support yet SANITIZERS="address thread" - # Suppress leaks on macOS. TODO: Check occasionally if these are still needed. - echo "leak:dyld4::RuntimeState" > suppressions.txt - echo "leak:fetchInitializingClassList" >> suppressions.txt - export LSAN_OPTIONS="suppressions=suppressions.txt" + # Suppress non-mtu leaks on macOS. TODO: Check occasionally if these are still needed. + { + echo "leak:dyld4::RuntimeState" + echo "leak:fetchInitializingClassList" + } > suppressions.txt + PWD=$(pwd) + export LSAN_OPTIONS="suppressions=$PWD/suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." From c58eda94e1cfc483a14a690a9553527ee9bee2ca Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 11 Oct 2024 09:09:07 +0300 Subject: [PATCH 053/128] Update src/linux.rs Co-authored-by: Max Inden --- src/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux.rs b/src/linux.rs index ca08c9e6..68736e6a 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -135,7 +135,7 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { ); }; - // Send RTM_GETROUTE message to get the interfce index associated with the destination. + // Send RTM_GETROUTE message to get the interface index associated with the destination. if unsafe { write(fd, buf.as_ptr().cast(), buf.len()) } < 0 { let err = Error::last_os_error(); unsafe { close(fd) }; From 5f57b4acb65b4453255985ec254582b61171c4bc Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 11 Oct 2024 09:17:50 +0300 Subject: [PATCH 054/128] Use `OwnedFd` --- src/bsd.rs | 30 ++++++++++++------------------ src/linux.rs | 39 +++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index a23884ba..187af41e 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -8,21 +8,22 @@ use std::{ io::Error, mem::{self, size_of}, net::IpAddr, + os::fd::{AsRawFd, FromRawFd, OwnedFd}, ptr, slice, str, }; use libc::{ - close, getpid, read, rt_msghdr, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, - socket, write, AF_INET, AF_INET6, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, - RTM_VERSION, SOCK_RAW, + getpid, read, rt_msghdr, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, + write, AF_INET, AF_INET6, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, + SOCK_RAW, }; use crate::{default_err, next_item_aligned_by_four}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. - let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; - if fd == -1 { + let fd = unsafe { OwnedFd::from_raw_fd(socket(PF_ROUTE, SOCK_RAW, 0)) }; + if fd.as_raw_fd() == -1 { return Err(Error::last_os_error()); } @@ -54,7 +55,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> .map_err(|_| default_err())?; // Length includes sockaddr rtm.rtm_version = RTM_VERSION.try_into().map_err(|_| default_err())?; rtm.rtm_type = RTM_GET.try_into().map_err(|_| default_err())?; - rtm.rtm_seq = fd; // Abuse file descriptor as sequence number, since it's unique + rtm.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info // Copy route message and destination `sockaddr` into message buffer. @@ -73,11 +74,9 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Send route message. - let res = unsafe { write(fd, msg.as_ptr().cast(), msg.len()) }; + let res = unsafe { write(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len()) }; if res == -1 { - let err = Error::last_os_error(); - unsafe { close(fd) }; - return Err(err); + return Err(Error::last_os_error()); } // Read route messages. @@ -88,25 +87,20 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { - let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { - let err = Error::last_os_error(); - unsafe { close(fd) }; - return Err(err); + return Err(Error::last_os_error()); } let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; if rtm.rtm_type == RTM_GET.try_into().map_err(|_| default_err())? && rtm.rtm_pid == unsafe { getpid() } - && rtm.rtm_seq == fd + && rtm.rtm_seq == fd.as_raw_fd() { // This is the response we are looking for. break rtm; } }; - // Close the route socket. - unsafe { close(fd) }; - // Parse the route message for the interface name. let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; for i in 0..RTAX_MAX { diff --git a/src/linux.rs b/src/linux.rs index 68736e6a..63af7253 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -9,14 +9,15 @@ use std::{ io::{Error, ErrorKind}, mem, net::IpAddr, + os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, ptr, }; use libc::{ - c_int, c_uchar, c_uint, c_ushort, close, nlmsghdr, read, socket, write, AF_INET, AF_INET6, - AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLM_F_ACK, - NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, - RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, + c_int, c_uchar, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, + AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLM_F_ACK, NLM_F_REQUEST, + RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, + RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; use crate::{default_err, next_item_aligned_by_four}; @@ -83,7 +84,7 @@ fn prepare_nlmsg( Ok(nlm) } -fn if_index(remote: IpAddr, fd: i32) -> Result { +fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { // Prepare RTM_GETROUTE message. let nlmsg_len = mem::size_of::() + mem::size_of::() @@ -136,16 +137,14 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { }; // Send RTM_GETROUTE message to get the interface index associated with the destination. - if unsafe { write(fd, buf.as_ptr().cast(), buf.len()) } < 0 { - let err = Error::last_os_error(); - unsafe { close(fd) }; - return Err(err); + if unsafe { write(fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) } < 0 { + return Err(Error::last_os_error()); } // Receive RTM_GETROUTE response. loop { let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len < 0 { return Err(Error::last_os_error()); } @@ -177,7 +176,7 @@ fn if_index(remote: IpAddr, fd: i32) -> Result { } } -fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { +fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> { // Prepare RTM_GETLINK message to get the interface name and MTU for the interface with the // obtained index. let nlmsg_len = mem::size_of::() + mem::size_of::(); @@ -204,10 +203,8 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { } // Send RTM_GETLINK message. - if unsafe { write(fd, buf.as_ptr().cast(), buf.len()) } < 0 { - let err = Error::last_os_error(); - unsafe { close(fd) }; - return Err(err); + if unsafe { write(fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) } < 0 { + return Err(Error::last_os_error()); } // Receive RTM_GETLINK response. @@ -215,7 +212,7 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { let mut mtu = None; 'recv: loop { let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len < 0 { return Err(Error::last_os_error()); } @@ -268,13 +265,11 @@ fn if_name_mtu(if_index: i32, fd: i32) -> Result<(String, usize), Error> { pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Create a netlink socket. - let fd = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) }; - if fd < 0 { + let fd = unsafe { OwnedFd::from_raw_fd(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) }; + if fd.as_raw_fd() < 0 { return Err(Error::last_os_error()); } - let if_index = if_index(remote, fd)?; - let res = if_name_mtu(if_index, fd); - unsafe { close(fd) }; - res + let if_index = if_index(remote, fd.as_fd())?; + if_name_mtu(if_index, fd.as_fd()) } From 248391b63c6a1d098750c22f79000e50b3ab07d7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 14:36:25 +0300 Subject: [PATCH 055/128] Address code review --- src/bsd.rs | 37 +++++++++++++++++++++++++------------ src/lib.rs | 6 ++++++ src/linux.rs | 48 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 187af41e..b066de03 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -8,6 +8,7 @@ use std::{ io::Error, mem::{self, size_of}, net::IpAddr, + num::TryFromIntError, os::fd::{AsRawFd, FromRawFd, OwnedFd}, ptr, slice, str, }; @@ -18,14 +19,15 @@ use libc::{ SOCK_RAW, }; -use crate::{default_err, next_item_aligned_by_four}; +use crate::{default_err, next_item_aligned_by_four, unlikely_err}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. - let fd = unsafe { OwnedFd::from_raw_fd(socket(PF_ROUTE, SOCK_RAW, 0)) }; - if fd.as_raw_fd() == -1 { + let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; + if fd == -1 { return Err(Error::last_os_error()); } + let fd = unsafe { OwnedFd::from_raw_fd(fd) }; // Prepare buffer with destination `sockaddr`. let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; @@ -34,27 +36,35 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin.sin_len = size_of::() .try_into() - .map_err(|_| default_err())?; - sin.sin_family = AF_INET.try_into().map_err(|_| default_err())?; + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + sin.sin_family = AF_INET + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; sin.sin_addr.s_addr = u32::from_ne_bytes(ip.octets()); } IpAddr::V6(ip) => { let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin6.sin6_len = size_of::() .try_into() - .map_err(|_| default_err())?; - sin6.sin6_family = AF_INET6.try_into().map_err(|_| default_err())?; + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + sin6.sin6_family = AF_INET6 + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; sin6.sin6_addr.s6_addr = ip.octets(); } }; // Prepare route message structure. let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; - rtm.rtm_msglen = (size_of::() + dst.ss_len as usize) + rtm.rtm_msglen = (size_of::() + dst.ss_len as usize) // Length includes sockaddr + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + rtm.rtm_version = RTM_VERSION .try_into() - .map_err(|_| default_err())?; // Length includes sockaddr - rtm.rtm_version = RTM_VERSION.try_into().map_err(|_| default_err())?; - rtm.rtm_type = RTM_GET.try_into().map_err(|_| default_err())?; + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + rtm.rtm_type = RTM_GET + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; rtm.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info @@ -92,7 +102,10 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> return Err(Error::last_os_error()); } let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; - if rtm.rtm_type == RTM_GET.try_into().map_err(|_| default_err())? + if rtm.rtm_type + == RTM_GET + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? && rtm.rtm_pid == unsafe { getpid() } && rtm.rtm_seq == fd.as_raw_fd() { diff --git a/src/lib.rs b/src/lib.rs index 636defd5..80d88cd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,12 @@ fn default_err() -> Error { Error::new(ErrorKind::NotFound, "Local interface MTU not found") } +/// Prepare an error for cases that "should never happen". +fn unlikely_err(msg: String) -> Error { + debug_assert!(false, "{msg}"); + Error::new(ErrorKind::Other, msg) +} + /// Align a size to the next multiple of four. #[cfg(not(target_os = "windows"))] const fn next_item_aligned_by_four(size: usize) -> usize { diff --git a/src/linux.rs b/src/linux.rs index 63af7253..042fba15 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -6,9 +6,10 @@ use std::{ ffi::CStr, - io::{Error, ErrorKind}, + io::Error, mem, net::IpAddr, + num::TryFromIntError, os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, ptr, }; @@ -20,7 +21,7 @@ use libc::{ RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; -use crate::{default_err, next_item_aligned_by_four}; +use crate::{default_err, next_item_aligned_by_four, unlikely_err}; const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. @@ -75,11 +76,13 @@ fn prepare_nlmsg( nlmsg_seq: u32, ) -> Result { let mut nlm = unsafe { mem::zeroed::() }; - nlm.nlmsg_len = nlmsg_len.try_into().map_err(|_| default_err())?; + nlm.nlmsg_len = nlmsg_len + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; nlm.nlmsg_type = nlmsg_type; nlm.nlmsg_flags = (NLM_F_REQUEST | NLM_F_ACK) .try_into() - .map_err(|_| default_err())?; + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; nlm.nlmsg_seq = nlmsg_seq; Ok(nlm) } @@ -95,8 +98,12 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { let mut rtm = unsafe { mem::zeroed::() }; rtm.rtm_family = match remote { - IpAddr::V4(_) => AF_INET.try_into().map_err(|_| default_err())?, - IpAddr::V6(_) => AF_INET6.try_into().map_err(|_| default_err())?, + IpAddr::V4(_) => AF_INET + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, + IpAddr::V6(_) => AF_INET6 + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, }; rtm.rtm_dst_len = addr_len(&remote); rtm.rtm_table = RT_TABLE_MAIN; @@ -106,7 +113,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { let mut attr = unsafe { mem::zeroed::() }; attr.rta_len = (mem::size_of::() + addr_bytes(&remote).len()) .try_into() - .map_err(|_| default_err())?; + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; attr.rta_type = RTA_DST; let mut buf = vec![0u8; nlmsg_len]; @@ -150,7 +157,11 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { } let mut offset = 0; - while offset < len.try_into().map_err(|_| default_err())? { + while offset + < len + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? + { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWROUTE { // This is the response, parse through the attributes to find the interface index. @@ -184,7 +195,9 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> let hdr = prepare_nlmsg(RTM_GETLINK, nlmsg_len, nlmsg_seq)?; let mut ifim: ifinfomsg = unsafe { mem::zeroed() }; - ifim.ifi_family = AF_UNSPEC.try_into().map_err(|_| default_err())?; + ifim.ifi_family = AF_UNSPEC + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; ifim.ifi_type = ARPHRD_NONE; ifim.ifi_index = if_index; @@ -218,7 +231,11 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> } let mut offset = 0; - while offset < len.try_into().map_err(|_| default_err())? { + while offset + < len + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? + { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWLINK { let mut attr_ptr = unsafe { @@ -244,7 +261,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> ) } .try_into() - .map_err(|_| default_err())?, + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, ); } if ifname.is_some() && mtu.is_some() { @@ -258,17 +275,18 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> } } - let name = ifname.ok_or_else(|| Error::new(ErrorKind::Other, "Interface name not found"))?; - let mtu = mtu.ok_or_else(|| Error::new(ErrorKind::Other, "MTU not found"))?; + let name = ifname.ok_or_else(default_err)?; + let mtu = mtu.ok_or_else(default_err)?; Ok((name, mtu)) } pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Create a netlink socket. - let fd = unsafe { OwnedFd::from_raw_fd(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) }; - if fd.as_raw_fd() < 0 { + let fd = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) }; + if fd == -1 { return Err(Error::last_os_error()); } + let fd = unsafe { OwnedFd::from_raw_fd(fd) }; let if_index = if_index(remote, fd.as_fd())?; if_name_mtu(if_index, fd.as_fd()) From 4a77574a3b3e8a4178c9d206c43a20973a8f23eb Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 14:48:01 +0300 Subject: [PATCH 056/128] BSDs and Windows --- src/bsd.rs | 43 ++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 1 + 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index b066de03..2fa47412 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -13,12 +13,49 @@ use std::{ ptr, slice, str, }; +#[cfg(not(bsd))] +use libc::rt_msghdr; use libc::{ - getpid, read, rt_msghdr, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, - write, AF_INET, AF_INET6, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, - SOCK_RAW, + getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, + AF_INET6, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, SOCK_RAW, }; +// The BSDs are lacking `rt_metrics` in their libc bindings. +#[cfg(bsd)] +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +struct rt_metrics { + rmx_locks: libc::c_ulong, // Kernel must leave these values alone + rmx_mtu: libc::c_ulong, // MTU for this path + rmx_hopcount: libc::c_ulong, // max hops expected + rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect + rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product + rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product + rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit + rmx_rtt: libc::c_ulong, // estimated round trip time + rmx_rttvar: libc::c_ulong, // estimated rtt variance + rmx_pksent: libc::c_ulong, // packets sent using this route +} + +// The BSDs are lacking `rt_msghdr` in their libc bindings. +#[cfg(bsd)] +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +struct rt_msghdr { + rtm_msglen: libc::c_ushort, // to skip over non-understood messages + rtm_version: libc::c_uchar, // future binary compatibility + rtm_type: libc::c_uchar, // message type + rtm_index: libc::c_ushort, // index for associated ifp + rtm_flags: libc::c_int, // flags, incl kern & message, e.g. DONE + rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg + rtm_pid: libc::pid_t, // identify sender + rtm_seq: libc::c_int, // for sender to identify action + rtm_errno: libc::c_int, // why failed + rtm_use: libc::c_int, // from rtentry + rtm_inits: libc::c_ulong, // which metrics we are initializing + rtm_rmx: rt_metrics, // metrics themselves +} + use crate::{default_err, next_item_aligned_by_four, unlikely_err}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { diff --git a/src/lib.rs b/src/lib.rs index 80d88cd1..16159319 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ fn default_err() -> Error { } /// Prepare an error for cases that "should never happen". +#[cfg(not(target_os = "windows"))] fn unlikely_err(msg: String) -> Error { debug_assert!(false, "{msg}"); Error::new(ErrorKind::Other, msg) From b00aebd5229b48fc0ee4aae849a9d686b2c76948 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 14:54:40 +0300 Subject: [PATCH 057/128] ifconfig --- .github/workflows/check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 765ed4b6..819b1e35 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -263,6 +263,7 @@ jobs: pkg update pkg install -y rust run: | + ifconfig cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -274,6 +275,7 @@ jobs: prepare: | pkg_add rust run: | + ifconfig cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -285,6 +287,7 @@ jobs: prepare: | /usr/sbin/pkg_add rust run: | + ifconfig cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -296,5 +299,6 @@ jobs: prepare: | pkg install cargo run: | + ifconfig cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast From 075f3114624b401df7d865bf90b3f9c81a2ff713 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 15:02:23 +0300 Subject: [PATCH 058/128] env --- .github/workflows/check.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 819b1e35..50bbf755 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -257,6 +257,7 @@ jobs: with: usesh: true copyback: false + envs: "CARGO_TERM_COLOR RUST_BACKTRACE" prepare: | mkdir -p /usr/local/etc/pkg/repos sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf @@ -272,6 +273,7 @@ jobs: with: usesh: true copyback: false + envs: "CARGO_TERM_COLOR RUST_BACKTRACE" prepare: | pkg_add rust run: | @@ -284,10 +286,11 @@ jobs: with: usesh: true copyback: false + envs: "CARGO_TERM_COLOR RUST_BACKTRACE" prepare: | /usr/sbin/pkg_add rust run: | - ifconfig + /sbin/ifconfig cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -296,6 +299,7 @@ jobs: with: usesh: true copyback: false + envs: "CARGO_TERM_COLOR RUST_BACKTRACE" prepare: | pkg install cargo run: | From 18a69384525f20562bef8ef541057fbeda31f499 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 15:13:17 +0300 Subject: [PATCH 059/128] rmx_filler --- src/bsd.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 2fa47412..f1b642bb 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -25,16 +25,17 @@ use libc::{ #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] struct rt_metrics { - rmx_locks: libc::c_ulong, // Kernel must leave these values alone - rmx_mtu: libc::c_ulong, // MTU for this path - rmx_hopcount: libc::c_ulong, // max hops expected - rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect - rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product - rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product - rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit - rmx_rtt: libc::c_ulong, // estimated round trip time - rmx_rttvar: libc::c_ulong, // estimated rtt variance - rmx_pksent: libc::c_ulong, // packets sent using this route + rmx_locks: libc::c_ulong, // Kernel must leave these values alone + rmx_mtu: libc::c_ulong, // MTU for this path + rmx_hopcount: libc::c_ulong, // max hops expected + rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect + rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product + rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product + rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit + rmx_rtt: libc::c_ulong, // estimated round trip time + rmx_rttvar: libc::c_ulong, // estimated rtt variance + rmx_pksent: libc::c_ulong, // packets sent using this route + rmx_filler: [libc::c_ulong; 4], //empty space available for protocol-specific information } // The BSDs are lacking `rt_msghdr` in their libc bindings. @@ -43,13 +44,13 @@ struct rt_metrics { #[repr(C)] struct rt_msghdr { rtm_msglen: libc::c_ushort, // to skip over non-understood messages - rtm_version: libc::c_uchar, // future binary compatibility + rtm_version: libc::c_uchar, // future binary compatibility rtm_type: libc::c_uchar, // message type rtm_index: libc::c_ushort, // index for associated ifp - rtm_flags: libc::c_int, // flags, incl kern & message, e.g. DONE + rtm_flags: libc::c_int, // flags, incl kern & message, e.g. DONE rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg rtm_pid: libc::pid_t, // identify sender - rtm_seq: libc::c_int, // for sender to identify action + rtm_seq: libc::c_int, // for sender to identify action rtm_errno: libc::c_int, // why failed rtm_use: libc::c_int, // from rtentry rtm_inits: libc::c_ulong, // which metrics we are initializing From e571427877c1d1f48e3a7354dc3f39f1c0ec60fe Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 15:28:16 +0300 Subject: [PATCH 060/128] Debug --- .github/workflows/check.yml | 8 ++++---- src/bsd.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 50bbf755..6d20568e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -257,7 +257,7 @@ jobs: with: usesh: true copyback: false - envs: "CARGO_TERM_COLOR RUST_BACKTRACE" + envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | mkdir -p /usr/local/etc/pkg/repos sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf @@ -273,7 +273,7 @@ jobs: with: usesh: true copyback: false - envs: "CARGO_TERM_COLOR RUST_BACKTRACE" + envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | pkg_add rust run: | @@ -286,7 +286,7 @@ jobs: with: usesh: true copyback: false - envs: "CARGO_TERM_COLOR RUST_BACKTRACE" + envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | /usr/sbin/pkg_add rust run: | @@ -299,7 +299,7 @@ jobs: with: usesh: true copyback: false - envs: "CARGO_TERM_COLOR RUST_BACKTRACE" + envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | pkg install cargo run: | diff --git a/src/bsd.rs b/src/bsd.rs index f1b642bb..21c4221d 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -17,7 +17,8 @@ use std::{ use libc::rt_msghdr; use libc::{ getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, - AF_INET6, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, SOCK_RAW, + AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, + SOCK_RAW, }; // The BSDs are lacking `rt_metrics` in their libc bindings. @@ -60,12 +61,14 @@ struct rt_msghdr { use crate::{default_err, next_item_aligned_by_four, unlikely_err}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + eprintln!("bsd::interface_and_mtu_impl(remote: {remote:?})"); // Open route socket. - let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, 0) }; + let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; if fd == -1 { return Err(Error::last_os_error()); } let fd = unsafe { OwnedFd::from_raw_fd(fd) }; + eprintln!("fd: {fd:?}"); // Prepare buffer with destination `sockaddr`. let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; @@ -91,6 +94,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> sin6.sin6_addr.s6_addr = ip.octets(); } }; + eprintln!("dst"); // Prepare route message structure. let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; From 6942ecc504476bd699b0c1e805ebd22933b2928a Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 15:35:43 +0300 Subject: [PATCH 061/128] Debug --- src/bsd.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bsd.rs b/src/bsd.rs index 21c4221d..972d99be 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -126,10 +126,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Send route message. + eprintln!("before write"); let res = unsafe { write(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len()) }; if res == -1 { return Err(Error::last_os_error()); } + eprintln!("write"); // Read route messages. let mut buf = vec![ @@ -139,10 +141,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { + eprintln!("before read"); let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { return Err(Error::last_os_error()); } + eprintln!("read"); let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; if rtm.rtm_type == RTM_GET From 1eac2eed018d603e6069f2c62cdb3fcbba854f66 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 15:44:13 +0300 Subject: [PATCH 062/128] send --- src/bsd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 972d99be..754a6c16 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -16,7 +16,7 @@ use std::{ #[cfg(not(bsd))] use libc::rt_msghdr; use libc::{ - getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, + getpid, read, send, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, AF_INET, AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, SOCK_RAW, }; @@ -127,7 +127,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Send route message. eprintln!("before write"); - let res = unsafe { write(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len()) }; + let res = unsafe { send(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len(), 0) }; if res == -1 { return Err(Error::last_os_error()); } From a20ffea22407aec2507b8131aeae46dbb3fbca9d Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 15:53:34 +0300 Subject: [PATCH 063/128] recv --- src/bsd.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 754a6c16..9b46ca9c 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -16,9 +16,9 @@ use std::{ #[cfg(not(bsd))] use libc::rt_msghdr; use libc::{ - getpid, read, send, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, AF_INET, - AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, - SOCK_RAW, + getpid, recv, send, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, AF_INET, + AF_INET6, AF_UNSPEC, MSG_WAITALL, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, + RTM_VERSION, SOCK_RAW, }; // The BSDs are lacking `rt_metrics` in their libc bindings. @@ -126,12 +126,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Send route message. - eprintln!("before write"); + eprintln!("before send"); let res = unsafe { send(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len(), 0) }; if res == -1 { return Err(Error::last_os_error()); } - eprintln!("write"); + eprintln!("send"); // Read route messages. let mut buf = vec![ @@ -141,12 +141,19 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { - eprintln!("before read"); - let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; + eprintln!("before recv"); + let len = unsafe { + recv( + fd.as_raw_fd(), + buf.as_mut_ptr().cast(), + buf.len(), + MSG_WAITALL, + ) + }; if len <= 0 { return Err(Error::last_os_error()); } - eprintln!("read"); + eprintln!("recv"); let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; if rtm.rtm_type == RTM_GET From 0901ad8287bd7c2ad5c240075e66877867a5b46e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 16:01:10 +0300 Subject: [PATCH 064/128] Again --- .github/workflows/check.yml | 4 ++++ src/bsd.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6d20568e..4d42173f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -265,6 +265,7 @@ jobs: pkg install -y rust run: | ifconfig + env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -278,6 +279,7 @@ jobs: pkg_add rust run: | ifconfig + env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -291,6 +293,7 @@ jobs: /usr/sbin/pkg_add rust run: | /sbin/ifconfig + env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -304,5 +307,6 @@ jobs: pkg install cargo run: | ifconfig + env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast diff --git a/src/bsd.rs b/src/bsd.rs index 9b46ca9c..a07ff4f0 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -36,6 +36,7 @@ struct rt_metrics { rmx_rtt: libc::c_ulong, // estimated round trip time rmx_rttvar: libc::c_ulong, // estimated rtt variance rmx_pksent: libc::c_ulong, // packets sent using this route + #[cfg(target_os = "freebsd")] rmx_filler: [libc::c_ulong; 4], //empty space available for protocol-specific information } From 546a452c1fa4553563b0f695b80c2bbe0c51b142 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 16:15:26 +0300 Subject: [PATCH 065/128] Again --- .github/workflows/check.yml | 4 ---- src/bsd.rs | 46 ++++++++++++++++--------------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4d42173f..6d20568e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -265,7 +265,6 @@ jobs: pkg install -y rust run: | ifconfig - env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -279,7 +278,6 @@ jobs: pkg_add rust run: | ifconfig - env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -293,7 +291,6 @@ jobs: /usr/sbin/pkg_add rust run: | /sbin/ifconfig - env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast @@ -307,6 +304,5 @@ jobs: pkg install cargo run: | ifconfig - env | sort cargo check --all-targets RUST_LOG=trace cargo test --no-fail-fast diff --git a/src/bsd.rs b/src/bsd.rs index a07ff4f0..de22b3c9 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -16,9 +16,9 @@ use std::{ #[cfg(not(bsd))] use libc::rt_msghdr; use libc::{ - getpid, recv, send, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, AF_INET, - AF_INET6, AF_UNSPEC, MSG_WAITALL, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, - RTM_VERSION, SOCK_RAW, + getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, + AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, + SOCK_RAW, }; // The BSDs are lacking `rt_metrics` in their libc bindings. @@ -26,16 +26,16 @@ use libc::{ #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] struct rt_metrics { - rmx_locks: libc::c_ulong, // Kernel must leave these values alone - rmx_mtu: libc::c_ulong, // MTU for this path - rmx_hopcount: libc::c_ulong, // max hops expected - rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect - rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product - rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product - rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit - rmx_rtt: libc::c_ulong, // estimated round trip time - rmx_rttvar: libc::c_ulong, // estimated rtt variance - rmx_pksent: libc::c_ulong, // packets sent using this route + rmx_locks: libc::c_ulong, // Kernel must leave these values alone + rmx_mtu: libc::c_ulong, // MTU for this path + rmx_hopcount: libc::c_ulong, // max hops expected + rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect + rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product + rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product + rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit + rmx_rtt: libc::c_ulong, // estimated round trip time + rmx_rttvar: libc::c_ulong, // estimated rtt variance + rmx_pksent: libc::c_ulong, // packets sent using this route #[cfg(target_os = "freebsd")] rmx_filler: [libc::c_ulong; 4], //empty space available for protocol-specific information } @@ -127,12 +127,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Send route message. - eprintln!("before send"); - let res = unsafe { send(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len(), 0) }; + eprintln!("before write: {msg:?}"); + let res = unsafe { write(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len()) }; if res == -1 { return Err(Error::last_os_error()); } - eprintln!("send"); + eprintln!("write"); // Read route messages. let mut buf = vec![ @@ -142,19 +142,12 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { - eprintln!("before recv"); - let len = unsafe { - recv( - fd.as_raw_fd(), - buf.as_mut_ptr().cast(), - buf.len(), - MSG_WAITALL, - ) - }; + eprintln!("before read"); + let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { return Err(Error::last_os_error()); } - eprintln!("recv"); + eprintln!("read"); let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; if rtm.rtm_type == RTM_GET @@ -179,6 +172,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let name = unsafe { slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen as usize) }; + eprintln!("name: {name:?}"); if let Ok(name) = str::from_utf8(name) { // We have our interface name. return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); From 807116f2c3f9643c28c012914e942d0defeb4b58 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 17:21:15 +0300 Subject: [PATCH 066/128] Again --- Cargo.toml | 2 +- src/bsd.rs | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 994b5cbc..ca3a377c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed", branch = "main" } [dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock -libc = { version = "0.2", default-features = false } +libc = { version = "0.2.161", default-features = false } [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock diff --git a/src/bsd.rs b/src/bsd.rs index de22b3c9..0e48173f 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -17,14 +17,15 @@ use std::{ use libc::rt_msghdr; use libc::{ getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, - AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, - SOCK_RAW, + AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, + RTM_VERSION, SOCK_RAW, }; // The BSDs are lacking `rt_metrics` in their libc bindings. #[cfg(bsd)] #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +#[derive(Debug)] struct rt_metrics { rmx_locks: libc::c_ulong, // Kernel must leave these values alone rmx_mtu: libc::c_ulong, // MTU for this path @@ -44,6 +45,7 @@ struct rt_metrics { #[cfg(bsd)] #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +#[derive(Debug)] struct rt_msghdr { rtm_msglen: libc::c_ushort, // to skip over non-understood messages rtm_version: libc::c_uchar, // future binary compatibility @@ -62,14 +64,12 @@ struct rt_msghdr { use crate::{default_err, next_item_aligned_by_four, unlikely_err}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { - eprintln!("bsd::interface_and_mtu_impl(remote: {remote:?})"); // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; if fd == -1 { return Err(Error::last_os_error()); } let fd = unsafe { OwnedFd::from_raw_fd(fd) }; - eprintln!("fd: {fd:?}"); // Prepare buffer with destination `sockaddr`. let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; @@ -95,7 +95,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> sin6.sin6_addr.s6_addr = ip.octets(); } }; - eprintln!("dst"); // Prepare route message structure. let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; @@ -127,7 +126,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Send route message. - eprintln!("before write: {msg:?}"); let res = unsafe { write(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len()) }; if res == -1 { return Err(Error::last_os_error()); @@ -165,20 +163,26 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; for i in 0..RTAX_MAX { let sdl = unsafe { ptr::read_unaligned(sa.cast::()) }; + eprintln!( + "i {} af {} len {} nlen {}", + i, sdl.sdl_family, sdl.sdl_len, sdl.sdl_nlen + ); // Check if the address is present in the message if rtm.rtm_addrs & (1 << i) != 0 { // Check if the address is the interface address - if i == RTAX_IFP { + if (i == RTAX_IFA || i == RTAX_IFP) && sdl.sdl_nlen != 0 { + eprintln!("sdl {:?} af {}", sdl.sdl_data, sdl.sdl_family); let name = unsafe { slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen as usize) }; - eprintln!("name: {name:?}"); if let Ok(name) = str::from_utf8(name) { // We have our interface name. + eprintln!("name: {name:?}"); return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } } let incr = next_item_aligned_by_four(sdl.sdl_len.into()); + eprintln!("incr {} {}", sdl.sdl_len, incr); sa = unsafe { sa.add(incr) }; } } From 1304c70a0f01c71cb5ffbf37e2f751159361f669 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 21 Oct 2024 17:31:44 +0300 Subject: [PATCH 067/128] mtu --- src/bsd.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bsd.rs b/src/bsd.rs index 0e48173f..4a743edd 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -18,7 +18,7 @@ use libc::rt_msghdr; use libc::{ getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, - RTM_VERSION, SOCK_RAW, + RTM_VERSION, RTV_MTU, SOCK_RAW, }; // The BSDs are lacking `rt_metrics` in their libc bindings. @@ -109,6 +109,9 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; rtm.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info + rtm.rtm_inits = RTV_MTU + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; // Copy route message and destination `sockaddr` into message buffer. let mut msg: Vec = vec![0; rtm.rtm_msglen as usize]; From 6b7d0f5226b8c3fb5e56672463b99525c912f119 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 22 Oct 2024 14:26:19 +0300 Subject: [PATCH 068/128] Fix cloudflare.com v6 address --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 16159319..b1479ad2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,7 +165,7 @@ mod test { // cloudflare.com assert_eq!( interface_and_mtu(IpAddr::V6(Ipv6Addr::new( - 0x26, 0x06, 0x47, 0x00, 0x68, 0x10, 0x84, 0xe5, + 0x2606, 0x4700, 0, 0, 0, 0, 0x6810, 0x84e5, ))) .unwrap(), INET From 813ae8d47abbf6cd5bd2f4565d0fc738fb20bd40 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 22 Oct 2024 14:33:56 +0300 Subject: [PATCH 069/128] Undo --- Cargo.toml | 2 +- src/bsd.rs | 21 +++------------------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca3a377c..9052f151 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed", branch = "main" } [dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock -libc = { version = "0.2.161", default-features = false } +libc = { version = ">=0.2.161", default-features = false } [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock diff --git a/src/bsd.rs b/src/bsd.rs index 4a743edd..72f172bd 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -17,15 +17,14 @@ use std::{ use libc::rt_msghdr; use libc::{ getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, - AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, - RTM_VERSION, RTV_MTU, SOCK_RAW, + AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, + SOCK_RAW, }; // The BSDs are lacking `rt_metrics` in their libc bindings. #[cfg(bsd)] #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] -#[derive(Debug)] struct rt_metrics { rmx_locks: libc::c_ulong, // Kernel must leave these values alone rmx_mtu: libc::c_ulong, // MTU for this path @@ -45,7 +44,6 @@ struct rt_metrics { #[cfg(bsd)] #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] -#[derive(Debug)] struct rt_msghdr { rtm_msglen: libc::c_ushort, // to skip over non-understood messages rtm_version: libc::c_uchar, // future binary compatibility @@ -109,9 +107,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; rtm.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info - rtm.rtm_inits = RTV_MTU - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; // Copy route message and destination `sockaddr` into message buffer. let mut msg: Vec = vec![0; rtm.rtm_msglen as usize]; @@ -133,7 +128,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> if res == -1 { return Err(Error::last_os_error()); } - eprintln!("write"); // Read route messages. let mut buf = vec![ @@ -143,12 +137,10 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { - eprintln!("before read"); let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { return Err(Error::last_os_error()); } - eprintln!("read"); let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; if rtm.rtm_type == RTM_GET @@ -166,26 +158,19 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; for i in 0..RTAX_MAX { let sdl = unsafe { ptr::read_unaligned(sa.cast::()) }; - eprintln!( - "i {} af {} len {} nlen {}", - i, sdl.sdl_family, sdl.sdl_len, sdl.sdl_nlen - ); // Check if the address is present in the message if rtm.rtm_addrs & (1 << i) != 0 { // Check if the address is the interface address - if (i == RTAX_IFA || i == RTAX_IFP) && sdl.sdl_nlen != 0 { - eprintln!("sdl {:?} af {}", sdl.sdl_data, sdl.sdl_family); + if i == RTAX_IFP { let name = unsafe { slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen as usize) }; if let Ok(name) = str::from_utf8(name) { // We have our interface name. - eprintln!("name: {name:?}"); return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } } let incr = next_item_aligned_by_four(sdl.sdl_len.into()); - eprintln!("incr {} {}", sdl.sdl_len, incr); sa = unsafe { sa.add(incr) }; } } From 789d964c745611a3eefbde5189e7f3c59e3a35d7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 22 Oct 2024 14:53:48 +0300 Subject: [PATCH 070/128] rt_metrics --- src/bsd.rs | 63 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 72f172bd..58bd586d 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -22,22 +22,59 @@ use libc::{ }; // The BSDs are lacking `rt_metrics` in their libc bindings. -#[cfg(bsd)] +// And of course they are all slightly different. +#[cfg(target_os = "freebsd")] +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +struct rt_metrics { + rmx_locks: libc::c_ulong, // Kernel must leave these values alone + rmx_mtu: libc::c_ulong, // MTU for this path + rmx_hopcount: libc::c_ulong, // max hops expected + rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect + rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product + rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product + rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit + rmx_rtt: libc::c_ulong, // estimated round trip time + rmx_rttvar: libc::c_ulong, // estimated rtt variance + rmx_pksent: libc::c_ulong, // packets sent using this route + rmx_weight: libc::u_long, // route weight + rmx_nhidx: libc::u_long, // route nexhop index + rmx_filler: [libc::c_ulong; 2], // will be used for T/TCP later +} + +#[cfg(target_os = "netbsd")] +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +struct rt_metrics { + rmx_locks: libc::uint64_t, // Kernel must leave these values alone + rmx_mtu: libc::uint64_t, // MTU for this path + rmx_hopcount: libc::uint64_t, // max hops expected + rmx_recvpipe: libc::uint64_t, // inbound delay-bandwidth product + rmx_sendpipe: libc::uint64_t, // outbound delay-bandwidth product + rmx_ssthresh: libc::uint64_t, // outbound gateway buffer limit + rmx_rtt: libc::uint64_t, // estimated round trip time + rmx_rttvar: libc::uint64_t, // estimated rtt variance + rmx_expire: libc::time_t, // lifetime for route, e.g. redirect + rmx_pksent: libc::time_t, // packets sent using this route +} + +#[cfg(target_os = "openbsd")] #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] struct rt_metrics { - rmx_locks: libc::c_ulong, // Kernel must leave these values alone - rmx_mtu: libc::c_ulong, // MTU for this path - rmx_hopcount: libc::c_ulong, // max hops expected - rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect - rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product - rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product - rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit - rmx_rtt: libc::c_ulong, // estimated round trip time - rmx_rttvar: libc::c_ulong, // estimated rtt variance - rmx_pksent: libc::c_ulong, // packets sent using this route - #[cfg(target_os = "freebsd")] - rmx_filler: [libc::c_ulong; 4], //empty space available for protocol-specific information + rmx_pksent: libc::uint64_t, // packets sent using this route + rmx_expire: libc::int64_t, // lifetime for route, e.g. redirect + rmx_locks: libc::c_uint, // Kernel must leave these values + rmx_mtu: libc::c_uint, // MTU for this path + rmx_refcnt: libc::c_uint, // # references hold + // some apps may still need these no longer used metrics + rmx_hopcount: libc::c_uint, // max hops expected + rmx_recvpipe: libc::c_uint, // inbound delay-bandwidth product + rmx_sendpipe: libc::c_uint, // outbound delay-bandwidth product + rmx_ssthresh: libc::c_uint, // outbound gateway buffer limit + rmx_rtt: libc::c_uint, // estimated round trip time + rmx_rttvar: libc::c_uint, // estimated rtt variance + rmx_pad: libc::c_uint, } // The BSDs are lacking `rt_msghdr` in their libc bindings. From c2b27a96ee5cdb57bb1d0eb4f57bc94317bfd7b7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 22 Oct 2024 16:03:46 +0300 Subject: [PATCH 071/128] More BSD --- src/bsd.rs | 48 +++++++++++++++++++++++++++--------------------- src/lib.rs | 8 ++++---- src/linux.rs | 4 ++-- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 58bd586d..d10047de 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -21,11 +21,17 @@ use libc::{ SOCK_RAW, }; +#[cfg(not(target_os = "netbsd"))] +const ALIGN: usize = 4; + +#[cfg(target_os = "netbsd")] +const ALIGN: usize = 8; + // The BSDs are lacking `rt_metrics` in their libc bindings. // And of course they are all slightly different. #[cfg(target_os = "freebsd")] #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] +#[repr(C, align(8))] struct rt_metrics { rmx_locks: libc::c_ulong, // Kernel must leave these values alone rmx_mtu: libc::c_ulong, // MTU for this path @@ -44,29 +50,29 @@ struct rt_metrics { #[cfg(target_os = "netbsd")] #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] +#[repr(C, align(8))] struct rt_metrics { - rmx_locks: libc::uint64_t, // Kernel must leave these values alone - rmx_mtu: libc::uint64_t, // MTU for this path - rmx_hopcount: libc::uint64_t, // max hops expected - rmx_recvpipe: libc::uint64_t, // inbound delay-bandwidth product - rmx_sendpipe: libc::uint64_t, // outbound delay-bandwidth product - rmx_ssthresh: libc::uint64_t, // outbound gateway buffer limit - rmx_rtt: libc::uint64_t, // estimated round trip time - rmx_rttvar: libc::uint64_t, // estimated rtt variance - rmx_expire: libc::time_t, // lifetime for route, e.g. redirect - rmx_pksent: libc::time_t, // packets sent using this route + rmx_locks: u64, // Kernel must leave these values alone + rmx_mtu: u64, // MTU for this path + rmx_hopcount: u64, // max hops expected + rmx_recvpipe: u64, // inbound delay-bandwidth product + rmx_sendpipe: u64, // outbound delay-bandwidth product + rmx_ssthresh: u64, // outbound gateway buffer limit + rmx_rtt: u64, // estimated round trip time + rmx_rttvar: u64, // estimated rtt variance + rmx_expire: libc::time_t, // lifetime for route, e.g. redirect + rmx_pksent: libc::time_t, // packets sent using this route } #[cfg(target_os = "openbsd")] #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] +#[repr(C, align(8))] struct rt_metrics { - rmx_pksent: libc::uint64_t, // packets sent using this route - rmx_expire: libc::int64_t, // lifetime for route, e.g. redirect - rmx_locks: libc::c_uint, // Kernel must leave these values - rmx_mtu: libc::c_uint, // MTU for this path - rmx_refcnt: libc::c_uint, // # references hold + rmx_pksent: u64, // packets sent using this route + rmx_expire: i64, // lifetime for route, e.g. redirect + rmx_locks: libc::c_uint, // Kernel must leave these values + rmx_mtu: libc::c_uint, // MTU for this path + rmx_refcnt: libc::c_uint, // # references hold // some apps may still need these no longer used metrics rmx_hopcount: libc::c_uint, // max hops expected rmx_recvpipe: libc::c_uint, // inbound delay-bandwidth product @@ -80,7 +86,7 @@ struct rt_metrics { // The BSDs are lacking `rt_msghdr` in their libc bindings. #[cfg(bsd)] #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] +#[repr(C, align(8))] struct rt_msghdr { rtm_msglen: libc::c_ushort, // to skip over non-understood messages rtm_version: libc::c_uchar, // future binary compatibility @@ -96,7 +102,7 @@ struct rt_msghdr { rtm_rmx: rt_metrics, // metrics themselves } -use crate::{default_err, next_item_aligned_by_four, unlikely_err}; +use crate::{aligned_by, default_err, unlikely_err}; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Open route socket. @@ -207,7 +213,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); } } - let incr = next_item_aligned_by_four(sdl.sdl_len.into()); + let incr = aligned_by(sdl.sdl_len.into(), ALIGN); sa = unsafe { sa.add(incr) }; } } diff --git a/src/lib.rs b/src/lib.rs index b1479ad2..561a4641 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,13 +74,13 @@ fn unlikely_err(msg: String) -> Error { Error::new(ErrorKind::Other, msg) } -/// Align a size to the next multiple of four. +/// Align `size` to the next multiple of `align`. #[cfg(not(target_os = "windows"))] -const fn next_item_aligned_by_four(size: usize) -> usize { +const fn aligned_by(size: usize, align: usize) -> usize { if size == 0 { - 4 + align } else { - (size + 3) & !3 + 1 + ((size - 1) | (align - 1)) } } diff --git a/src/linux.rs b/src/linux.rs index 042fba15..ba89f257 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -21,7 +21,7 @@ use libc::{ RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; -use crate::{default_err, next_item_aligned_by_four, unlikely_err}; +use crate::{aligned_by, default_err, unlikely_err}; const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. @@ -267,7 +267,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> if ifname.is_some() && mtu.is_some() { break 'recv; } - let incr = next_item_aligned_by_four(attr.rta_len as usize); + let incr = aligned_by(attr.rta_len as usize, 4); attr_ptr = unsafe { attr_ptr.add(incr) }; } } From 4691e1819e26d10527665405c219b4f1e4774aff Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 22 Oct 2024 16:59:45 +0300 Subject: [PATCH 072/128] RTAX_IFA --- Cargo.toml | 2 +- src/bsd.rs | 10 +++++++--- src/lib.rs | 5 +++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9052f151..ce201c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed", branch = "main" } [dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock -libc = { version = ">=0.2.161", default-features = false } +libc = { version = ">=0.2.161", default-features = false, features = ["extra_traits"] } [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock diff --git a/src/bsd.rs b/src/bsd.rs index d10047de..c28e6897 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -17,8 +17,8 @@ use std::{ use libc::rt_msghdr; use libc::{ getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, - AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, RTM_VERSION, - SOCK_RAW, + AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, + RTM_VERSION, SOCK_RAW, }; #[cfg(not(target_os = "netbsd"))] @@ -200,14 +200,18 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Parse the route message for the interface name. let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; for i in 0..RTAX_MAX { + eprintln!("i: {i}"); let sdl = unsafe { ptr::read_unaligned(sa.cast::()) }; // Check if the address is present in the message if rtm.rtm_addrs & (1 << i) != 0 { + eprintln!("hit {i} sdl {sdl:?}"); // Check if the address is the interface address - if i == RTAX_IFP { + if (i == RTAX_IFP || i == RTAX_IFA) && sdl.sdl_nlen > 0 { + eprintln!("sdl_len: {}", sdl.sdl_nlen); let name = unsafe { slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen as usize) }; + eprintln!("name: {name:?}"); if let Ok(name) = str::from_utf8(name) { // We have our interface name. return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); diff --git a/src/lib.rs b/src/lib.rs index 561a4641..8da9228d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,13 +74,14 @@ fn unlikely_err(msg: String) -> Error { Error::new(ErrorKind::Other, msg) } -/// Align `size` to the next multiple of `align`. +/// Align `size` to the next multiple of `align` (which needs to be a power of two). #[cfg(not(target_os = "windows"))] const fn aligned_by(size: usize, align: usize) -> usize { if size == 0 { align } else { - 1 + ((size - 1) | (align - 1)) + (size + (align - 1)) & (!(align - 1)) + // 1 + ((size - 1) | (align - 1)) } } From 76d7eb973a2ca2c5765b1b64542b37f9b84df03b Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 22 Oct 2024 18:50:53 +0300 Subject: [PATCH 073/128] All the BSDs are different, sigh --- src/bsd/freebsd.rs | 44 +++++++++++++++++++ src/{bsd.rs => bsd/mod.rs} | 89 +++++--------------------------------- src/bsd/netbsd.rs | 40 +++++++++++++++++ src/bsd/openbsd.rs | 47 ++++++++++++++++++++ 4 files changed, 142 insertions(+), 78 deletions(-) create mode 100644 src/bsd/freebsd.rs rename src/{bsd.rs => bsd/mod.rs} (60%) create mode 100644 src/bsd/netbsd.rs create mode 100644 src/bsd/openbsd.rs diff --git a/src/bsd/freebsd.rs b/src/bsd/freebsd.rs new file mode 100644 index 00000000..d1325850 --- /dev/null +++ b/src/bsd/freebsd.rs @@ -0,0 +1,44 @@ +// 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. + +pub const ALIGN: usize = 4; + +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +pub struct rt_metrics { + pub rmx_locks: libc::c_ulong, // Kernel must leave these values alone + pub rmx_mtu: libc::c_ulong, // MTU for this path + pub rmx_hopcount: libc::c_ulong, // max hops expected + pub rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect + pub rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product + pub rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product + pub rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit + pub rmx_rtt: libc::c_ulong, // estimated round trip time + pub rmx_rttvar: libc::c_ulong, // estimated rtt variance + pub rmx_pksent: libc::c_ulong, // packets sent using this route + pub rmx_weight: libc::u_long, // route weight + pub rmx_nhidx: libc::u_long, // route nexhop index + pub rmx_filler: [libc::c_ulong; 2], // will be used for T/TCP later +} + +// The BSDs are lacking `rt_msghdr` in their libc bindings. +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +pub struct rt_msghdr { + pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages + pub rtm_version: libc::c_uchar, // future binary compatibility + pub rtm_type: libc::c_uchar, // message type + pub rtm_index: libc::c_ushort, // index for associated ifp + pub _rtm_spare1: libc::c_ushort, + pub rtm_flags: libc::c_int, // flags, incl. kern & message, e.g. DONE + pub rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg + pub rtm_pid: libc::pid_t, // identify sender + pub rtm_seq: libc::c_int, // for sender to identify action + pub rtm_errno: libc::c_int, // why failed + pub rtm_fmask: libc::c_int, // bitmask used in RTM_CHANGE message + pub rtm_inits: libc::u_long, // which metrics we are initializing + pub rtm_rmx: rt_metrics, // metrics themselves +} diff --git a/src/bsd.rs b/src/bsd/mod.rs similarity index 60% rename from src/bsd.rs rename to src/bsd/mod.rs index c28e6897..be293854 100644 --- a/src/bsd.rs +++ b/src/bsd/mod.rs @@ -13,7 +13,7 @@ use std::{ ptr, slice, str, }; -#[cfg(not(bsd))] +#[cfg(apple)] use libc::rt_msghdr; use libc::{ getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, @@ -21,86 +21,18 @@ use libc::{ RTM_VERSION, SOCK_RAW, }; -#[cfg(not(target_os = "netbsd"))] -const ALIGN: usize = 4; - -#[cfg(target_os = "netbsd")] -const ALIGN: usize = 8; - -// The BSDs are lacking `rt_metrics` in their libc bindings. -// And of course they are all slightly different. #[cfg(target_os = "freebsd")] -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] -struct rt_metrics { - rmx_locks: libc::c_ulong, // Kernel must leave these values alone - rmx_mtu: libc::c_ulong, // MTU for this path - rmx_hopcount: libc::c_ulong, // max hops expected - rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect - rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product - rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product - rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit - rmx_rtt: libc::c_ulong, // estimated round trip time - rmx_rttvar: libc::c_ulong, // estimated rtt variance - rmx_pksent: libc::c_ulong, // packets sent using this route - rmx_weight: libc::u_long, // route weight - rmx_nhidx: libc::u_long, // route nexhop index - rmx_filler: [libc::c_ulong; 2], // will be used for T/TCP later -} - +use crate::bsd::freebsd::{rt_msghdr, ALIGN}; #[cfg(target_os = "netbsd")] -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] -struct rt_metrics { - rmx_locks: u64, // Kernel must leave these values alone - rmx_mtu: u64, // MTU for this path - rmx_hopcount: u64, // max hops expected - rmx_recvpipe: u64, // inbound delay-bandwidth product - rmx_sendpipe: u64, // outbound delay-bandwidth product - rmx_ssthresh: u64, // outbound gateway buffer limit - rmx_rtt: u64, // estimated round trip time - rmx_rttvar: u64, // estimated rtt variance - rmx_expire: libc::time_t, // lifetime for route, e.g. redirect - rmx_pksent: libc::time_t, // packets sent using this route -} - +use crate::bsd::netbsd::{rt_msghdr, ALIGN}; #[cfg(target_os = "openbsd")] -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] -struct rt_metrics { - rmx_pksent: u64, // packets sent using this route - rmx_expire: i64, // lifetime for route, e.g. redirect - rmx_locks: libc::c_uint, // Kernel must leave these values - rmx_mtu: libc::c_uint, // MTU for this path - rmx_refcnt: libc::c_uint, // # references hold - // some apps may still need these no longer used metrics - rmx_hopcount: libc::c_uint, // max hops expected - rmx_recvpipe: libc::c_uint, // inbound delay-bandwidth product - rmx_sendpipe: libc::c_uint, // outbound delay-bandwidth product - rmx_ssthresh: libc::c_uint, // outbound gateway buffer limit - rmx_rtt: libc::c_uint, // estimated round trip time - rmx_rttvar: libc::c_uint, // estimated rtt variance - rmx_pad: libc::c_uint, -} +use crate::bsd::openbsd::{rt_msghdr, ALIGN}; -// The BSDs are lacking `rt_msghdr` in their libc bindings. -#[cfg(bsd)] -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] -struct rt_msghdr { - rtm_msglen: libc::c_ushort, // to skip over non-understood messages - rtm_version: libc::c_uchar, // future binary compatibility - rtm_type: libc::c_uchar, // message type - rtm_index: libc::c_ushort, // index for associated ifp - rtm_flags: libc::c_int, // flags, incl kern & message, e.g. DONE - rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg - rtm_pid: libc::pid_t, // identify sender - rtm_seq: libc::c_int, // for sender to identify action - rtm_errno: libc::c_int, // why failed - rtm_use: libc::c_int, // from rtentry - rtm_inits: libc::c_ulong, // which metrics we are initializing - rtm_rmx: rt_metrics, // metrics themselves -} +#[cfg(target_os = "freebsd")] +mod freebsd; + +#[cfg(apple)] +const ALIGN: usize = 4; use crate::{aligned_by, default_err, unlikely_err}; @@ -177,7 +109,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> 0u8; size_of::() + // There will never be `RTAX_MAX` sockaddrs attached, but it's a safe upper bound. - (RTAX_MAX as usize * size_of::()) + (RTAX_MAX as usize * size_of::() * 10) ]; let rtm = loop { let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; @@ -191,6 +123,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? && rtm.rtm_pid == unsafe { getpid() } && rtm.rtm_seq == fd.as_raw_fd() + && rtm.rtm_rmx.rmx_mtu > 0 { // This is the response we are looking for. break rtm; diff --git a/src/bsd/netbsd.rs b/src/bsd/netbsd.rs new file mode 100644 index 00000000..07645e57 --- /dev/null +++ b/src/bsd/netbsd.rs @@ -0,0 +1,40 @@ +// 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. + +pub const ALIGN: usize = 8; + +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C, align(8))] +pub struct rt_metrics { + pub rmx_locks: u64, // Kernel must leave these values alone + pub rmx_mtu: u64, // MTU for this path + pub rmx_hopcount: u64, // max hops expected + pub rmx_recvpipe: u64, // inbound delay-bandwidth product + pub rmx_sendpipe: u64, // outbound delay-bandwidth product + pub rmx_ssthresh: u64, // outbound gateway buffer limit + pub rmx_rtt: u64, // estimated round trip time + pub rmx_rttvar: u64, // estimated rtt variance + pub rmx_expire: libc::time_t, // lifetime for route, e.g. redirect + pub rmx_pksent: libc::time_t, // packets sent using this route +} + +// The BSDs are lacking `rt_msghdr` in their libc bindings. +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C, align(8))] +pub struct rt_msghdr { + pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages + pub rtm_version: libc::c_uchar, // future binary compatibility + pub rtm_type: libc::c_uchar, // message type + pub rtm_index: libc::c_ushort, // index for associated ifp + pub rtm_flags: libc::c_int, // flags, incl. kern & message, e.g. DONE + pub rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg + pub rtm_pid: libc::pid_t, // identify sender + pub rtm_seq: libc::c_int, // for sender to identify action + pub rtm_errno: libc::c_int, // why failed + pub rtm_use: libc::c_int, // from rtentry + pub rtm_inits: libc::c_int, // which metrics we are initializing + pub rtm_rmx: rt_metrics, // metrics themselves +} diff --git a/src/bsd/openbsd.rs b/src/bsd/openbsd.rs new file mode 100644 index 00000000..46222a50 --- /dev/null +++ b/src/bsd/openbsd.rs @@ -0,0 +1,47 @@ +// 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. + +pub const ALIGN: usize = 4; + +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +pub struct rt_metrics { + pub rmx_pksent: u64, // packets sent using this route + pub rmx_expire: i64, // lifetime for route, e.g. redirect + pub rmx_locks: libc::c_uint, // Kernel must leave these values + pub rmx_mtu: libc::c_uint, // MTU for this path + pub rmx_refcnt: libc::c_uint, // # references hold + // some apps may still need these no longer used metrics + pub rmx_hopcount: libc::c_uint, // max hops expected + pub rmx_recvpipe: libc::c_uint, // inbound delay-bandwidth product + pub rmx_sendpipe: libc::c_uint, // outbound delay-bandwidth product + pub rmx_ssthresh: libc::c_uint, // outbound gateway buffer limit + pub rmx_rtt: libc::c_uint, // estimated round trip time + pub rmx_rttvar: libc::c_uint, // estimated rtt variance + pub rmx_pad: libc::c_uint, +} + +// The BSDs are lacking `rt_msghdr` in their libc bindings. +#[allow(non_camel_case_types, clippy::struct_field_names)] +#[repr(C)] +pub struct rt_msghdr { + pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages + pub rtm_version: libc::c_uchar, // future binary compatibility + pub rtm_type: libc::c_uchar, // message type + pub rtm_hdrlen: libc::c_ushort, // sizeof(rt_msghdr) to skip over the header + pub rtm_index: libc::c_ushort, // index for associated ifp + pub rtm_tableid: libc::c_ushort, // routing table id + pub rtm_priority: libc::c_uchar, // routing priority + pub rtm_mpls: libc::c_uchar, // MPLS additional infos + pub rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg + pub rtm_flags: libc::c_int, // flags, incl. kern & message, e.g. DONE + pub rtm_fmask: libc::c_int, // bitmask used in RTM_CHANGE message + pub rtm_pid: libc::pid_t, // identify sender + pub rtm_seq: libc::c_int, // for sender to identify action + pub rtm_errno: libc::c_int, // why failed + pub rtm_inits: libc::c_uint, // which metrics we are initializing + pub rtm_rmx: rt_metrics, // metrics themselves +} From 8f71dea436e6614dd601b84876d181866cb02565 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 23 Oct 2024 18:41:01 +0300 Subject: [PATCH 074/128] More --- Cargo.toml | 2 +- src/bsd/apple.rs | 10 +++ src/bsd/freebsd.rs | 1 - src/bsd/mod.rs | 150 ++++++++++++++++++++++++++++++++------------- src/bsd/netbsd.rs | 5 +- src/bsd/openbsd.rs | 3 +- src/lib.rs | 24 +++----- 7 files changed, 131 insertions(+), 64 deletions(-) create mode 100644 src/bsd/apple.rs diff --git a/Cargo.toml b/Cargo.toml index ce201c60..9052f151 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed", branch = "main" } [dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock -libc = { version = ">=0.2.161", default-features = false, features = ["extra_traits"] } +libc = { version = ">=0.2.161", default-features = false } [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock diff --git a/src/bsd/apple.rs b/src/bsd/apple.rs new file mode 100644 index 00000000..691a0d6e --- /dev/null +++ b/src/bsd/apple.rs @@ -0,0 +1,10 @@ +// 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. + +pub const ALIGN: usize = 4; + +#[allow(non_camel_case_types)] +pub type rt_msghdr = libc::rt_msghdr; diff --git a/src/bsd/freebsd.rs b/src/bsd/freebsd.rs index d1325850..6496868b 100644 --- a/src/bsd/freebsd.rs +++ b/src/bsd/freebsd.rs @@ -24,7 +24,6 @@ pub struct rt_metrics { pub rmx_filler: [libc::c_ulong; 2], // will be used for T/TCP later } -// The BSDs are lacking `rt_msghdr` in their libc bindings. #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] pub struct rt_msghdr { diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index be293854..94fe772c 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -5,6 +5,7 @@ // except according to those terms. use std::{ + ffi::CStr, io::Error, mem::{self, size_of}, net::IpAddr, @@ -13,14 +14,16 @@ use std::{ ptr, slice, str, }; -#[cfg(apple)] -use libc::rt_msghdr; +#[cfg(not(target_os = "openbsd"))] +use libc::RTA_IFP; use libc::{ - getpid, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, - AF_INET6, AF_UNSPEC, PF_ROUTE, RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTA_IFP, RTM_GET, - RTM_VERSION, SOCK_RAW, + freeifaddrs, getifaddrs, getpid, if_data, ifaddrs, read, sockaddr_dl, sockaddr_in, + sockaddr_in6, sockaddr_storage, socket, write, AF_INET, AF_INET6, AF_LINK, AF_UNSPEC, PF_ROUTE, + RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTM_GET, RTM_VERSION, SOCK_RAW, }; +#[cfg(apple)] +use crate::bsd::apple::{rt_msghdr, ALIGN}; #[cfg(target_os = "freebsd")] use crate::bsd::freebsd::{rt_msghdr, ALIGN}; #[cfg(target_os = "netbsd")] @@ -28,25 +31,54 @@ use crate::bsd::netbsd::{rt_msghdr, ALIGN}; #[cfg(target_os = "openbsd")] use crate::bsd::openbsd::{rt_msghdr, ALIGN}; +#[cfg(apple)] +mod apple; + #[cfg(target_os = "freebsd")] mod freebsd; -#[cfg(apple)] -const ALIGN: usize = 4; +#[cfg(target_os = "netbsd")] +mod netbsd; + +#[cfg(target_os = "openbsd")] +mod openbsd; use crate::{aligned_by, default_err, unlikely_err}; -pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { - // Open route socket. - let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; - if fd == -1 { +fn get_mtu_for_interface(name: &str) -> Result { + let mut ifap: *mut ifaddrs = ptr::null_mut(); + if unsafe { getifaddrs(&mut ifap) } != 0 { return Err(Error::last_os_error()); } - let fd = unsafe { OwnedFd::from_raw_fd(fd) }; + let ifap = ifap; // Do not modify this pointer. + let mut res = Err(default_err()); - // Prepare buffer with destination `sockaddr`. + 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 ifa_name = unsafe { CStr::from_ptr(ifa.ifa_name).to_str().unwrap_or_default() }; + if libc::c_int::from(saddr.sa_family) == AF_LINK + && !ifa.ifa_data.is_null() + && ifa_name == name + { + let data = unsafe { &*(ifa.ifa_data as *const if_data) }; + if let Ok(mtu) = usize::try_from(data.ifi_mtu) { + res = Ok(mtu); + break; + } + } + } + cursor = ifa.ifa_next; + } + unsafe { freeifaddrs(ifap) }; + res +} + +fn as_sockaddr_storage(ip: IpAddr) -> Result { let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; - match remote { + match ip { IpAddr::V4(ip) => { let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin.sin_len = size_of::() @@ -68,33 +100,56 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> sin6.sin6_addr.s6_addr = ip.octets(); } }; + Ok(dst) +} + +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + // Open route socket. + let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; + if fd == -1 { + return Err(Error::last_os_error()); + } + let fd = unsafe { OwnedFd::from_raw_fd(fd) }; + + // Prepare buffer with destination `sockaddr`. + let dst = as_sockaddr_storage(remote)?; // Prepare route message structure. - let mut rtm: rt_msghdr = unsafe { mem::zeroed() }; - rtm.rtm_msglen = (size_of::() + dst.ss_len as usize) // Length includes sockaddr + let mut query: rt_msghdr = unsafe { mem::zeroed() }; + query.rtm_msglen = (size_of::() + aligned_by(dst.ss_len.into(), ALIGN)) // Length includes sockaddr .try_into() .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - rtm.rtm_version = RTM_VERSION + query.rtm_version = RTM_VERSION .try_into() .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - rtm.rtm_type = RTM_GET + query.rtm_type = RTM_GET .try_into() .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - rtm.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique - rtm.rtm_addrs = RTA_DST | RTA_IFP; // Query for destination and obtain interface info + query.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique + query.rtm_addrs = RTA_DST; // Query for destination + #[cfg(not(target_os = "openbsd"))] + { + query.rtm_addrs |= RTA_IFP; // Obtain interface info + } + #[cfg(target_os = "openbsd")] + { + query.rtm_hdrlen = size_of::() + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + } // Copy route message and destination `sockaddr` into message buffer. - let mut msg: Vec = vec![0; rtm.rtm_msglen as usize]; + let mut msg: Vec = vec![0; query.rtm_msglen.into()]; unsafe { ptr::copy_nonoverlapping( - ptr::from_ref(&rtm).cast(), + ptr::from_ref(&query).cast(), msg.as_mut_ptr(), size_of::(), ); ptr::copy_nonoverlapping( ptr::from_ref(&dst).cast(), msg.as_mut_ptr().add(size_of::()), - dst.ss_len as usize, + dst.ss_len.into(), ); } @@ -109,45 +164,54 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> 0u8; size_of::() + // There will never be `RTAX_MAX` sockaddrs attached, but it's a safe upper bound. - (RTAX_MAX as usize * size_of::() * 10) + (RTAX_MAX as usize * size_of::()) ]; let rtm = loop { let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { return Err(Error::last_os_error()); } - let rtm = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; - if rtm.rtm_type - == RTM_GET - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? - && rtm.rtm_pid == unsafe { getpid() } - && rtm.rtm_seq == fd.as_raw_fd() - && rtm.rtm_rmx.rmx_mtu > 0 + let reply = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; + if reply.rtm_version == query.rtm_version + && reply.rtm_type == query.rtm_type + && reply.rtm_pid == unsafe { getpid() } + && reply.rtm_seq == query.rtm_seq { - // This is the response we are looking for. - break rtm; + // This is the reply we are looking for. + break reply; } }; // Parse the route message for the interface name. let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; for i in 0..RTAX_MAX { - eprintln!("i: {i}"); let sdl = unsafe { ptr::read_unaligned(sa.cast::()) }; // Check if the address is present in the message if rtm.rtm_addrs & (1 << i) != 0 { - eprintln!("hit {i} sdl {sdl:?}"); // Check if the address is the interface address - if (i == RTAX_IFP || i == RTAX_IFA) && sdl.sdl_nlen > 0 { - eprintln!("sdl_len: {}", sdl.sdl_nlen); - let name = unsafe { - slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen as usize) + if (i == RTAX_IFP || i == RTAX_IFA) + && sdl.sdl_family + == AF_LINK + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? + && sdl.sdl_nlen > 0 + { + let if_name = unsafe { + slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen.into()) }; - eprintln!("name: {name:?}"); - if let Ok(name) = str::from_utf8(name) { + if let Ok(if_name) = str::from_utf8(if_name) { // We have our interface name. - return Ok((name.to_string(), rtm.rtm_rmx.rmx_mtu as usize)); + // If rtm.rtm_rmx.rmx_mtu is 0, which can happen on OpenBSD and NetBSD, we need + // to get the MTU via different means based on the interface name. + let mtu = if rtm.rtm_rmx.rmx_mtu > 0 { + rtm.rtm_rmx + .rmx_mtu + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? + } else { + get_mtu_for_interface(if_name)? + }; + return Ok((if_name.to_string(), mtu)); } } let incr = aligned_by(sdl.sdl_len.into(), ALIGN); diff --git a/src/bsd/netbsd.rs b/src/bsd/netbsd.rs index 07645e57..78e75ff3 100644 --- a/src/bsd/netbsd.rs +++ b/src/bsd/netbsd.rs @@ -7,7 +7,7 @@ pub const ALIGN: usize = 8; #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] +#[repr(C)] pub struct rt_metrics { pub rmx_locks: u64, // Kernel must leave these values alone pub rmx_mtu: u64, // MTU for this path @@ -21,9 +21,8 @@ pub struct rt_metrics { pub rmx_pksent: libc::time_t, // packets sent using this route } -// The BSDs are lacking `rt_msghdr` in their libc bindings. #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] +#[repr(C)] pub struct rt_msghdr { pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages pub rtm_version: libc::c_uchar, // future binary compatibility diff --git a/src/bsd/openbsd.rs b/src/bsd/openbsd.rs index 46222a50..b51ea617 100644 --- a/src/bsd/openbsd.rs +++ b/src/bsd/openbsd.rs @@ -4,7 +4,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -pub const ALIGN: usize = 4; +pub const ALIGN: usize = 8; #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] @@ -24,7 +24,6 @@ pub struct rt_metrics { pub rmx_pad: libc::c_uint, } -// The BSDs are lacking `rt_msghdr` in their libc bindings. #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] pub struct rt_msghdr { diff --git a/src/lib.rs b/src/lib.rs index 8da9228d..ead0fc21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,8 +80,7 @@ const fn aligned_by(size: usize, align: usize) -> usize { if size == 0 { align } else { - (size + (align - 1)) & (!(align - 1)) - // 1 + ((size - 1) | (align - 1)) + 1 + ((size - 1) | (align - 1)) } } @@ -150,26 +149,23 @@ mod test { #[test] fn inet_v4() { - // cloudflare.com assert_eq!( - interface_and_mtu(IpAddr::V4(Ipv4Addr::new(104, 16, 132, 229))).unwrap(), + interface_and_mtu(IpAddr::V4(Ipv4Addr::new( + 104, 16, 132, 229 // cloudflare.com + ))) + .unwrap(), INET ); } #[test] fn inet_v6() { - if env::var("GITHUB_ACTIONS").is_ok() { + match interface_and_mtu(IpAddr::V6(Ipv6Addr::new( + 0x2606, 0x4700, 0, 0, 0, 0, 0x6810, 0x84e5, // cloudflare.com + ))) { + Ok(res) => assert_eq!(res, INET), // The GitHub CI environment does not have IPv6 connectivity. - return; + Err(_) => assert!(env::var("GITHUB_ACTIONS").is_ok()), } - // cloudflare.com - assert_eq!( - interface_and_mtu(IpAddr::V6(Ipv6Addr::new( - 0x2606, 0x4700, 0, 0, 0, 0, 0x6810, 0x84e5, - ))) - .unwrap(), - INET - ); } } From 63ed25f79fa4bc297aaff93c8f97c20aa53b9ed5 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 23 Oct 2024 18:48:15 +0300 Subject: [PATCH 075/128] codecov --- .github/workflows/check.yml | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6d20568e..d735e295 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -256,53 +256,66 @@ jobs: uses: vmactions/freebsd-vm@c96341966a3954a757e6ea41692f7c7b32312a0c with: usesh: true - copyback: false envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | mkdir -p /usr/local/etc/pkg/repos sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf pkg update pkg install -y rust + cargo install cargo-quickinstall + cargo quickinstall cargo-llvm-cov run: | ifconfig cargo check --all-targets - RUST_LOG=trace cargo test --no-fail-fast + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'openbsd' uses: vmactions/openbsd-vm@b526dc847c977759bb655a756b14341e85a72e84 with: usesh: true - copyback: false envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | pkg_add rust + cargo install cargo-quickinstall + cargo quickinstall cargo-llvm-cov run: | ifconfig cargo check --all-targets - RUST_LOG=trace cargo test --no-fail-fast + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'netbsd' uses: vmactions/netbsd-vm@7c9086fdb4cc1aa814cda6e305390c2b966551a9 with: usesh: true - copyback: false envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | /usr/sbin/pkg_add rust + cargo install cargo-quickinstall + cargo quickinstall cargo-llvm-cov run: | /sbin/ifconfig cargo check --all-targets - RUST_LOG=trace cargo test --no-fail-fast + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'solaris' uses: vmactions/solaris-vm@a89b9438868c70db27e41625f0a5de6ff5e90809 with: usesh: true - copyback: false envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | pkg install cargo + cargo install cargo-quickinstall + cargo quickinstall cargo-llvm-cov run: | ifconfig cargo check --all-targets - RUST_LOG=trace cargo test --no-fail-fast + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + + - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 + with: + file: lcov.info + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 5155ee71e32698f234e406b0e72fc34eb80aa434 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 23 Oct 2024 18:54:00 +0300 Subject: [PATCH 076/128] Again --- .github/workflows/check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d735e295..4eb181b6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -262,6 +262,7 @@ jobs: sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf pkg update pkg install -y rust + export PATH="/root/.cargo/bin:$PATH" cargo install cargo-quickinstall cargo quickinstall cargo-llvm-cov run: | @@ -276,6 +277,7 @@ jobs: envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | pkg_add rust + export PATH="/root/.cargo/bin:$PATH" cargo install cargo-quickinstall cargo quickinstall cargo-llvm-cov run: | @@ -290,6 +292,7 @@ jobs: envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | /usr/sbin/pkg_add rust + export PATH="/root/.cargo/bin:$PATH" cargo install cargo-quickinstall cargo quickinstall cargo-llvm-cov run: | @@ -304,6 +307,7 @@ jobs: envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | pkg install cargo + export PATH="/root/.cargo/bin:$PATH" cargo install cargo-quickinstall cargo quickinstall cargo-llvm-cov run: | From 33e9a9bd22b7fa43680c734758c0b261423869fb Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 23 Oct 2024 19:01:02 +0300 Subject: [PATCH 077/128] binstall --- .github/workflows/check.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4eb181b6..035f6b6b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -263,8 +263,8 @@ jobs: pkg update pkg install -y rust export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-quickinstall - cargo quickinstall cargo-llvm-cov + cargo install cargo-binstall + cargo binstall cargo-llvm-cov run: | ifconfig cargo check --all-targets @@ -278,8 +278,8 @@ jobs: prepare: | pkg_add rust export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-quickinstall - cargo quickinstall cargo-llvm-cov + cargo install cargo-binstall + cargo binstall cargo-llvm-cov run: | ifconfig cargo check --all-targets @@ -293,8 +293,8 @@ jobs: prepare: | /usr/sbin/pkg_add rust export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-quickinstall - cargo quickinstall cargo-llvm-cov + cargo install cargo-binstall + cargo binstall cargo-llvm-cov run: | /sbin/ifconfig cargo check --all-targets @@ -308,8 +308,8 @@ jobs: prepare: | pkg install cargo export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-quickinstall - cargo quickinstall cargo-llvm-cov + cargo install cargo-binstall + cargo binstall cargo-llvm-cov run: | ifconfig cargo check --all-targets From a873c4332c571595ca5f8093a4ce0b51ac1e4732 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 23 Oct 2024 19:08:41 +0300 Subject: [PATCH 078/128] cargo-llvm-cov --- .github/workflows/check.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 035f6b6b..9ff7a8be 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -261,10 +261,7 @@ jobs: mkdir -p /usr/local/etc/pkg/repos sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf pkg update - pkg install -y rust - export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-binstall - cargo binstall cargo-llvm-cov + pkg install -y rust cargo-llvm-cov run: | ifconfig cargo check --all-targets From c9a8d5be827b367df36e55d6c643d5e8be4931e7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 23 Oct 2024 19:14:46 +0300 Subject: [PATCH 079/128] Again --- .github/workflows/check.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9ff7a8be..f27773ec 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -275,8 +275,7 @@ jobs: prepare: | pkg_add rust export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-binstall - cargo binstall cargo-llvm-cov + cargo install cargo-llvm-cov run: | ifconfig cargo check --all-targets @@ -290,8 +289,7 @@ jobs: prepare: | /usr/sbin/pkg_add rust export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-binstall - cargo binstall cargo-llvm-cov + cargo install cargo-llvm-cov run: | /sbin/ifconfig cargo check --all-targets @@ -305,8 +303,7 @@ jobs: prepare: | pkg install cargo export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-binstall - cargo binstall cargo-llvm-cov + cargo install cargo-llvm-cov run: | ifconfig cargo check --all-targets From b667a8630c34271dbd9a3dd23bb7f2d0545cbf55 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 08:47:32 +0300 Subject: [PATCH 080/128] llvm --- .github/workflows/check.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f27773ec..3167edbd 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -265,7 +265,8 @@ jobs: run: | ifconfig cargo check --all-targets - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + LLVM_COV=/usr/bin/llvm-cov LLVM_PROFDATA=/usr/bin/llvm-profdata RUST_LOG=trace \ + cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'openbsd' uses: vmactions/openbsd-vm@b526dc847c977759bb655a756b14341e85a72e84 @@ -279,7 +280,8 @@ jobs: run: | ifconfig cargo check --all-targets - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + LLVM_COV=/usr/bin/llvm-cov LLVM_PROFDATA=/usr/bin/llvm-profdata RUST_LOG=trace \ + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'netbsd' uses: vmactions/netbsd-vm@7c9086fdb4cc1aa814cda6e305390c2b966551a9 @@ -293,7 +295,8 @@ jobs: run: | /sbin/ifconfig cargo check --all-targets - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + LLVM_COV=/usr/pkg/bin/llvm-cov LLVM_PROFDATA=/usr/pkg/bin/llvm-profdata RUST_LOG=trace \ + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'solaris' uses: vmactions/solaris-vm@a89b9438868c70db27e41625f0a5de6ff5e90809 From 42b97478c4fd7afd0757f371c2bcf36b23bf7699 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 10:17:41 +0300 Subject: [PATCH 081/128] Minimize conversions --- .github/workflows/check.yml | 4 +- Cargo.toml | 3 +- src/bsd/apple.rs | 4 ++ src/bsd/freebsd.rs | 4 ++ src/bsd/mod.rs | 112 ++++++++++++++++++------------------ src/bsd/netbsd.rs | 4 ++ src/bsd/openbsd.rs | 4 ++ src/linux.rs | 97 +++++++++++++++++-------------- 8 files changed, 131 insertions(+), 101 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3167edbd..174b65ce 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -261,7 +261,7 @@ jobs: mkdir -p /usr/local/etc/pkg/repos sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf pkg update - pkg install -y rust cargo-llvm-cov + pkg install -y rust-nightly cargo-llvm-cov run: | ifconfig cargo check --all-targets @@ -289,7 +289,7 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - /usr/sbin/pkg_add rust + /usr/sbin/pkg_add rust-bin export PATH="/root/.cargo/bin:$PATH" cargo install cargo-llvm-cov run: | diff --git a/Cargo.toml b/Cargo.toml index 9052f151..bc8ce3b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ maintenance = { status = "actively-developed", branch = "main" } [dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock -libc = { version = ">=0.2.161", default-features = false } +libc = { version = ">=0.2.161", default-features = false} +static_assertions = { version = "1.1", default-features = false } [target."cfg(windows)".dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock diff --git a/src/bsd/apple.rs b/src/bsd/apple.rs index 691a0d6e..ca533c37 100644 --- a/src/bsd/apple.rs +++ b/src/bsd/apple.rs @@ -4,7 +4,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use libc::{RTA_DST, RTA_IFP}; + pub const ALIGN: usize = 4; +pub const RTM_ADDRS: i32 = RTA_DST | RTA_IFP; + #[allow(non_camel_case_types)] pub type rt_msghdr = libc::rt_msghdr; diff --git a/src/bsd/freebsd.rs b/src/bsd/freebsd.rs index 6496868b..e05823a7 100644 --- a/src/bsd/freebsd.rs +++ b/src/bsd/freebsd.rs @@ -4,8 +4,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use libc::{RTA_DST, RTA_IFP}; + pub const ALIGN: usize = 4; +pub const RTM_ADDRS: i32 = RTA_DST | RTA_IFP; + #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] pub struct rt_metrics { diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index 94fe772c..bf9f018b 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -14,22 +14,21 @@ use std::{ ptr, slice, str, }; -#[cfg(not(target_os = "openbsd"))] -use libc::RTA_IFP; use libc::{ freeifaddrs, getifaddrs, getpid, if_data, ifaddrs, read, sockaddr_dl, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, AF_INET6, AF_LINK, AF_UNSPEC, PF_ROUTE, - RTAX_IFA, RTAX_IFP, RTAX_MAX, RTA_DST, RTM_GET, RTM_VERSION, SOCK_RAW, + RTAX_IFA, RTAX_IFP, RTAX_MAX, RTM_GET, RTM_VERSION, SOCK_RAW, }; +use static_assertions::{const_assert, const_assert_eq}; #[cfg(apple)] -use crate::bsd::apple::{rt_msghdr, ALIGN}; +use crate::bsd::apple::{rt_msghdr, ALIGN, RTM_ADDRS}; #[cfg(target_os = "freebsd")] -use crate::bsd::freebsd::{rt_msghdr, ALIGN}; +use crate::bsd::freebsd::{rt_msghdr, ALIGN, RTM_ADDRS}; #[cfg(target_os = "netbsd")] -use crate::bsd::netbsd::{rt_msghdr, ALIGN}; +use crate::bsd::netbsd::{rt_msghdr, ALIGN, RTM_ADDRS}; #[cfg(target_os = "openbsd")] -use crate::bsd::openbsd::{rt_msghdr, ALIGN}; +use crate::bsd::openbsd::{rt_msghdr, ALIGN, RTM_ADDRS}; #[cfg(apple)] mod apple; @@ -45,6 +44,30 @@ mod openbsd; use crate::{aligned_by, default_err, unlikely_err}; +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const AF_INET_U8: u8 = AF_INET as u8; +const_assert_eq!(AF_INET_U8 as i32, AF_INET); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const AF_INET6_U8: u8 = AF_INET6 as u8; +const_assert_eq!(AF_INET6_U8 as i32, AF_INET6); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const AF_LINK_U8: u8 = AF_LINK as u8; +const_assert_eq!(AF_LINK_U8 as i32, AF_LINK); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const RTM_VERSION_U8: u8 = RTM_VERSION as u8; +const_assert_eq!(RTM_VERSION_U8 as i32, RTM_VERSION); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const RTM_GET_U8: u8 = RTM_GET as u8; +const_assert_eq!(RTM_GET_U8 as i32, RTM_GET); + +const_assert!(size_of::() <= u8::MAX as usize); +const_assert!(size_of::() <= u8::MAX as usize); +const_assert!(size_of::() <= u8::MAX as usize); + fn get_mtu_for_interface(name: &str) -> Result { let mut ifap: *mut ifaddrs = ptr::null_mut(); if unsafe { getifaddrs(&mut ifap) } != 0 { @@ -53,54 +76,45 @@ fn get_mtu_for_interface(name: &str) -> Result { let ifap = ifap; // Do not modify this pointer. let mut res = Err(default_err()); - let mut cursor = ifap; - while !cursor.is_null() { - let ifa = unsafe { &*cursor }; + let mut ifa_next = ifap; + while !ifa_next.is_null() { + let ifa = unsafe { &*ifa_next }; if !ifa.ifa_addr.is_null() { - let saddr = unsafe { &*ifa.ifa_addr }; + let ifa_addr = unsafe { &*ifa.ifa_addr }; let ifa_name = unsafe { CStr::from_ptr(ifa.ifa_name).to_str().unwrap_or_default() }; - if libc::c_int::from(saddr.sa_family) == AF_LINK - && !ifa.ifa_data.is_null() - && ifa_name == name - { - let data = unsafe { &*(ifa.ifa_data as *const if_data) }; - if let Ok(mtu) = usize::try_from(data.ifi_mtu) { + if ifa_addr.sa_family == AF_LINK_U8 && !ifa.ifa_data.is_null() && ifa_name == name { + let ifa_data = unsafe { &*(ifa.ifa_data as *const if_data) }; + if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { res = Ok(mtu); break; } } } - cursor = ifa.ifa_next; + ifa_next = ifa.ifa_next; } unsafe { freeifaddrs(ifap) }; res } -fn as_sockaddr_storage(ip: IpAddr) -> Result { +fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; match ip { + #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. IpAddr::V4(ip) => { let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; - sin.sin_len = size_of::() - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - sin.sin_family = AF_INET - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + sin.sin_len = size_of::() as u8; + sin.sin_family = AF_INET_U8; sin.sin_addr.s_addr = u32::from_ne_bytes(ip.octets()); } + #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. IpAddr::V6(ip) => { let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; - sin6.sin6_len = size_of::() - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - sin6.sin6_family = AF_INET6 - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + sin6.sin6_len = size_of::() as u8; + sin6.sin6_family = AF_INET6_U8; sin6.sin6_addr.s6_addr = ip.octets(); } }; - Ok(dst) + dst } pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { @@ -112,30 +126,21 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let fd = unsafe { OwnedFd::from_raw_fd(fd) }; // Prepare buffer with destination `sockaddr`. - let dst = as_sockaddr_storage(remote)?; + let dst = as_sockaddr_storage(remote); // Prepare route message structure. let mut query: rt_msghdr = unsafe { mem::zeroed() }; - query.rtm_msglen = (size_of::() + aligned_by(dst.ss_len.into(), ALIGN)) // Length includes sockaddr - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - query.rtm_version = RTM_VERSION - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; - query.rtm_type = RTM_GET - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + #[allow(clippy::cast_possible_truncation)] + // Structs len is <= u8::MAX per `const_assert!`s above; `aligned_by` returns max. 16 for IPv6. + let rtm_msglen = (size_of::() + aligned_by(dst.ss_len.into(), ALIGN)) as u16; // Length includes sockaddr + query.rtm_msglen = rtm_msglen; + query.rtm_version = RTM_VERSION_U8; + query.rtm_type = RTM_GET_U8; query.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique - query.rtm_addrs = RTA_DST; // Query for destination - #[cfg(not(target_os = "openbsd"))] - { - query.rtm_addrs |= RTA_IFP; // Obtain interface info - } + query.rtm_addrs = RTM_ADDRS; #[cfg(target_os = "openbsd")] { - query.rtm_hdrlen = size_of::() - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + query.rtm_hdrlen = size_of::() as libc::c_ushort; } // Copy route message and destination `sockaddr` into message buffer. @@ -189,12 +194,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Check if the address is present in the message if rtm.rtm_addrs & (1 << i) != 0 { // Check if the address is the interface address - if (i == RTAX_IFP || i == RTAX_IFA) - && sdl.sdl_family - == AF_LINK - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? - && sdl.sdl_nlen > 0 + if (i == RTAX_IFP || i == RTAX_IFA) && sdl.sdl_family == AF_LINK_U8 && sdl.sdl_nlen > 0 { let if_name = unsafe { slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen.into()) diff --git a/src/bsd/netbsd.rs b/src/bsd/netbsd.rs index 78e75ff3..2281fd5b 100644 --- a/src/bsd/netbsd.rs +++ b/src/bsd/netbsd.rs @@ -4,8 +4,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use libc::{RTA_DST, RTA_IFP}; + pub const ALIGN: usize = 8; +pub const RTM_ADDRS: i32 = RTA_DST | RTA_IFP; + #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] pub struct rt_metrics { diff --git a/src/bsd/openbsd.rs b/src/bsd/openbsd.rs index b51ea617..f312ccf0 100644 --- a/src/bsd/openbsd.rs +++ b/src/bsd/openbsd.rs @@ -4,8 +4,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use libc::RTA_DST; + pub const ALIGN: usize = 8; +pub const RTM_ADDRS: i32 = RTA_DST; + #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] pub struct rt_metrics { diff --git a/src/linux.rs b/src/linux.rs index ba89f257..1827335f 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -20,9 +20,35 @@ use libc::{ RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; +use static_assertions::{const_assert, const_assert_eq}; use crate::{aligned_by, default_err, unlikely_err}; +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const AF_INET_U8: u8 = AF_INET as u8; +const_assert_eq!(AF_INET_U8 as i32, AF_INET); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const AF_INET6_U8: u8 = AF_INET6 as u8; +const_assert_eq!(AF_INET6_U8 as i32, AF_INET6); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const AF_UNSPEC_U8: u8 = AF_UNSPEC as u8; +const_assert_eq!(AF_UNSPEC_U8 as i32, AF_UNSPEC); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const NLM_F_REQUEST_U16: u16 = NLM_F_REQUEST as u16; +const_assert_eq!(NLM_F_REQUEST_U16 as c_int, NLM_F_REQUEST); + +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const NLM_F_ACK_U16: u16 = NLM_F_ACK as u16; +const_assert_eq!(NLM_F_ACK_U16 as c_int, NLM_F_ACK); + +const_assert!(size_of::() <= u8::MAX as usize); +const_assert!(size_of::() <= u8::MAX as usize); +const_assert!(size_of::() <= u8::MAX as usize); +const_assert!(size_of::() <= u8::MAX as usize); + const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. #[allow(non_camel_case_types, clippy::struct_field_names)] @@ -70,40 +96,30 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } } -fn prepare_nlmsg( - nlmsg_type: c_ushort, - nlmsg_len: usize, - nlmsg_seq: u32, -) -> Result { +const fn prepare_nlmsg(nlmsg_type: c_ushort, nlmsg_len: u32, nlmsg_seq: u32) -> libc::nlmsghdr { let mut nlm = unsafe { mem::zeroed::() }; - nlm.nlmsg_len = nlmsg_len - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + nlm.nlmsg_len = nlmsg_len; nlm.nlmsg_type = nlmsg_type; - nlm.nlmsg_flags = (NLM_F_REQUEST | NLM_F_ACK) - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + nlm.nlmsg_flags = NLM_F_REQUEST_U16 | NLM_F_ACK_U16; nlm.nlmsg_seq = nlmsg_seq; - Ok(nlm) + nlm } fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { // Prepare RTM_GETROUTE message. - let nlmsg_len = mem::size_of::() + #[allow(clippy::cast_possible_truncation)] + // Structs lens are <= u8::MAX per `const_assert!`s above; `addr_bytes` is max. 16 for IPv6. + let nlmsg_len = (mem::size_of::() + mem::size_of::() + mem::size_of::() - + addr_bytes(&remote).len(); + + addr_bytes(&remote).len()) as u32; let nlmsg_seq = 1; - let hdr = prepare_nlmsg(RTM_GETROUTE, nlmsg_len, nlmsg_seq)?; + let hdr = prepare_nlmsg(RTM_GETROUTE, nlmsg_len, nlmsg_seq); let mut rtm = unsafe { mem::zeroed::() }; rtm.rtm_family = match remote { - IpAddr::V4(_) => AF_INET - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, - IpAddr::V6(_) => AF_INET6 - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, + IpAddr::V4(_) => AF_INET_U8, + IpAddr::V6(_) => AF_INET6_U8, }; rtm.rtm_dst_len = addr_len(&remote); rtm.rtm_table = RT_TABLE_MAIN; @@ -111,12 +127,13 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { rtm.rtm_type = RTN_UNICAST; let mut attr = unsafe { mem::zeroed::() }; - attr.rta_len = (mem::size_of::() + addr_bytes(&remote).len()) - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + #[allow(clippy::cast_possible_truncation)] + // Structs len is <= u8::MAX per `const_assert!` above; `addr_bytes` is max. 16 for IPv6. + let rta_len = (mem::size_of::() + addr_bytes(&remote).len()) as u16; + attr.rta_len = rta_len; attr.rta_type = RTA_DST; - let mut buf = vec![0u8; nlmsg_len]; + let mut buf = vec![0u8; nlmsg_len as usize]; unsafe { ptr::copy_nonoverlapping( ptr::from_ref(&hdr).cast(), @@ -155,13 +172,11 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { if len < 0 { return Err(Error::last_os_error()); } + #[allow(clippy::cast_sign_loss)] // We handled negative sizes above, so this is OK. + let len = len as usize; let mut offset = 0; - while offset - < len - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? - { + while offset < len { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWROUTE { // This is the response, parse through the attributes to find the interface index. @@ -190,18 +205,18 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> { // Prepare RTM_GETLINK message to get the interface name and MTU for the interface with the // obtained index. - let nlmsg_len = mem::size_of::() + mem::size_of::(); + #[allow(clippy::cast_possible_truncation)] + // Structs lens are <= u8::MAX per `const_assert!`s above. + let nlmsg_len = (mem::size_of::() + mem::size_of::()) as u32; let nlmsg_seq = 2; - let hdr = prepare_nlmsg(RTM_GETLINK, nlmsg_len, nlmsg_seq)?; + let hdr = prepare_nlmsg(RTM_GETLINK, nlmsg_len, nlmsg_seq); let mut ifim: ifinfomsg = unsafe { mem::zeroed() }; - ifim.ifi_family = AF_UNSPEC - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?; + ifim.ifi_family = AF_UNSPEC_U8; ifim.ifi_type = ARPHRD_NONE; ifim.ifi_index = if_index; - let mut buf = vec![0u8; nlmsg_len]; + let mut buf = vec![0u8; nlmsg_len as usize]; unsafe { ptr::copy_nonoverlapping( ptr::from_ref(&hdr).cast(), @@ -229,13 +244,11 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> if len < 0 { return Err(Error::last_os_error()); } + #[allow(clippy::cast_sign_loss)] // We handled negative sizes above, so this is OK. + let len = len as usize; let mut offset = 0; - while offset - < len - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? - { + while offset < len { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWLINK { let mut attr_ptr = unsafe { @@ -257,7 +270,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> mtu = Some( unsafe { ptr::read_unaligned( - attr_ptr.add(mem::size_of::()).cast::(), + attr_ptr.add(mem::size_of::()).cast::(), ) } .try_into() From fe78985e93f8acba132ed6c4504bb9e9280bdaa7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 10:56:48 +0300 Subject: [PATCH 082/128] More --- .github/workflows/check.yml | 36 +++++++++++++++---------- src/bsd/freebsd.rs | 9 ++++--- src/bsd/mod.rs | 6 ++--- src/bsd/netbsd.rs | 13 ++++----- src/bsd/openbsd.rs | 9 ++++--- src/linux.rs | 54 ++++++++++++++++++------------------- 6 files changed, 68 insertions(+), 59 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 174b65ce..9476f66c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -251,17 +251,19 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - + - run: curl -o rustup.sh --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs - if: matrix.os == 'freebsd' uses: vmactions/freebsd-vm@c96341966a3954a757e6ea41692f7c7b32312a0c with: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - mkdir -p /usr/local/etc/pkg/repos - sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf - pkg update - pkg install -y rust-nightly cargo-llvm-cov + # mkdir -p /usr/local/etc/pkg/repos + # sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf + # pkg update + # pkg install -y rust-nightly cargo-llvm-cov + sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + cargo install llvm-cov run: | ifconfig cargo check --all-targets @@ -274,9 +276,11 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg_add rust - export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-llvm-cov + # pkg_add rust + # export PATH="/root/.cargo/bin:$PATH" + # cargo install cargo-llvm-cov + sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + cargo install llvm-cov run: | ifconfig cargo check --all-targets @@ -289,9 +293,11 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - /usr/sbin/pkg_add rust-bin - export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-llvm-cov + # /usr/sbin/pkg_add rust-bin + # export PATH="/root/.cargo/bin:$PATH" + # cargo install cargo-llvm-cov + sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + cargo install llvm-cov run: | /sbin/ifconfig cargo check --all-targets @@ -304,9 +310,11 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg install cargo - export PATH="/root/.cargo/bin:$PATH" - cargo install cargo-llvm-cov + # pkg install cargo + # export PATH="/root/.cargo/bin:$PATH" + # cargo install cargo-llvm-cov + sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + cargo install llvm-cov run: | ifconfig cargo check --all-targets diff --git a/src/bsd/freebsd.rs b/src/bsd/freebsd.rs index e05823a7..f5c953b5 100644 --- a/src/bsd/freebsd.rs +++ b/src/bsd/freebsd.rs @@ -4,14 +4,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use libc::{RTA_DST, RTA_IFP}; +pub const RTM_ADDRS: i32 = libc::RTA_DST | libc::RTA_IFP; -pub const ALIGN: usize = 4; - -pub const RTM_ADDRS: i32 = RTA_DST | RTA_IFP; +// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L362-L371 +pub const ALIGN: usize = std::mem::size_of::(); #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L79-L93 pub struct rt_metrics { pub rmx_locks: libc::c_ulong, // Kernel must leave these values alone pub rmx_mtu: libc::c_ulong, // MTU for this path @@ -30,6 +30,7 @@ pub struct rt_metrics { #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L249-L263 pub struct rt_msghdr { pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages pub rtm_version: libc::c_uchar, // future binary compatibility diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index bf9f018b..8253704f 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -7,7 +7,7 @@ use std::{ ffi::CStr, io::Error, - mem::{self, size_of}, + mem::{size_of, zeroed}, net::IpAddr, num::TryFromIntError, os::fd::{AsRawFd, FromRawFd, OwnedFd}, @@ -97,7 +97,7 @@ fn get_mtu_for_interface(name: &str) -> Result { } fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { - let mut dst: sockaddr_storage = unsafe { mem::zeroed() }; + let mut dst: sockaddr_storage = unsafe { zeroed() }; match ip { #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. IpAddr::V4(ip) => { @@ -129,7 +129,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let dst = as_sockaddr_storage(remote); // Prepare route message structure. - let mut query: rt_msghdr = unsafe { mem::zeroed() }; + let mut query: rt_msghdr = unsafe { zeroed() }; #[allow(clippy::cast_possible_truncation)] // Structs len is <= u8::MAX per `const_assert!`s above; `aligned_by` returns max. 16 for IPv6. let rtm_msglen = (size_of::() + aligned_by(dst.ss_len.into(), ALIGN)) as u16; // Length includes sockaddr diff --git a/src/bsd/netbsd.rs b/src/bsd/netbsd.rs index 2281fd5b..0c4a0ac9 100644 --- a/src/bsd/netbsd.rs +++ b/src/bsd/netbsd.rs @@ -4,14 +4,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use libc::{RTA_DST, RTA_IFP}; +pub const RTM_ADDRS: i32 = libc::RTA_DST | libc::RTA_IFP; -pub const ALIGN: usize = 8; - -pub const RTM_ADDRS: i32 = RTA_DST | RTA_IFP; +// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L329-L331 +pub const ALIGN: usize = std::mem::size_of::(); #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] +#[repr(C, align(8))] +// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L77-L88 pub struct rt_metrics { pub rmx_locks: u64, // Kernel must leave these values alone pub rmx_mtu: u64, // MTU for this path @@ -26,7 +26,8 @@ pub struct rt_metrics { } #[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] +#[repr(C, align(8))] +// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L219-L234 pub struct rt_msghdr { pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages pub rtm_version: libc::c_uchar, // future binary compatibility diff --git a/src/bsd/openbsd.rs b/src/bsd/openbsd.rs index f312ccf0..43a441f8 100644 --- a/src/bsd/openbsd.rs +++ b/src/bsd/openbsd.rs @@ -4,14 +4,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use libc::RTA_DST; +pub const RTM_ADDRS: i32 = libc::RTA_DST; -pub const ALIGN: usize = 8; - -pub const RTM_ADDRS: i32 = RTA_DST; +// See https://github.com/openbsd/src/blob/d16b38913788369d27d69c304ba0cf7026e7c3fc/sbin/route/route.c#L146-L148 +pub const ALIGN: usize = std::mem::size_of::(); #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +// See https://github.com/openbsd/src/blob/d16b38913788369d27d69c304ba0cf7026e7c3fc/sys/net/route.h#L71-L85 pub struct rt_metrics { pub rmx_pksent: u64, // packets sent using this route pub rmx_expire: i64, // lifetime for route, e.g. redirect @@ -30,6 +30,7 @@ pub struct rt_metrics { #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +// See https://github.com/openbsd/src/blob/d16b38913788369d27d69c304ba0cf7026e7c3fc/sys/net/route.h#L221-L238 pub struct rt_msghdr { pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages pub rtm_version: libc::c_uchar, // future binary compatibility diff --git a/src/linux.rs b/src/linux.rs index 1827335f..279ccf8d 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -7,7 +7,7 @@ use std::{ ffi::CStr, io::Error, - mem, + mem::{size_of, zeroed}, net::IpAddr, num::TryFromIntError, os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, @@ -97,7 +97,7 @@ fn addr_bytes(remote: &IpAddr) -> Vec { } const fn prepare_nlmsg(nlmsg_type: c_ushort, nlmsg_len: u32, nlmsg_seq: u32) -> libc::nlmsghdr { - let mut nlm = unsafe { mem::zeroed::() }; + let mut nlm = unsafe { zeroed::() }; nlm.nlmsg_len = nlmsg_len; nlm.nlmsg_type = nlmsg_type; nlm.nlmsg_flags = NLM_F_REQUEST_U16 | NLM_F_ACK_U16; @@ -109,14 +109,14 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { // Prepare RTM_GETROUTE message. #[allow(clippy::cast_possible_truncation)] // Structs lens are <= u8::MAX per `const_assert!`s above; `addr_bytes` is max. 16 for IPv6. - let nlmsg_len = (mem::size_of::() - + mem::size_of::() - + mem::size_of::() + let nlmsg_len = (size_of::() + + size_of::() + + size_of::() + addr_bytes(&remote).len()) as u32; let nlmsg_seq = 1; let hdr = prepare_nlmsg(RTM_GETROUTE, nlmsg_len, nlmsg_seq); - let mut rtm = unsafe { mem::zeroed::() }; + let mut rtm = unsafe { zeroed::() }; rtm.rtm_family = match remote { IpAddr::V4(_) => AF_INET_U8, IpAddr::V6(_) => AF_INET6_U8, @@ -126,10 +126,10 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { rtm.rtm_scope = RT_SCOPE_UNIVERSE; rtm.rtm_type = RTN_UNICAST; - let mut attr = unsafe { mem::zeroed::() }; + let mut attr = unsafe { zeroed::() }; #[allow(clippy::cast_possible_truncation)] // Structs len is <= u8::MAX per `const_assert!` above; `addr_bytes` is max. 16 for IPv6. - let rta_len = (mem::size_of::() + addr_bytes(&remote).len()) as u16; + let rta_len = (size_of::() + addr_bytes(&remote).len()) as u16; attr.rta_len = rta_len; attr.rta_type = RTA_DST; @@ -138,24 +138,23 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { ptr::copy_nonoverlapping( ptr::from_ref(&hdr).cast(), buf.as_mut_ptr(), - mem::size_of::(), + size_of::(), ); ptr::copy_nonoverlapping( ptr::from_ref(&rtm).cast(), - buf.as_mut_ptr().add(mem::size_of::()), - mem::size_of::(), + buf.as_mut_ptr().add(size_of::()), + size_of::(), ); ptr::copy_nonoverlapping( ptr::from_ref(&attr).cast(), buf.as_mut_ptr() - .add(mem::size_of::() + mem::size_of::()), - mem::size_of::(), + .add(size_of::() + size_of::()), + size_of::(), ); ptr::copy_nonoverlapping( addr_bytes(&remote).as_ptr(), - buf.as_mut_ptr().add( - mem::size_of::() + mem::size_of::() + mem::size_of::(), - ), + buf.as_mut_ptr() + .add(size_of::() + size_of::() + size_of::()), addr_bytes(&remote).len(), ); }; @@ -182,7 +181,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { // This is the response, parse through the attributes to find the interface index. let mut attr_ptr = unsafe { buf.as_ptr() - .add(offset + mem::size_of::() + mem::size_of::()) + .add(offset + size_of::() + size_of::()) }; let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; while attr_ptr < attr_end { @@ -190,7 +189,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { if attr.rta_type == RTA_OIF { // We have our interface index. let idx = unsafe { - ptr::read_unaligned(attr_ptr.add(mem::size_of::()).cast()) + ptr::read_unaligned(attr_ptr.add(size_of::()).cast()) }; return Ok(idx); } @@ -207,11 +206,11 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> // obtained index. #[allow(clippy::cast_possible_truncation)] // Structs lens are <= u8::MAX per `const_assert!`s above. - let nlmsg_len = (mem::size_of::() + mem::size_of::()) as u32; + let nlmsg_len = (size_of::() + size_of::()) as u32; let nlmsg_seq = 2; let hdr = prepare_nlmsg(RTM_GETLINK, nlmsg_len, nlmsg_seq); - let mut ifim: ifinfomsg = unsafe { mem::zeroed() }; + let mut ifim: ifinfomsg = unsafe { zeroed() }; ifim.ifi_family = AF_UNSPEC_U8; ifim.ifi_type = ARPHRD_NONE; ifim.ifi_index = if_index; @@ -221,12 +220,12 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> ptr::copy_nonoverlapping( ptr::from_ref(&hdr).cast(), buf.as_mut_ptr(), - mem::size_of::(), + size_of::(), ); ptr::copy_nonoverlapping( ptr::from_ref(&ifim).cast(), - buf.as_mut_ptr().add(mem::size_of::()), - mem::size_of::(), + buf.as_mut_ptr().add(size_of::()), + size_of::(), ); } @@ -253,15 +252,14 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWLINK { let mut attr_ptr = unsafe { buf.as_ptr() - .add(offset + mem::size_of::() + mem::size_of::()) + .add(offset + size_of::() + size_of::()) }; let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; while attr_ptr < attr_end { let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; if attr.rta_type == IFLA_IFNAME { - let name = unsafe { - CStr::from_ptr(attr_ptr.add(mem::size_of::()).cast()) - }; + let name = + unsafe { CStr::from_ptr(attr_ptr.add(size_of::()).cast()) }; if let Ok(name) = name.to_str() { // We have our interface name. ifname = Some(name.to_string()); @@ -270,7 +268,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> mtu = Some( unsafe { ptr::read_unaligned( - attr_ptr.add(mem::size_of::()).cast::(), + attr_ptr.add(size_of::()).cast::(), ) } .try_into() From 54c6435dbc41c831451e327035fe29cc7ab027e0 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:04:20 +0300 Subject: [PATCH 083/128] Again --- .github/workflows/check.yml | 6 +++++- README.md | 5 +++-- src/lib.rs | 5 +++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8f534363..7c56df4d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -252,7 +252,7 @@ jobs: steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: curl -o rustup.sh --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs - + - if: matrix.os == 'freebsd' uses: vmactions/freebsd-vm@c96341966a3954a757e6ea41692f7c7b32312a0c with: @@ -263,6 +263,7 @@ jobs: # sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf # pkg update # pkg install -y rust-nightly cargo-llvm-cov + ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov run: | @@ -280,6 +281,7 @@ jobs: # pkg_add rust # export PATH="/root/.cargo/bin:$PATH" # cargo install cargo-llvm-cov + ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov run: | @@ -297,6 +299,7 @@ jobs: # /usr/sbin/pkg_add rust-bin # export PATH="/root/.cargo/bin:$PATH" # cargo install cargo-llvm-cov + ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov run: | @@ -314,6 +317,7 @@ jobs: # pkg install cargo # export PATH="/root/.cargo/bin:$PATH" # cargo install cargo-llvm-cov + ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov run: | diff --git a/README.md b/README.md index 451469e9..760efb5f 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,9 @@ println!("MTU towards {destination} is {mtu} on {name}"); * Linux * macOS * Windows - -FreeBSD, NetBSD and OpenBSD support is waiting for [rust/libc#3714](https://github.com/rust-lang/libc/pull/3714). +* FreeBSD +* NetBSD +* OpenBSD ## Notes diff --git a/src/lib.rs b/src/lib.rs index ead0fc21..9b95925b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,9 @@ //! * Linux //! * macOS //! * Windows -//! -//! FreeBSD, NetBSD and OpenBSD support is waiting for [rust/libc#3714](https://github.com/rust-lang/libc/pull/3714). +//! * FreeBSD +//! * NetBSD +//! * OpenBSD //! //! # Notes //! From 59f806b2945244511c13bd414c61916bbfb60f54 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:20:16 +0300 Subject: [PATCH 084/128] Again --- .github/workflows/check.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 7c56df4d..4235634b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -263,10 +263,10 @@ jobs: # sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf # pkg update # pkg install -y rust-nightly cargo-llvm-cov + run: | ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov - run: | ifconfig cargo check --all-targets LLVM_COV=/usr/bin/llvm-cov LLVM_PROFDATA=/usr/bin/llvm-profdata RUST_LOG=trace \ @@ -281,10 +281,10 @@ jobs: # pkg_add rust # export PATH="/root/.cargo/bin:$PATH" # cargo install cargo-llvm-cov + run: | ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov - run: | ifconfig cargo check --all-targets LLVM_COV=/usr/bin/llvm-cov LLVM_PROFDATA=/usr/bin/llvm-profdata RUST_LOG=trace \ @@ -299,10 +299,10 @@ jobs: # /usr/sbin/pkg_add rust-bin # export PATH="/root/.cargo/bin:$PATH" # cargo install cargo-llvm-cov + run: | ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov - run: | /sbin/ifconfig cargo check --all-targets LLVM_COV=/usr/pkg/bin/llvm-cov LLVM_PROFDATA=/usr/pkg/bin/llvm-profdata RUST_LOG=trace \ @@ -317,10 +317,10 @@ jobs: # pkg install cargo # export PATH="/root/.cargo/bin:$PATH" # cargo install cargo-llvm-cov + run: | ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov - run: | ifconfig cargo check --all-targets RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info From 4c95881d90da822a2420c7b8fc11a3a27b686035 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:25:32 +0300 Subject: [PATCH 085/128] Again --- .github/workflows/check.yml | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4235634b..28108451 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -259,18 +259,14 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - # mkdir -p /usr/local/etc/pkg/repos - # sed 's/quarterly/latest/' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf - # pkg update - # pkg install -y rust-nightly cargo-llvm-cov + pkg install -y curl run: | - ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov ifconfig cargo check --all-targets - LLVM_COV=/usr/bin/llvm-cov LLVM_PROFDATA=/usr/bin/llvm-profdata RUST_LOG=trace \ - cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + cargo clippy + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'openbsd' uses: vmactions/openbsd-vm@0cfe06e734a0ea3a546fca7ebf200b984b94d58a @@ -278,17 +274,14 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - # pkg_add rust - # export PATH="/root/.cargo/bin:$PATH" - # cargo install cargo-llvm-cov + pkg_add curl run: | - ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov ifconfig cargo check --all-targets - LLVM_COV=/usr/bin/llvm-cov LLVM_PROFDATA=/usr/bin/llvm-profdata RUST_LOG=trace \ - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + cargo clippy + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'netbsd' uses: vmactions/netbsd-vm@7c9086fdb4cc1aa814cda6e305390c2b966551a9 @@ -296,17 +289,14 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - # /usr/sbin/pkg_add rust-bin - # export PATH="/root/.cargo/bin:$PATH" - # cargo install cargo-llvm-cov + /usr/sbin/pkg_add curl run: | - ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov /sbin/ifconfig cargo check --all-targets - LLVM_COV=/usr/pkg/bin/llvm-cov LLVM_PROFDATA=/usr/pkg/bin/llvm-profdata RUST_LOG=trace \ - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + cargo clippy + llvm-profdata RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'solaris' uses: vmactions/solaris-vm@a89b9438868c70db27e41625f0a5de6ff5e90809 @@ -314,15 +304,13 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - # pkg install cargo - # export PATH="/root/.cargo/bin:$PATH" - # cargo install cargo-llvm-cov + pkg install curl run: | - ls -l sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y cargo install llvm-cov ifconfig cargo check --all-targets + cargo clippy RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 From f9e5544d12a943d2e567060dfcabc251d6ba1892 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:33:50 +0300 Subject: [PATCH 086/128] Again --- .github/workflows/check.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 28108451..c4746661 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -246,7 +246,7 @@ jobs: strategy: fail-fast: false matrix: - os: [freebsd, openbsd, netbsd] # rust on solaris is too old + os: [freebsd, openbsd, netbsd, solaris] # rust on solaris is too old runs-on: ubuntu-latest steps: @@ -262,6 +262,7 @@ jobs: pkg install -y curl run: | sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + . "$HOME/.cargo/env" cargo install llvm-cov ifconfig cargo check --all-targets @@ -274,14 +275,12 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg_add curl + pkg_add rust # rustup doesn't support OpenBSD at all run: | - sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y - cargo install llvm-cov ifconfig cargo check --all-targets cargo clippy - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + RUST_LOG=trace cargo test --no-fail-fast - if: matrix.os == 'netbsd' uses: vmactions/netbsd-vm@7c9086fdb4cc1aa814cda6e305390c2b966551a9 @@ -292,6 +291,7 @@ jobs: /usr/sbin/pkg_add curl run: | sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + . "$HOME/.cargo/env" cargo install llvm-cov /sbin/ifconfig cargo check --all-targets @@ -307,6 +307,7 @@ jobs: pkg install curl run: | sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + . "$HOME/.cargo/env" cargo install llvm-cov ifconfig cargo check --all-targets From 4871a6b39df14a3f1ecc88ba6f773d149ac8be70 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:39:13 +0300 Subject: [PATCH 087/128] Again --- .github/workflows/check.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index c4746661..4238b66a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -263,7 +263,7 @@ jobs: run: | sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y . "$HOME/.cargo/env" - cargo install llvm-cov + cargo install cargo-llvm-cov --locked ifconfig cargo check --all-targets cargo clippy @@ -292,7 +292,7 @@ jobs: run: | sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y . "$HOME/.cargo/env" - cargo install llvm-cov + cargo install cargo-llvm-cov --locked /sbin/ifconfig cargo check --all-targets cargo clippy @@ -303,12 +303,12 @@ jobs: with: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" - prepare: | - pkg install curl + # prepare: | + # pkg install curl run: | sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y . "$HOME/.cargo/env" - cargo install llvm-cov + cargo install cargo-llvm-cov --locked ifconfig cargo check --all-targets cargo clippy From 707f8ba918236171353611d13e4b46d583800586 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:54:05 +0300 Subject: [PATCH 088/128] More --- .github/workflows/check.yml | 3 ++- src/linux.rs | 7 ++++--- src/windows/mod.rs | 12 +++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4238b66a..1efef07c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -105,6 +105,7 @@ jobs: cargo +${{ matrix.rust-toolchain }} test $BUILD_TYPE --no-fail-fast $FILTER fi cargo +${{ matrix.rust-toolchain }} bench --no-run + - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: file: lcov.info @@ -246,7 +247,7 @@ jobs: strategy: fail-fast: false matrix: - os: [freebsd, openbsd, netbsd, solaris] # rust on solaris is too old + os: [freebsd, openbsd, netbsd] # rust on solaris is too old runs-on: ubuntu-latest steps: diff --git a/src/linux.rs b/src/linux.rs index 279ccf8d..91509633 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -15,7 +15,7 @@ use std::{ }; use libc::{ - c_int, c_uchar, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, + c_int, c_uchar, c_uint, c_ushort, nlmsghdr, recv, socket, write, AF_INET, AF_INET6, AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, @@ -167,7 +167,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { // Receive RTM_GETROUTE response. loop { let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; + let len = unsafe { recv(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len(), 0) }; if len < 0 { return Err(Error::last_os_error()); } @@ -239,7 +239,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> let mut mtu = None; 'recv: loop { let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; + let len = unsafe { recv(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len(), 0) }; if len < 0 { return Err(Error::last_os_error()); } @@ -300,5 +300,6 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let fd = unsafe { OwnedFd::from_raw_fd(fd) }; let if_index = if_index(remote, fd.as_fd())?; + eprintln!("if_index: {}", if_index); if_name_mtu(if_index, fd.as_fd()) } diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 4326fc04..f0c63482 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -26,10 +26,15 @@ use crate::{ mod win_bindings; pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { - // Convert remote to Windows SOCKADDR_INET format. + // Convert remote to Windows SOCKADDR_INET format. The SOCKADDR_INET union contains an IPv4 or + // an IPv6 address. We allocate and zero-initialize it here. + // + // See https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-sockaddr_inet + let mut dst: SOCKADDR_INET = unsafe { mem::zeroed() }; match remote { IpAddr::V4(ip) => { + // Initialize the `SOCKADDR_IN` variant of `SOCKADDR_INET` based on `ip`. let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin.sin_family = AF_INET; sin.sin_addr = IN_ADDR { @@ -39,6 +44,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } } IpAddr::V6(ip) => { + // Initialize the `SOCKADDR_IN6` variant of `SOCKADDR_INET` based on `ip`. let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin6.sin6_family = AF_INET6; sin6.sin6_addr = IN6_ADDR { @@ -50,6 +56,10 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Get the interface index of the best outbound interface towards `dst`. let mut idx = 0; let res = unsafe { + // We're now casting `&dst` to a `SOCKADDR` pointer. This is OK based on + // https://learn.microsoft.com/en-us/windows/win32/winsock/sockaddr-2. + // With that, we call `GetBestInterfaceEx` to get the interface index into `idx`. + // See https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getbestinterfaceex GetBestInterfaceEx( ptr::from_ref(&dst).cast::(), ptr::from_mut(&mut idx), From 1dc568aa1203559c4fe9cc9f0a4ef92e478991cb Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 11:55:19 +0300 Subject: [PATCH 089/128] Again --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1efef07c..757fc23d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -297,7 +297,7 @@ jobs: /sbin/ifconfig cargo check --all-targets cargo clippy - llvm-profdata RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info - if: matrix.os == 'solaris' uses: vmactions/solaris-vm@a89b9438868c70db27e41625f0a5de6ff5e90809 From ccdc7d562a0698cf34fbac0b463f0cbe2961bbd7 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 12:16:07 +0300 Subject: [PATCH 090/128] Again --- .github/workflows/check.yml | 8 ++++---- src/linux.rs | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 757fc23d..83d0dbb7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -81,7 +81,7 @@ jobs: - uses: ./neqo/.github/actions/rust with: version: ${{ matrix.rust-toolchain }} - components: ${{ matrix.rust-toolchain == 'stable' && 'llvm-tools-preview' || '' }} ${{ matrix.rust-toolchain == 'nightly' && 'rust-src' || '' }} + components: ${{ matrix.rust-toolchain == 'stable' && 'llvm-tools' || '' }} ${{ matrix.rust-toolchain == 'nightly' && 'rust-src' || '' }} tools: ${{ matrix.rust-toolchain == 'stable' && 'cargo-llvm-cov, ' || '' }} token: ${{ secrets.GITHUB_TOKEN }} @@ -262,7 +262,7 @@ jobs: prepare: | pkg install -y curl run: | - sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" cargo install cargo-llvm-cov --locked ifconfig @@ -291,7 +291,7 @@ jobs: prepare: | /usr/sbin/pkg_add curl run: | - sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + sh rustup.sh --default-toolchain nightly --component llvm-tools -y . "$HOME/.cargo/env" cargo install cargo-llvm-cov --locked /sbin/ifconfig @@ -307,7 +307,7 @@ jobs: # prepare: | # pkg install curl run: | - sh rustup.sh --default-toolchain stable --component llvm-tools-preview -y + sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" cargo install cargo-llvm-cov --locked ifconfig diff --git a/src/linux.rs b/src/linux.rs index 91509633..279ccf8d 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -15,7 +15,7 @@ use std::{ }; use libc::{ - c_int, c_uchar, c_uint, c_ushort, nlmsghdr, recv, socket, write, AF_INET, AF_INET6, AF_NETLINK, + c_int, c_uchar, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, @@ -167,7 +167,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { // Receive RTM_GETROUTE response. loop { let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { recv(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len(), 0) }; + let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len < 0 { return Err(Error::last_os_error()); } @@ -239,7 +239,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> let mut mtu = None; 'recv: loop { let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { recv(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len(), 0) }; + let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len < 0 { return Err(Error::last_os_error()); } @@ -300,6 +300,5 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> let fd = unsafe { OwnedFd::from_raw_fd(fd) }; let if_index = if_index(remote, fd.as_fd())?; - eprintln!("if_index: {}", if_index); if_name_mtu(if_index, fd.as_fd()) } From a080cff7476f67666bce8f625634df6f206b438d Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 13:30:17 +0300 Subject: [PATCH 091/128] Linux fix --- src/linux.rs | 132 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 279ccf8d..18cecf23 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -16,9 +16,9 @@ use std::{ use libc::{ c_int, c_uchar, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, - AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLM_F_ACK, NLM_F_REQUEST, - RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, - RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, + AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLMSG_ERROR, NLM_F_ACK, + NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, + RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, }; use static_assertions::{const_assert, const_assert_eq}; @@ -44,6 +44,10 @@ const_assert_eq!(NLM_F_REQUEST_U16 as c_int, NLM_F_REQUEST); const NLM_F_ACK_U16: u16 = NLM_F_ACK as u16; const_assert_eq!(NLM_F_ACK_U16 as c_int, NLM_F_ACK); +#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. +const NLMSG_ERROR_U16: u16 = NLMSG_ERROR as u16; +const_assert_eq!(NLMSG_ERROR_U16 as c_int, NLMSG_ERROR); + const_assert!(size_of::() <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); @@ -113,7 +117,9 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { + size_of::() + size_of::() + addr_bytes(&remote).len()) as u32; - let nlmsg_seq = 1; + #[allow(clippy::cast_sign_loss)] + // OK, because they are the same length and we just care about a unique value here. + let nlmsg_seq = fd.as_raw_fd() as u32; let hdr = prepare_nlmsg(RTM_GETROUTE, nlmsg_len, nlmsg_seq); let mut rtm = unsafe { zeroed::() }; @@ -177,23 +183,38 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { let mut offset = 0; while offset < len { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; - if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWROUTE { - // This is the response, parse through the attributes to find the interface index. - let mut attr_ptr = unsafe { - buf.as_ptr() - .add(offset + size_of::() + size_of::()) - }; - let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; - while attr_ptr < attr_end { - let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; - if attr.rta_type == RTA_OIF { - // We have our interface index. - let idx = unsafe { - ptr::read_unaligned(attr_ptr.add(size_of::()).cast()) + if hdr.nlmsg_seq == nlmsg_seq { + match hdr.nlmsg_type { + NLMSG_ERROR_U16 => { + // Extract the error code and return it. + let err: c_int = unsafe { + ptr::read_unaligned( + buf.as_ptr().add(offset + size_of::()).cast(), + ) }; - return Ok(idx); + return Err(Error::from_raw_os_error(-err)); } - attr_ptr = unsafe { attr_ptr.add(attr.rta_len as usize) }; + RTM_NEWROUTE => { + // This is the response, parse through the attributes to find the interface + // index. + let mut attr_ptr = unsafe { + buf.as_ptr() + .add(offset + size_of::() + size_of::()) + }; + let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; + while attr_ptr < attr_end { + let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; + if attr.rta_type == RTA_OIF { + // We have our interface index. + let idx = unsafe { + ptr::read_unaligned(attr_ptr.add(size_of::()).cast()) + }; + return Ok(idx); + } + attr_ptr = unsafe { attr_ptr.add(attr.rta_len as usize) }; + } + } + _ => (), } } offset += hdr.nlmsg_len as usize; @@ -249,37 +270,52 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> let mut offset = 0; while offset < len { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; - if hdr.nlmsg_seq == nlmsg_seq && hdr.nlmsg_type == RTM_NEWLINK { - let mut attr_ptr = unsafe { - buf.as_ptr() - .add(offset + size_of::() + size_of::()) - }; - let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; - while attr_ptr < attr_end { - let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; - if attr.rta_type == IFLA_IFNAME { - let name = - unsafe { CStr::from_ptr(attr_ptr.add(size_of::()).cast()) }; - if let Ok(name) = name.to_str() { - // We have our interface name. - ifname = Some(name.to_string()); - } - } else if attr.rta_type == IFLA_MTU { - mtu = Some( - unsafe { - ptr::read_unaligned( - attr_ptr.add(size_of::()).cast::(), - ) - } - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, - ); + if hdr.nlmsg_seq == nlmsg_seq { + match hdr.nlmsg_type { + NLMSG_ERROR_U16 => { + // Extract the error code and return it. + let err: c_int = unsafe { + ptr::read_unaligned( + buf.as_ptr().add(offset + size_of::()).cast(), + ) + }; + return Err(Error::from_raw_os_error(-err)); } - if ifname.is_some() && mtu.is_some() { - break 'recv; + RTM_NEWLINK => { + let mut attr_ptr = unsafe { + buf.as_ptr() + .add(offset + size_of::() + size_of::()) + }; + let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; + while attr_ptr < attr_end { + let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; + if attr.rta_type == IFLA_IFNAME { + let name = unsafe { + CStr::from_ptr(attr_ptr.add(size_of::()).cast()) + }; + if let Ok(name) = name.to_str() { + // We have our interface name. + ifname = Some(name.to_string()); + } + } else if attr.rta_type == IFLA_MTU { + mtu = Some( + unsafe { + ptr::read_unaligned( + attr_ptr.add(size_of::()).cast::(), + ) + } + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, + ); + } + if ifname.is_some() && mtu.is_some() { + break 'recv; + } + let incr = aligned_by(attr.rta_len as usize, 4); + attr_ptr = unsafe { attr_ptr.add(incr) }; + } } - let incr = aligned_by(attr.rta_len as usize, 4); - attr_ptr = unsafe { attr_ptr.add(incr) }; + _ => (), } } offset += hdr.nlmsg_len as usize; From 858f34407cd3a6ebba0d6e4fa0f4b83fef1c54b2 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 13:39:42 +0300 Subject: [PATCH 092/128] More --- .github/workflows/check.yml | 4 +++- Cargo.toml | 2 +- src/linux.rs | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 83d0dbb7..325fb588 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -297,7 +297,9 @@ jobs: /sbin/ifconfig cargo check --all-targets cargo clippy - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + RUST_LOG=trace cargo test --no-fail-fast + # FIXME: error[E0463]: can't find crate for `profiler_builtins` + RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info || true - if: matrix.os == 'solaris' uses: vmactions/solaris-vm@a89b9438868c70db27e41625f0a5de6ff5e90809 diff --git a/Cargo.toml b/Cargo.toml index bc8ce3b4..69baa20d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed", branch = "main" } [dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock -libc = { version = ">=0.2.161", default-features = false} +libc = { version = ">=0.2.161", default-features = false } static_assertions = { version = "1.1", default-features = false } [target."cfg(windows)".dependencies] diff --git a/src/linux.rs b/src/linux.rs index 18cecf23..37d88ed9 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -57,6 +57,7 @@ const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +// See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L561-L568 struct ifinfomsg { ifi_family: c_uchar, // AF_UNSPEC ifi_type: c_ushort, // Device type @@ -67,6 +68,7 @@ struct ifinfomsg { #[allow(non_camel_case_types, clippy::struct_field_names)] #[repr(C)] +// See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L237-L249 struct rtmsg { rtm_family: c_uchar, // Address family of route rtm_dst_len: c_uchar, // Length of destination @@ -81,6 +83,7 @@ struct rtmsg { #[allow(non_camel_case_types)] #[repr(C)] +// See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L211-L214 struct rtattr { rta_len: c_ushort, // Length of option rta_type: c_ushort, // Type of option @@ -311,6 +314,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> if ifname.is_some() && mtu.is_some() { break 'recv; } + // See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L218 let incr = aligned_by(attr.rta_len as usize, 4); attr_ptr = unsafe { attr_ptr.add(incr) }; } From bb96df8ce50c681def4770c59a383e52e5deed09 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 24 Oct 2024 13:40:42 +0300 Subject: [PATCH 093/128] stable --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 325fb588..320cca56 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -291,7 +291,7 @@ jobs: prepare: | /usr/sbin/pkg_add curl run: | - sh rustup.sh --default-toolchain nightly --component llvm-tools -y + sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" cargo install cargo-llvm-cov --locked /sbin/ifconfig From f7a9cc588b8c68659c23e4a3375770438b84ad0b Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 25 Oct 2024 09:42:10 +0300 Subject: [PATCH 094/128] Simplify --- src/bsd/apple.rs | 4 +- src/bsd/freebsd.rs | 2 +- src/bsd/mod.rs | 109 +++++++++++++++++++++------------------------ src/lib.rs | 7 --- src/linux.rs | 10 ++++- 5 files changed, 61 insertions(+), 71 deletions(-) diff --git a/src/bsd/apple.rs b/src/bsd/apple.rs index ca533c37..14bf29c7 100644 --- a/src/bsd/apple.rs +++ b/src/bsd/apple.rs @@ -4,11 +4,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use libc::{RTA_DST, RTA_IFP}; +pub const RTM_ADDRS: i32 = libc::RTA_DST; pub const ALIGN: usize = 4; -pub const RTM_ADDRS: i32 = RTA_DST | RTA_IFP; - #[allow(non_camel_case_types)] pub type rt_msghdr = libc::rt_msghdr; diff --git a/src/bsd/freebsd.rs b/src/bsd/freebsd.rs index f5c953b5..fd0ecec8 100644 --- a/src/bsd/freebsd.rs +++ b/src/bsd/freebsd.rs @@ -4,7 +4,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -pub const RTM_ADDRS: i32 = libc::RTA_DST | libc::RTA_IFP; +pub const RTM_ADDRS: i32 = libc::RTA_DST; // See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L362-L371 pub const ALIGN: usize = std::mem::size_of::(); diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index 8253704f..0cd7f7dd 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -9,15 +9,14 @@ use std::{ io::Error, mem::{size_of, zeroed}, net::IpAddr, - num::TryFromIntError, os::fd::{AsRawFd, FromRawFd, OwnedFd}, - ptr, slice, str, + ptr, }; use libc::{ - freeifaddrs, getifaddrs, getpid, if_data, ifaddrs, read, sockaddr_dl, sockaddr_in, + freeifaddrs, getifaddrs, getpid, if_data, if_indextoname, ifaddrs, read, sockaddr_in, sockaddr_in6, sockaddr_storage, socket, write, AF_INET, AF_INET6, AF_LINK, AF_UNSPEC, PF_ROUTE, - RTAX_IFA, RTAX_IFP, RTAX_MAX, RTM_GET, RTM_VERSION, SOCK_RAW, + RTAX_MAX, RTM_GET, RTM_VERSION, SOCK_RAW, }; use static_assertions::{const_assert, const_assert_eq}; @@ -42,7 +41,7 @@ mod netbsd; #[cfg(target_os = "openbsd")] mod openbsd; -use crate::{aligned_by, default_err, unlikely_err}; +use crate::{aligned_by, default_err}; #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. const AF_INET_U8: u8 = AF_INET as u8; @@ -68,32 +67,50 @@ const_assert!(size_of::() <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); -fn get_mtu_for_interface(name: &str) -> Result { - let mut ifap: *mut ifaddrs = ptr::null_mut(); - if unsafe { getifaddrs(&mut ifap) } != 0 { +struct IfAddrPtr(*mut ifaddrs); + +impl Drop for IfAddrPtr { + fn drop(&mut self) { + unsafe { freeifaddrs(self.0) }; + } +} + +pub fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { + let mut name = [0; libc::IF_NAMESIZE]; + if unsafe { if_indextoname(idx, name.as_mut_ptr()).is_null() } { return Err(Error::last_os_error()); } - let ifap = ifap; // Do not modify this pointer. - let mut res = Err(default_err()); + let name = unsafe { + CStr::from_ptr(name.as_ptr()) + .to_str() + .map_err(|_| default_err())? + }; - let mut ifa_next = ifap; + let mut ifap = IfAddrPtr(ptr::null_mut()); + if unsafe { getifaddrs(ptr::from_mut(&mut ifap.0)) } != 0 { + return Err(Error::last_os_error()); + } + + let mut ifa_next = ifap.0; while !ifa_next.is_null() { - let ifa = unsafe { &*ifa_next }; + let ifa = unsafe { *ifa_next }; if !ifa.ifa_addr.is_null() { - let ifa_addr = unsafe { &*ifa.ifa_addr }; - let ifa_name = unsafe { CStr::from_ptr(ifa.ifa_name).to_str().unwrap_or_default() }; + let ifa_addr = unsafe { *ifa.ifa_addr }; + let ifa_name = unsafe { + CStr::from_ptr(ifa.ifa_name) + .to_str() + .map_err(|_| default_err())? + }; if ifa_addr.sa_family == AF_LINK_U8 && !ifa.ifa_data.is_null() && ifa_name == name { - let ifa_data = unsafe { &*(ifa.ifa_data as *const if_data) }; + let ifa_data = unsafe { *(ifa.ifa_data as *const if_data) }; if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { - res = Ok(mtu); - break; + return Ok((name.to_string(), mtu)); } } } ifa_next = ifa.ifa_next; } - unsafe { freeifaddrs(ifap) }; - res + Err(default_err()) } fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { @@ -117,7 +134,7 @@ fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { dst } -pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { +pub fn if_index(remote: IpAddr) -> Result { // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; if fd == -1 { @@ -153,7 +170,8 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> ); ptr::copy_nonoverlapping( ptr::from_ref(&dst).cast(), - msg.as_mut_ptr().add(size_of::()), + msg.as_mut_ptr() + .add(aligned_by(size_of::(), ALIGN)), dst.ss_len.into(), ); } @@ -171,53 +189,28 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // There will never be `RTAX_MAX` sockaddrs attached, but it's a safe upper bound. (RTAX_MAX as usize * size_of::()) ]; - let rtm = loop { + loop { let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if len <= 0 { return Err(Error::last_os_error()); } let reply = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; if reply.rtm_version == query.rtm_version - && reply.rtm_type == query.rtm_type && reply.rtm_pid == unsafe { getpid() } && reply.rtm_seq == query.rtm_seq { - // This is the reply we are looking for. - break reply; - } - }; - - // Parse the route message for the interface name. - let mut sa = unsafe { buf.as_ptr().add(size_of::()) }; - for i in 0..RTAX_MAX { - let sdl = unsafe { ptr::read_unaligned(sa.cast::()) }; - // Check if the address is present in the message - if rtm.rtm_addrs & (1 << i) != 0 { - // Check if the address is the interface address - if (i == RTAX_IFP || i == RTAX_IFA) && sdl.sdl_family == AF_LINK_U8 && sdl.sdl_nlen > 0 - { - let if_name = unsafe { - slice::from_raw_parts(sdl.sdl_data.as_ptr().cast(), sdl.sdl_nlen.into()) - }; - if let Ok(if_name) = str::from_utf8(if_name) { - // We have our interface name. - // If rtm.rtm_rmx.rmx_mtu is 0, which can happen on OpenBSD and NetBSD, we need - // to get the MTU via different means based on the interface name. - let mtu = if rtm.rtm_rmx.rmx_mtu > 0 { - rtm.rtm_rmx - .rmx_mtu - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))? - } else { - get_mtu_for_interface(if_name)? - }; - return Ok((if_name.to_string(), mtu)); - } - } - let incr = aligned_by(sdl.sdl_len.into(), ALIGN); - sa = unsafe { sa.add(incr) }; + // This is a reply to our query. + return if reply.rtm_type == query.rtm_type { + // This is *the* reply we are looking for. + Ok(reply.rtm_index) + } else { + Err(default_err()) + }; } } +} - Err(default_err()) +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { + let if_index = if_index(remote)?; + if_name_mtu(if_index.into()) } diff --git a/src/lib.rs b/src/lib.rs index 9b95925b..93fec11c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,13 +68,6 @@ fn default_err() -> Error { Error::new(ErrorKind::NotFound, "Local interface MTU not found") } -/// Prepare an error for cases that "should never happen". -#[cfg(not(target_os = "windows"))] -fn unlikely_err(msg: String) -> Error { - debug_assert!(false, "{msg}"); - Error::new(ErrorKind::Other, msg) -} - /// Align `size` to the next multiple of `align` (which needs to be a power of two). #[cfg(not(target_os = "windows"))] const fn aligned_by(size: usize, align: usize) -> usize { diff --git a/src/linux.rs b/src/linux.rs index 37d88ed9..e76aab11 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -6,7 +6,7 @@ use std::{ ffi::CStr, - io::Error, + io::{Error, ErrorKind}, mem::{size_of, zeroed}, net::IpAddr, num::TryFromIntError, @@ -22,7 +22,7 @@ use libc::{ }; use static_assertions::{const_assert, const_assert_eq}; -use crate::{aligned_by, default_err, unlikely_err}; +use crate::{aligned_by, default_err}; #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. const AF_INET_U8: u8 = AF_INET as u8; @@ -89,6 +89,12 @@ struct rtattr { rta_type: c_ushort, // Type of option } // Data follows +/// Prepare an error for cases that "should never happen". +fn unlikely_err(msg: String) -> Error { + debug_assert!(false, "{msg}"); + Error::new(ErrorKind::Other, msg) +} + const fn addr_len(remote: &IpAddr) -> u8 { match remote { IpAddr::V4(_) => 32, From fe9b4bd9934d6714d9b578a61dd9e23dd3606e75 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 25 Oct 2024 12:49:13 +0300 Subject: [PATCH 095/128] More comments --- src/bsd/mod.rs | 9 +++++++++ src/linux.rs | 6 +++++- src/windows/mod.rs | 24 ++++++++++++++++-------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index 0cd7f7dd..f3445668 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -71,15 +71,18 @@ struct IfAddrPtr(*mut ifaddrs); impl Drop for IfAddrPtr { fn drop(&mut self) { + // Free the memory allocated by `getifaddrs`. unsafe { freeifaddrs(self.0) }; } } pub fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { let mut name = [0; libc::IF_NAMESIZE]; + // if_indextoname writes into the provided buffer. if unsafe { if_indextoname(idx, name.as_mut_ptr()).is_null() } { return Err(Error::last_os_error()); } + // Convert to Rust string. let name = unsafe { CStr::from_ptr(name.as_ptr()) .to_str() @@ -87,11 +90,14 @@ pub fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { }; let mut ifap = IfAddrPtr(ptr::null_mut()); + // getifaddrs allocates memory for the linked list of interfaces that is freed by + // `IfAddrPtr::drop`. if unsafe { getifaddrs(ptr::from_mut(&mut ifap.0)) } != 0 { return Err(Error::last_os_error()); } let mut ifa_next = ifap.0; + // All `unsafe` statements in this loop access memory initialized by `getifaddrs`. while !ifa_next.is_null() { let ifa = unsafe { *ifa_next }; if !ifa.ifa_addr.is_null() { @@ -118,6 +124,7 @@ fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { match ip { #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. IpAddr::V4(ip) => { + // Reinterpret the `sockaddr_storage` as `sockaddr_in`. let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin.sin_len = size_of::() as u8; sin.sin_family = AF_INET_U8; @@ -125,6 +132,7 @@ fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { } #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. IpAddr::V6(ip) => { + // Reinterpret the `sockaddr_storage` as `sockaddr_in6`. let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; sin6.sin6_len = size_of::() as u8; sin6.sin6_family = AF_INET6_U8; @@ -140,6 +148,7 @@ pub fn if_index(remote: IpAddr) -> Result { if fd == -1 { return Err(Error::last_os_error()); } + // Let OwnedFd take care of closing the file descriptor. let fd = unsafe { OwnedFd::from_raw_fd(fd) }; // Prepare buffer with destination `sockaddr`. diff --git a/src/linux.rs b/src/linux.rs index e76aab11..dfb3a239 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -175,6 +175,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { }; // Send RTM_GETROUTE message to get the interface index associated with the destination. + // Accesses the initialized memory in `buf`. if unsafe { write(fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) } < 0 { return Err(Error::last_os_error()); } @@ -190,6 +191,7 @@ fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { let len = len as usize; let mut offset = 0; + // All `unsafe` blocks are accessing memory initialized by `read` above. while offset < len { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == nlmsg_seq { @@ -259,7 +261,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> ); } - // Send RTM_GETLINK message. + // Send RTM_GETLINK message. Accesses the initialized memory in `buf`. if unsafe { write(fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) } < 0 { return Err(Error::last_os_error()); } @@ -277,6 +279,7 @@ fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> let len = len as usize; let mut offset = 0; + // All `unsafe` blocks are accessing memory initialized by `read` above. while offset < len { let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; if hdr.nlmsg_seq == nlmsg_seq { @@ -343,6 +346,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> if fd == -1 { return Err(Error::last_os_error()); } + // Let `OwnedFd` take care of closing the socket. let fd = unsafe { OwnedFd::from_raw_fd(fd) }; let if_index = if_index(remote, fd.as_fd())?; diff --git a/src/windows/mod.rs b/src/windows/mod.rs index f0c63482..a12a0f69 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -25,6 +25,15 @@ use crate::{ #[allow(clippy::semicolon_if_nothing_returned, clippy::struct_field_names)] mod win_bindings; +struct MibTablePtr(*mut MIB_IPINTERFACE_TABLE); + +impl Drop for MibTablePtr { + fn drop(&mut self) { + // Free the memory allocated by GetIpInterfaceTable. + unsafe { FreeMibTable(self.0 as *const c_void) }; + } +} + pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Convert remote to Windows SOCKADDR_INET format. The SOCKADDR_INET union contains an IPv4 or // an IPv6 address. We allocate and zero-initialize it here. @@ -70,15 +79,16 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Get a list of all interfaces with associated metadata. - let mut if_table: *mut MIB_IPINTERFACE_TABLE = ptr::null_mut(); - if unsafe { GetIpInterfaceTable(AF_UNSPEC, &mut if_table) } != NO_ERROR { + let mut if_table = MibTablePtr(ptr::null_mut()); + // GetIpInterfaceTable allocates memory, which MibTablePtr::drop will free. + if unsafe { GetIpInterfaceTable(AF_UNSPEC, ptr::from_mut(&mut if_table.0)) } != NO_ERROR { return Err(Error::last_os_error()); } - let if_table = if_table; // Do not modify this pointer. + // Make a slice let ifaces = unsafe { slice::from_raw_parts::( - &(*if_table).Table[0], - (*if_table).NumEntries as usize, + &(*if_table.0).Table[0], + (*if_table.0).NumEntries as usize, ) }; @@ -87,11 +97,11 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> if iface.InterfaceIndex == idx { if let Ok(mtu) = iface.NlMtu.try_into() { let mut name = [0u8; 256]; // IF_NAMESIZE not available? + // if_indextoname writes into the provided buffer. if unsafe { !if_indextoname(iface.InterfaceIndex, &mut name).is_null() } { if let Ok(name) = CStr::from_bytes_until_nul(&name) { if let Ok(name) = name.to_str() { // We found our interface information. - unsafe { FreeMibTable(if_table as *const c_void) }; return Ok((name.to_string(), mtu)); } } @@ -100,7 +110,5 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> break; } } - - unsafe { FreeMibTable(if_table as *const c_void) }; Err(default_err()) } From 017dac4459ba71496b9347c25b67d959dae4baa4 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 28 Oct 2024 19:52:22 +0200 Subject: [PATCH 096/128] Use bindgen --- Cargo.toml | 11 +- build.rs | 82 +++++- src/{bsd/mod.rs => bsd.rs} | 40 +-- src/bsd/apple.rs | 12 - src/bsd/freebsd.rs | 48 ---- src/bsd/netbsd.rs | 44 ---- src/bsd/openbsd.rs | 51 ---- src/linux.rs | 39 +-- src/{windows/mod.rs => windows.rs} | 20 +- src/windows/win_bindings.rs | 400 ----------------------------- tests/win_bindings.rs | 38 --- 11 files changed, 124 insertions(+), 661 deletions(-) rename src/{bsd/mod.rs => bsd.rs} (89%) delete mode 100644 src/bsd/apple.rs delete mode 100644 src/bsd/freebsd.rs delete mode 100644 src/bsd/netbsd.rs delete mode 100644 src/bsd/openbsd.rs rename src/{windows/mod.rs => windows.rs} (88%) delete mode 100644 src/windows/win_bindings.rs delete mode 100644 tests/win_bindings.rs diff --git a/Cargo.toml b/Cargo.toml index 69baa20d..d8a3eb22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,17 +26,20 @@ maintenance = { status = "actively-developed", branch = "main" } libc = { version = ">=0.2.161", default-features = false } static_assertions = { version = "1.1", default-features = false } -[target."cfg(windows)".dependencies] +[target.'cfg(windows)'.dependencies] # Don't increase beyond what Firefox is currently using: https://searchfox.org/mozilla-central/source/Cargo.lock windows-core = "0.58" windows-targets = "0.52" -[target."cfg(windows)".dev-dependencies] -windows-bindgen = { version = "0.58" } # MSRV is 1.70 - [build-dependencies] cfg_aliases = "0.2" +[target.'cfg(not(any(apple, windows)))'.build-dependencies] +bindgen = { version = "0.69.5" } + +[target.'cfg(windows)'.build-dependencies] +windows-bindgen = { version = "0.58" } + [lints.clippy] cargo = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } diff --git a/build.rs b/build.rs index 30458642..9a1cf798 100644 --- a/build.rs +++ b/build.rs @@ -4,11 +4,88 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use cfg_aliases::cfg_aliases; +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "visionos" +)))] +pub const BINDINGS: &str = "bindings.rs"; + +#[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux" +))] +fn bindgen() { + #[cfg(target_os = "linux")] + let bindings = bindgen::Builder::default() + .header_contents("rtnetlink.h", "#include ") + // Only generate bindings for the following types + .allowlist_type("rtattr|rtmsg|ifinfomsg"); + #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))] + let bindings = bindgen::Builder::default() + .header_contents( + "route.h", + "#include \n#include \n#include ", + ) + // Only generate bindings for the following types + .allowlist_type("rt_msghdr"); + let bindings = bindings + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(BINDINGS); + bindings + .write_to_file(out_path.clone()) + .expect("Couldn't write bindings!"); + println!("cargo:rustc-env=BINDINGS={}", out_path.display()); +} + +#[cfg(windows)] +fn bindgen() { + let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(BINDINGS); + windows_bindgen::bindgen([ + "--out", + out_path.to_str().unwrap(), + "--config", + "flatten", + "no-inner-attributes", + "--filter", + "Windows.Win32.Foundation.NO_ERROR", + "Windows.Win32.Networking.WinSock.AF_INET", + "Windows.Win32.Networking.WinSock.AF_INET6", + "Windows.Win32.Networking.WinSock.AF_UNSPEC", + "Windows.Win32.Networking.WinSock.SOCKADDR_INET", + "Windows.Win32.NetworkManagement.IpHelper.FreeMibTable", + "Windows.Win32.NetworkManagement.IpHelper.GetBestInterfaceEx", + "Windows.Win32.NetworkManagement.IpHelper.GetIpInterfaceTable", + "Windows.Win32.NetworkManagement.IpHelper.if_indextoname", + "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_ROW", + "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_TABLE", + ]) + .unwrap(); + println!("cargo:rustc-env=BINDINGS={}", out_path.display()); +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "visionos" +))] +const fn bindgen() {} fn main() { // Setup cfg aliases - cfg_aliases! { + cfg_aliases::cfg_aliases! { // Platforms apple: { any( @@ -26,4 +103,5 @@ fn main() { ) } } + bindgen(); } diff --git a/src/bsd/mod.rs b/src/bsd.rs similarity index 89% rename from src/bsd/mod.rs rename to src/bsd.rs index f3445668..e152ad59 100644 --- a/src/bsd/mod.rs +++ b/src/bsd.rs @@ -20,26 +20,32 @@ use libc::{ }; use static_assertions::{const_assert, const_assert_eq}; -#[cfg(apple)] -use crate::bsd::apple::{rt_msghdr, ALIGN, RTM_ADDRS}; -#[cfg(target_os = "freebsd")] -use crate::bsd::freebsd::{rt_msghdr, ALIGN, RTM_ADDRS}; -#[cfg(target_os = "netbsd")] -use crate::bsd::netbsd::{rt_msghdr, ALIGN, RTM_ADDRS}; -#[cfg(target_os = "openbsd")] -use crate::bsd::openbsd::{rt_msghdr, ALIGN, RTM_ADDRS}; +#[cfg(not(apple))] +use crate::bsd::bindings::rt_msghdr; -#[cfg(apple)] -mod apple; +#[cfg(not(apple))] +#[allow(non_camel_case_types)] +mod bindings { + include!(env!("BINDINGS")); +} -#[cfg(target_os = "freebsd")] -mod freebsd; +#[cfg(any(apple, target_os = "freebsd", target_os = "openbsd"))] +const RTM_ADDRS: i32 = libc::RTA_DST; #[cfg(target_os = "netbsd")] -mod netbsd; +const RTM_ADDRS: i32 = libc::RTA_DST | libc::RTA_IFP; -#[cfg(target_os = "openbsd")] -mod openbsd; +#[cfg(apple)] +const ALIGN: usize = size_of::(); + +#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] +// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L362-L371 +// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L329-L331 +const ALIGN: usize = size_of::(); + +#[cfg(apple)] +#[allow(non_camel_case_types)] +type rt_msghdr = libc::rt_msghdr; use crate::{aligned_by, default_err}; @@ -76,7 +82,7 @@ impl Drop for IfAddrPtr { } } -pub fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { +fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { let mut name = [0; libc::IF_NAMESIZE]; // if_indextoname writes into the provided buffer. if unsafe { if_indextoname(idx, name.as_mut_ptr()).is_null() } { @@ -142,7 +148,7 @@ fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { dst } -pub fn if_index(remote: IpAddr) -> Result { +fn if_index(remote: IpAddr) -> Result { // Open route socket. let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; if fd == -1 { diff --git a/src/bsd/apple.rs b/src/bsd/apple.rs deleted file mode 100644 index 14bf29c7..00000000 --- a/src/bsd/apple.rs +++ /dev/null @@ -1,12 +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. - -pub const RTM_ADDRS: i32 = libc::RTA_DST; - -pub const ALIGN: usize = 4; - -#[allow(non_camel_case_types)] -pub type rt_msghdr = libc::rt_msghdr; diff --git a/src/bsd/freebsd.rs b/src/bsd/freebsd.rs deleted file mode 100644 index fd0ecec8..00000000 --- a/src/bsd/freebsd.rs +++ /dev/null @@ -1,48 +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. - -pub const RTM_ADDRS: i32 = libc::RTA_DST; - -// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L362-L371 -pub const ALIGN: usize = std::mem::size_of::(); - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] -// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L79-L93 -pub struct rt_metrics { - pub rmx_locks: libc::c_ulong, // Kernel must leave these values alone - pub rmx_mtu: libc::c_ulong, // MTU for this path - pub rmx_hopcount: libc::c_ulong, // max hops expected - pub rmx_expire: libc::c_ulong, // lifetime for route, e.g. redirect - pub rmx_recvpipe: libc::c_ulong, // inbound delay-bandwidth product - pub rmx_sendpipe: libc::c_ulong, // outbound delay-bandwidth product - pub rmx_ssthresh: libc::c_ulong, // outbound gateway buffer limit - pub rmx_rtt: libc::c_ulong, // estimated round trip time - pub rmx_rttvar: libc::c_ulong, // estimated rtt variance - pub rmx_pksent: libc::c_ulong, // packets sent using this route - pub rmx_weight: libc::u_long, // route weight - pub rmx_nhidx: libc::u_long, // route nexhop index - pub rmx_filler: [libc::c_ulong; 2], // will be used for T/TCP later -} - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] -// See https://github.com/freebsd/freebsd-src/blob/524a425d30fce3d5e47614db796046830b1f6a83/sys/net/route.h#L249-L263 -pub struct rt_msghdr { - pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages - pub rtm_version: libc::c_uchar, // future binary compatibility - pub rtm_type: libc::c_uchar, // message type - pub rtm_index: libc::c_ushort, // index for associated ifp - pub _rtm_spare1: libc::c_ushort, - pub rtm_flags: libc::c_int, // flags, incl. kern & message, e.g. DONE - pub rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg - pub rtm_pid: libc::pid_t, // identify sender - pub rtm_seq: libc::c_int, // for sender to identify action - pub rtm_errno: libc::c_int, // why failed - pub rtm_fmask: libc::c_int, // bitmask used in RTM_CHANGE message - pub rtm_inits: libc::u_long, // which metrics we are initializing - pub rtm_rmx: rt_metrics, // metrics themselves -} diff --git a/src/bsd/netbsd.rs b/src/bsd/netbsd.rs deleted file mode 100644 index 0c4a0ac9..00000000 --- a/src/bsd/netbsd.rs +++ /dev/null @@ -1,44 +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. - -pub const RTM_ADDRS: i32 = libc::RTA_DST | libc::RTA_IFP; - -// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L329-L331 -pub const ALIGN: usize = std::mem::size_of::(); - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] -// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L77-L88 -pub struct rt_metrics { - pub rmx_locks: u64, // Kernel must leave these values alone - pub rmx_mtu: u64, // MTU for this path - pub rmx_hopcount: u64, // max hops expected - pub rmx_recvpipe: u64, // inbound delay-bandwidth product - pub rmx_sendpipe: u64, // outbound delay-bandwidth product - pub rmx_ssthresh: u64, // outbound gateway buffer limit - pub rmx_rtt: u64, // estimated round trip time - pub rmx_rttvar: u64, // estimated rtt variance - pub rmx_expire: libc::time_t, // lifetime for route, e.g. redirect - pub rmx_pksent: libc::time_t, // packets sent using this route -} - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C, align(8))] -// See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L219-L234 -pub struct rt_msghdr { - pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages - pub rtm_version: libc::c_uchar, // future binary compatibility - pub rtm_type: libc::c_uchar, // message type - pub rtm_index: libc::c_ushort, // index for associated ifp - pub rtm_flags: libc::c_int, // flags, incl. kern & message, e.g. DONE - pub rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg - pub rtm_pid: libc::pid_t, // identify sender - pub rtm_seq: libc::c_int, // for sender to identify action - pub rtm_errno: libc::c_int, // why failed - pub rtm_use: libc::c_int, // from rtentry - pub rtm_inits: libc::c_int, // which metrics we are initializing - pub rtm_rmx: rt_metrics, // metrics themselves -} diff --git a/src/bsd/openbsd.rs b/src/bsd/openbsd.rs deleted file mode 100644 index 43a441f8..00000000 --- a/src/bsd/openbsd.rs +++ /dev/null @@ -1,51 +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. - -pub const RTM_ADDRS: i32 = libc::RTA_DST; - -// See https://github.com/openbsd/src/blob/d16b38913788369d27d69c304ba0cf7026e7c3fc/sbin/route/route.c#L146-L148 -pub const ALIGN: usize = std::mem::size_of::(); - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] -// See https://github.com/openbsd/src/blob/d16b38913788369d27d69c304ba0cf7026e7c3fc/sys/net/route.h#L71-L85 -pub struct rt_metrics { - pub rmx_pksent: u64, // packets sent using this route - pub rmx_expire: i64, // lifetime for route, e.g. redirect - pub rmx_locks: libc::c_uint, // Kernel must leave these values - pub rmx_mtu: libc::c_uint, // MTU for this path - pub rmx_refcnt: libc::c_uint, // # references hold - // some apps may still need these no longer used metrics - pub rmx_hopcount: libc::c_uint, // max hops expected - pub rmx_recvpipe: libc::c_uint, // inbound delay-bandwidth product - pub rmx_sendpipe: libc::c_uint, // outbound delay-bandwidth product - pub rmx_ssthresh: libc::c_uint, // outbound gateway buffer limit - pub rmx_rtt: libc::c_uint, // estimated round trip time - pub rmx_rttvar: libc::c_uint, // estimated rtt variance - pub rmx_pad: libc::c_uint, -} - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] -// See https://github.com/openbsd/src/blob/d16b38913788369d27d69c304ba0cf7026e7c3fc/sys/net/route.h#L221-L238 -pub struct rt_msghdr { - pub rtm_msglen: libc::c_ushort, // to skip over non-understood messages - pub rtm_version: libc::c_uchar, // future binary compatibility - pub rtm_type: libc::c_uchar, // message type - pub rtm_hdrlen: libc::c_ushort, // sizeof(rt_msghdr) to skip over the header - pub rtm_index: libc::c_ushort, // index for associated ifp - pub rtm_tableid: libc::c_ushort, // routing table id - pub rtm_priority: libc::c_uchar, // routing priority - pub rtm_mpls: libc::c_uchar, // MPLS additional infos - pub rtm_addrs: libc::c_int, // bitmask identifying sockaddrs in msg - pub rtm_flags: libc::c_int, // flags, incl. kern & message, e.g. DONE - pub rtm_fmask: libc::c_int, // bitmask used in RTM_CHANGE message - pub rtm_pid: libc::pid_t, // identify sender - pub rtm_seq: libc::c_int, // for sender to identify action - pub rtm_errno: libc::c_int, // why failed - pub rtm_inits: libc::c_uint, // which metrics we are initializing - pub rtm_rmx: rt_metrics, // metrics themselves -} diff --git a/src/linux.rs b/src/linux.rs index dfb3a239..0f834820 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -4,6 +4,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![allow(clippy::struct_field_names)] +include!(concat!(env!("OUT_DIR"), "/route.rs")); + use std::{ ffi::CStr, io::{Error, ErrorKind}, @@ -15,7 +18,7 @@ use std::{ }; use libc::{ - c_int, c_uchar, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, + c_int, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLMSG_ERROR, NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, @@ -55,40 +58,6 @@ const_assert!(size_of::() <= u8::MAX as usize); const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] -// See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L561-L568 -struct ifinfomsg { - ifi_family: c_uchar, // AF_UNSPEC - ifi_type: c_ushort, // Device type - ifi_index: c_int, // Interface index - ifi_flags: c_uint, // Device flags - ifi_change: c_uint, // change mask -} - -#[allow(non_camel_case_types, clippy::struct_field_names)] -#[repr(C)] -// See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L237-L249 -struct rtmsg { - rtm_family: c_uchar, // Address family of route - rtm_dst_len: c_uchar, // Length of destination - rtm_src_len: c_uchar, // Length of source - rtm_tos: c_uchar, // TOS filter - rtm_table: c_uchar, // Routing table ID; see RTA_TABLE below - rtm_protocol: c_uchar, // Routing protocol; see below - rtm_scope: c_uchar, // See below - rtm_type: c_uchar, // See below - rtm_flags: c_uint, -} - -#[allow(non_camel_case_types)] -#[repr(C)] -// See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L211-L214 -struct rtattr { - rta_len: c_ushort, // Length of option - rta_type: c_ushort, // Type of option -} // Data follows - /// Prepare an error for cases that "should never happen". fn unlikely_err(msg: String) -> Error { debug_assert!(false, "{msg}"); diff --git a/src/windows/mod.rs b/src/windows.rs similarity index 88% rename from src/windows/mod.rs rename to src/windows.rs index a12a0f69..f48004aa 100644 --- a/src/windows/mod.rs +++ b/src/windows.rs @@ -12,18 +12,18 @@ use std::{ ptr, slice, }; -use crate::{ - default_err, - windows::win_bindings::{ - if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, - AF_UNSPEC, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, - MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_INET, - }, +use bindings::{ + if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, + AF_UNSPEC, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, + MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_INET, }; -// Though the module includes `allow(clippy::all)`, that doesn't seem to affect some lints -#[allow(clippy::semicolon_if_nothing_returned, clippy::struct_field_names)] -mod win_bindings; +use crate::default_err; + +#[allow(non_camel_case_types, non_snake_case)] +mod bindings { + include!(env!("BINDINGS")); +} struct MibTablePtr(*mut MIB_IPINTERFACE_TABLE); diff --git a/src/windows/win_bindings.rs b/src/windows/win_bindings.rs deleted file mode 100644 index 1cd2af83..00000000 --- a/src/windows/win_bindings.rs +++ /dev/null @@ -1,400 +0,0 @@ -// Bindings generated by `windows-bindgen` 0.58.0 - -#![allow( - non_snake_case, - non_upper_case_globals, - non_camel_case_types, - dead_code, - clippy::all -)] -#[inline] -pub unsafe fn FreeMibTable(memory: *const core::ffi::c_void) { - windows_targets::link!("iphlpapi.dll" "system" fn FreeMibTable(memory : *const core::ffi::c_void)); - FreeMibTable(memory) -} -#[inline] -pub unsafe fn GetBestInterfaceEx(pdestaddr: *const SOCKADDR, pdwbestifindex: *mut u32) -> u32 { - windows_targets::link!("iphlpapi.dll" "system" fn GetBestInterfaceEx(pdestaddr : *const SOCKADDR, pdwbestifindex : *mut u32) -> u32); - GetBestInterfaceEx(pdestaddr, pdwbestifindex) -} -#[inline] -pub unsafe fn GetIpInterfaceTable( - family: ADDRESS_FAMILY, - table: *mut *mut MIB_IPINTERFACE_TABLE, -) -> WIN32_ERROR { - windows_targets::link!("iphlpapi.dll" "system" fn GetIpInterfaceTable(family : ADDRESS_FAMILY, table : *mut *mut MIB_IPINTERFACE_TABLE) -> WIN32_ERROR); - GetIpInterfaceTable(family, table) -} -#[inline] -pub unsafe fn if_indextoname( - interfaceindex: u32, - interfacename: &mut [u8; 256], -) -> windows_core::PSTR { - windows_targets::link!("iphlpapi.dll" "system" fn if_indextoname(interfaceindex : u32, interfacename : windows_core::PSTR) -> windows_core::PSTR); - if_indextoname(interfaceindex, core::mem::transmute(interfacename.as_ptr())) -} -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct ADDRESS_FAMILY(pub u16); -impl windows_core::TypeKind for ADDRESS_FAMILY { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for ADDRESS_FAMILY { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("ADDRESS_FAMILY").field(&self.0).finish() - } -} -pub const AF_INET: ADDRESS_FAMILY = ADDRESS_FAMILY(2u16); -pub const AF_INET6: ADDRESS_FAMILY = ADDRESS_FAMILY(23u16); -pub const AF_UNSPEC: ADDRESS_FAMILY = ADDRESS_FAMILY(0u16); -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct BOOLEAN(pub u8); -impl Default for BOOLEAN { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -impl windows_core::TypeKind for BOOLEAN { - type TypeKind = windows_core::CopyType; -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct IN6_ADDR { - pub u: IN6_ADDR_0, -} -impl windows_core::TypeKind for IN6_ADDR { - type TypeKind = windows_core::CopyType; -} -impl Default for IN6_ADDR { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub union IN6_ADDR_0 { - pub Byte: [u8; 16], - pub Word: [u16; 8], -} -impl windows_core::TypeKind for IN6_ADDR_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for IN6_ADDR_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct IN_ADDR { - pub S_un: IN_ADDR_0, -} -impl windows_core::TypeKind for IN_ADDR { - type TypeKind = windows_core::CopyType; -} -impl Default for IN_ADDR { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub union IN_ADDR_0 { - pub S_un_b: IN_ADDR_0_0, - pub S_un_w: IN_ADDR_0_1, - pub S_addr: u32, -} -impl windows_core::TypeKind for IN_ADDR_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for IN_ADDR_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct IN_ADDR_0_0 { - pub s_b1: u8, - pub s_b2: u8, - pub s_b3: u8, - pub s_b4: u8, -} -impl windows_core::TypeKind for IN_ADDR_0_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for IN_ADDR_0_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct IN_ADDR_0_1 { - pub s_w1: u16, - pub s_w2: u16, -} -impl windows_core::TypeKind for IN_ADDR_0_1 { - type TypeKind = windows_core::CopyType; -} -impl Default for IN_ADDR_0_1 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct MIB_IPINTERFACE_ROW { - pub Family: ADDRESS_FAMILY, - pub InterfaceLuid: NET_LUID_LH, - pub InterfaceIndex: u32, - pub MaxReassemblySize: u32, - pub InterfaceIdentifier: u64, - pub MinRouterAdvertisementInterval: u32, - pub MaxRouterAdvertisementInterval: u32, - pub AdvertisingEnabled: BOOLEAN, - pub ForwardingEnabled: BOOLEAN, - pub WeakHostSend: BOOLEAN, - pub WeakHostReceive: BOOLEAN, - pub UseAutomaticMetric: BOOLEAN, - pub UseNeighborUnreachabilityDetection: BOOLEAN, - pub ManagedAddressConfigurationSupported: BOOLEAN, - pub OtherStatefulConfigurationSupported: BOOLEAN, - pub AdvertiseDefaultRoute: BOOLEAN, - pub RouterDiscoveryBehavior: NL_ROUTER_DISCOVERY_BEHAVIOR, - pub DadTransmits: u32, - pub BaseReachableTime: u32, - pub RetransmitTime: u32, - pub PathMtuDiscoveryTimeout: u32, - pub LinkLocalAddressBehavior: NL_LINK_LOCAL_ADDRESS_BEHAVIOR, - pub LinkLocalAddressTimeout: u32, - pub ZoneIndices: [u32; 16], - pub SitePrefixLength: u32, - pub Metric: u32, - pub NlMtu: u32, - pub Connected: BOOLEAN, - pub SupportsWakeUpPatterns: BOOLEAN, - pub SupportsNeighborDiscovery: BOOLEAN, - pub SupportsRouterDiscovery: BOOLEAN, - pub ReachableTime: u32, - pub TransmitOffload: NL_INTERFACE_OFFLOAD_ROD, - pub ReceiveOffload: NL_INTERFACE_OFFLOAD_ROD, - pub DisableDefaultRoutes: BOOLEAN, -} -impl windows_core::TypeKind for MIB_IPINTERFACE_ROW { - type TypeKind = windows_core::CopyType; -} -impl Default for MIB_IPINTERFACE_ROW { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct MIB_IPINTERFACE_TABLE { - pub NumEntries: u32, - pub Table: [MIB_IPINTERFACE_ROW; 1], -} -impl windows_core::TypeKind for MIB_IPINTERFACE_TABLE { - type TypeKind = windows_core::CopyType; -} -impl Default for MIB_IPINTERFACE_TABLE { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub union NET_LUID_LH { - pub Value: u64, - pub Info: NET_LUID_LH_0, -} -impl windows_core::TypeKind for NET_LUID_LH { - type TypeKind = windows_core::CopyType; -} -impl Default for NET_LUID_LH { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct NET_LUID_LH_0 { - pub _bitfield: u64, -} -impl windows_core::TypeKind for NET_LUID_LH_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for NET_LUID_LH_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct NL_INTERFACE_OFFLOAD_ROD { - pub _bitfield: u8, -} -impl windows_core::TypeKind for NL_INTERFACE_OFFLOAD_ROD { - type TypeKind = windows_core::CopyType; -} -impl Default for NL_INTERFACE_OFFLOAD_ROD { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct NL_LINK_LOCAL_ADDRESS_BEHAVIOR(pub i32); -impl windows_core::TypeKind for NL_LINK_LOCAL_ADDRESS_BEHAVIOR { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for NL_LINK_LOCAL_ADDRESS_BEHAVIOR { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NL_LINK_LOCAL_ADDRESS_BEHAVIOR") - .field(&self.0) - .finish() - } -} -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct NL_ROUTER_DISCOVERY_BEHAVIOR(pub i32); -impl windows_core::TypeKind for NL_ROUTER_DISCOVERY_BEHAVIOR { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for NL_ROUTER_DISCOVERY_BEHAVIOR { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NL_ROUTER_DISCOVERY_BEHAVIOR") - .field(&self.0) - .finish() - } -} -pub const NO_ERROR: WIN32_ERROR = WIN32_ERROR(0u32); -#[repr(C)] -#[derive(Clone, Copy)] -pub struct SCOPE_ID { - pub Anonymous: SCOPE_ID_0, -} -impl windows_core::TypeKind for SCOPE_ID { - type TypeKind = windows_core::CopyType; -} -impl Default for SCOPE_ID { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub union SCOPE_ID_0 { - pub Anonymous: SCOPE_ID_0_0, - pub Value: u32, -} -impl windows_core::TypeKind for SCOPE_ID_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for SCOPE_ID_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct SCOPE_ID_0_0 { - pub _bitfield: u32, -} -impl windows_core::TypeKind for SCOPE_ID_0_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for SCOPE_ID_0_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct SOCKADDR { - pub sa_family: ADDRESS_FAMILY, - pub sa_data: [i8; 14], -} -impl windows_core::TypeKind for SOCKADDR { - type TypeKind = windows_core::CopyType; -} -impl Default for SOCKADDR { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct SOCKADDR_IN { - pub sin_family: ADDRESS_FAMILY, - pub sin_port: u16, - pub sin_addr: IN_ADDR, - pub sin_zero: [i8; 8], -} -impl windows_core::TypeKind for SOCKADDR_IN { - type TypeKind = windows_core::CopyType; -} -impl Default for SOCKADDR_IN { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub struct SOCKADDR_IN6 { - pub sin6_family: ADDRESS_FAMILY, - pub sin6_port: u16, - pub sin6_flowinfo: u32, - pub sin6_addr: IN6_ADDR, - pub Anonymous: SOCKADDR_IN6_0, -} -impl windows_core::TypeKind for SOCKADDR_IN6 { - type TypeKind = windows_core::CopyType; -} -impl Default for SOCKADDR_IN6 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub union SOCKADDR_IN6_0 { - pub sin6_scope_id: u32, - pub sin6_scope_struct: SCOPE_ID, -} -impl windows_core::TypeKind for SOCKADDR_IN6_0 { - type TypeKind = windows_core::CopyType; -} -impl Default for SOCKADDR_IN6_0 { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Clone, Copy)] -pub union SOCKADDR_INET { - pub Ipv4: SOCKADDR_IN, - pub Ipv6: SOCKADDR_IN6, - pub si_family: ADDRESS_FAMILY, -} -impl windows_core::TypeKind for SOCKADDR_INET { - type TypeKind = windows_core::CopyType; -} -impl Default for SOCKADDR_INET { - fn default() -> Self { - unsafe { core::mem::zeroed() } - } -} -#[repr(transparent)] -#[derive(PartialEq, Eq, Copy, Clone, Default)] -pub struct WIN32_ERROR(pub u32); -impl windows_core::TypeKind for WIN32_ERROR { - type TypeKind = windows_core::CopyType; -} -impl core::fmt::Debug for WIN32_ERROR { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("WIN32_ERROR").field(&self.0).finish() - } -} diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs deleted file mode 100644 index 17881e48..00000000 --- a/tests/win_bindings.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![cfg(windows)] - -use std::fs; - -#[test] -fn codegen_windows_bindings() { - let existing = fs::read_to_string(TARGET).unwrap_or_default(); - windows_bindgen::bindgen([ - "--out", - TARGET, - "--config", - "flatten", - "--filter", - "Windows.Win32.Foundation.NO_ERROR", - "Windows.Win32.Networking.WinSock.AF_INET", - "Windows.Win32.Networking.WinSock.AF_INET6", - "Windows.Win32.Networking.WinSock.AF_UNSPEC", - "Windows.Win32.Networking.WinSock.SOCKADDR_INET", - "Windows.Win32.NetworkManagement.IpHelper.FreeMibTable", - "Windows.Win32.NetworkManagement.IpHelper.GetBestInterfaceEx", - "Windows.Win32.NetworkManagement.IpHelper.GetIpInterfaceTable", - "Windows.Win32.NetworkManagement.IpHelper.if_indextoname", - "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_ROW", - "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_TABLE", - ]) - .unwrap(); - - // Check the output is the same as before. - // Depending on the git configuration the file may have been checked out with `\r\n` newlines or - // with `\n`. Compare line-by-line to ignore this difference. - let new = fs::read_to_string(TARGET).unwrap(); - if !new.lines().eq(existing.lines()) { - println!("{new}"); - panic!("generated file `{TARGET}` is changed"); - } -} - -const TARGET: &str = "src/windows/win_bindings.rs"; From 88a6363ea12bc4a215ccd10bb064679ba1fe56ce Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 29 Oct 2024 13:04:49 +0200 Subject: [PATCH 097/128] Address code review --- Cargo.toml | 2 +- build.rs | 40 ++-- src/bsd.rs | 241 ++++++++++++-------- src/lib.rs | 10 +- src/linux.rs | 543 +++++++++++++++++++++++++-------------------- src/routesocket.rs | 61 +++++ src/windows.rs | 89 +++++--- 7 files changed, 588 insertions(+), 398 deletions(-) create mode 100644 src/routesocket.rs diff --git a/Cargo.toml b/Cargo.toml index d8a3eb22..e14f7313 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ windows-targets = "0.52" [build-dependencies] cfg_aliases = "0.2" -[target.'cfg(not(any(apple, windows)))'.build-dependencies] +[target.'cfg(not(windows))'.build-dependencies] bindgen = { version = "0.69.5" } [target.'cfg(windows)'.build-dependencies] diff --git a/build.rs b/build.rs index 9a1cf798..a4843034 100644 --- a/build.rs +++ b/build.rs @@ -4,44 +4,39 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[cfg(not(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "visionos" -)))] pub const BINDINGS: &str = "bindings.rs"; -#[cfg(any( - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux" -))] +#[cfg(not(windows))] fn bindgen() { #[cfg(target_os = "linux")] let bindings = bindgen::Builder::default() .header_contents("rtnetlink.h", "#include ") // Only generate bindings for the following types - .allowlist_type("rtattr|rtmsg|ifinfomsg"); - #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))] + .allowlist_type("rtattr|rtmsg|ifinfomsg|nlmsghdr"); + #[cfg(not(target_os = "linux"))] let bindings = bindgen::Builder::default() .header_contents( "route.h", "#include \n#include \n#include ", ) // Only generate bindings for the following types - .allowlist_type("rt_msghdr"); + .allowlist_type("rt_msghdr|rt_metrics"); let bindings = bindings // Tell cargo to invalidate the built crate whenever any of the // included header files changed. .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + // Constants should be generated as &CStr instead of &[u8]. + .generate_cstr(true) + // Always emit explicit padding fields. + .explicit_padding(true) + // Default trait should be derived when possible + .derive_default(true) // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. .expect("Unable to generate bindings"); - // Write the bindings to the $OUT_DIR/bindings.rs file. + // Write the bindings to the $OUT_DIR/$BINDINGS file. let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join(BINDINGS); bindings .write_to_file(out_path.clone()) @@ -58,6 +53,7 @@ fn bindgen() { "--config", "flatten", "no-inner-attributes", + "minimal", "--filter", "Windows.Win32.Foundation.NO_ERROR", "Windows.Win32.Networking.WinSock.AF_INET", @@ -69,20 +65,12 @@ fn bindgen() { "Windows.Win32.NetworkManagement.IpHelper.GetIpInterfaceTable", "Windows.Win32.NetworkManagement.IpHelper.if_indextoname", "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_ROW", - "Windows.Win32.NetworkManagement.IpHelper.MIB_IPINTERFACE_TABLE", + "Windows.Win32.NetworkManagement.Ndis.IF_MAX_STRING_SIZE", ]) - .unwrap(); + .expect("Couldn't write bindings!"); println!("cargo:rustc-env=BINDINGS={}", out_path.display()); } -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "visionos" -))] -const fn bindgen() {} - fn main() { // Setup cfg aliases cfg_aliases::cfg_aliases! { diff --git a/src/bsd.rs b/src/bsd.rs index e152ad59..00c76005 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -6,25 +6,27 @@ use std::{ ffi::CStr, - io::Error, - mem::{size_of, zeroed}, + io::{Error, ErrorKind, Read, Write}, + mem::size_of, net::IpAddr, - os::fd::{AsRawFd, FromRawFd, OwnedFd}, - ptr, + os::fd::AsRawFd, + ptr, slice, }; use libc::{ - freeifaddrs, getifaddrs, getpid, if_data, if_indextoname, ifaddrs, read, sockaddr_in, - sockaddr_in6, sockaddr_storage, socket, write, AF_INET, AF_INET6, AF_LINK, AF_UNSPEC, PF_ROUTE, - RTAX_MAX, RTM_GET, RTM_VERSION, SOCK_RAW, + freeifaddrs, getifaddrs, getpid, if_data, if_indextoname, ifaddrs, in6_addr, in_addr, + sockaddr_in, sockaddr_in6, sockaddr_storage, AF_INET, AF_INET6, AF_LINK, AF_UNSPEC, PF_ROUTE, + RTAX_MAX, RTM_GET, RTM_VERSION, }; use static_assertions::{const_assert, const_assert_eq}; -#[cfg(not(apple))] -use crate::bsd::bindings::rt_msghdr; +use crate::{bsd::bindings::rt_msghdr, routesocket::RouteSocket}; -#[cfg(not(apple))] -#[allow(non_camel_case_types)] +#[allow( + non_camel_case_types, + clippy::struct_field_names, + clippy::too_many_lines +)] mod bindings { include!(env!("BINDINGS")); } @@ -43,10 +45,6 @@ const ALIGN: usize = size_of::(); // See https://github.com/NetBSD/src/blob/4b50954e98313db58d189dd87b4541929efccb09/sys/net/route.h#L329-L331 const ALIGN: usize = size_of::(); -#[cfg(apple)] -#[allow(non_camel_case_types)] -type rt_msghdr = libc::rt_msghdr; - use crate::{aligned_by, default_err}; #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. @@ -69,16 +67,24 @@ const_assert_eq!(RTM_VERSION_U8 as i32, RTM_VERSION); const RTM_GET_U8: u8 = RTM_GET as u8; const_assert_eq!(RTM_GET_U8 as i32, RTM_GET); -const_assert!(size_of::() <= u8::MAX as usize); -const_assert!(size_of::() <= u8::MAX as usize); +const_assert!(size_of::() + ALIGN <= u8::MAX as usize); +const_assert!(size_of::() + ALIGN <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); struct IfAddrPtr(*mut ifaddrs); +impl Default for IfAddrPtr { + fn default() -> Self { + Self(ptr::null_mut()) + } +} + impl Drop for IfAddrPtr { fn drop(&mut self) { - // Free the memory allocated by `getifaddrs`. - unsafe { freeifaddrs(self.0) }; + if !self.0.is_null() { + // Free the memory allocated by `getifaddrs`. + unsafe { freeifaddrs(self.0) }; + } } } @@ -92,10 +98,10 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { let name = unsafe { CStr::from_ptr(name.as_ptr()) .to_str() - .map_err(|_| default_err())? + .map_err(|err| Error::new(ErrorKind::Other, err))? }; - let mut ifap = IfAddrPtr(ptr::null_mut()); + let mut ifap = IfAddrPtr::default(); // getifaddrs allocates memory for the linked list of interfaces that is freed by // `IfAddrPtr::drop`. if unsafe { getifaddrs(ptr::from_mut(&mut ifap.0)) } != 0 { @@ -111,7 +117,7 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { let ifa_name = unsafe { CStr::from_ptr(ifa.ifa_name) .to_str() - .map_err(|_| default_err())? + .map_err(|err| Error::new(ErrorKind::Other, err))? }; if ifa_addr.sa_family == AF_LINK_U8 && !ifa.ifa_data.is_null() && ifa_name == name { let ifa_data = unsafe { *(ifa.ifa_data as *const if_data) }; @@ -125,98 +131,137 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { Err(default_err()) } -fn as_sockaddr_storage(ip: IpAddr) -> sockaddr_storage { - let mut dst: sockaddr_storage = unsafe { zeroed() }; - match ip { - #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. - IpAddr::V4(ip) => { - // Reinterpret the `sockaddr_storage` as `sockaddr_in`. - let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; - sin.sin_len = size_of::() as u8; - sin.sin_family = AF_INET_U8; - sin.sin_addr.s_addr = u32::from_ne_bytes(ip.octets()); - } - #[allow(clippy::cast_possible_truncation)] // Guarded by `const_assert!` above. - IpAddr::V6(ip) => { - // Reinterpret the `sockaddr_storage` as `sockaddr_in6`. - let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; - sin6.sin6_len = size_of::() as u8; - sin6.sin6_family = AF_INET6_U8; - sin6.sin6_addr.s6_addr = ip.octets(); +#[repr(C)] +union SockaddrStorage { + sin: sockaddr_in, + sin6: sockaddr_in6, +} + +impl SockaddrStorage { + const fn len(&self) -> u8 { + unsafe { self.sin.sin_len } + } +} + +impl From for SockaddrStorage { + fn from(ip: IpAddr) -> Self { + match ip { + IpAddr::V4(ip) => SockaddrStorage { + sin: sockaddr_in { + #[allow(clippy::cast_possible_truncation)] + // `sockaddr_in` len is <= u8::MAX per `const_assert!` above. + sin_len: size_of::() as u8, + sin_family: AF_INET_U8, + sin_addr: in_addr { + s_addr: u32::from_ne_bytes(ip.octets()), + }, + sin_port: 0, + sin_zero: [0; 8], + }, + }, + IpAddr::V6(ip) => SockaddrStorage { + sin6: sockaddr_in6 { + #[allow(clippy::cast_possible_truncation)] + // `sockaddr_in6` len is <= u8::MAX per `const_assert!` above. + sin6_len: size_of::() as u8, + sin6_family: AF_INET6_U8, + sin6_addr: in6_addr { + s6_addr: ip.octets(), + }, + sin6_port: 0, + sin6_flowinfo: 0, + sin6_scope_id: 0, + }, + }, } - }; - dst + } } -fn if_index(remote: IpAddr) -> Result { - // Open route socket. - let fd = unsafe { socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC) }; - if fd == -1 { - return Err(Error::last_os_error()); +#[repr(C)] +struct RouteMessage { + rtm: rt_msghdr, + sa: SockaddrStorage, +} + +impl RouteMessage { + fn new(remote: IpAddr, seq: i32) -> Self { + let sa = SockaddrStorage::from(remote); + Self { + rtm: rt_msghdr { + #[allow(clippy::cast_possible_truncation)] + // `rt_msghdr` len + `ALIGN` is <= u8::MAX per `const_assert!` above. + rtm_msglen: (size_of::() + aligned_by(sa.len().into(), ALIGN)) as u16, + rtm_version: RTM_VERSION_U8, + rtm_type: RTM_GET_U8, + rtm_seq: seq, + rtm_addrs: RTM_ADDRS, + ..Default::default() + }, + sa, + } } - // Let OwnedFd take care of closing the file descriptor. - let fd = unsafe { OwnedFd::from_raw_fd(fd) }; - - // Prepare buffer with destination `sockaddr`. - let dst = as_sockaddr_storage(remote); - - // Prepare route message structure. - let mut query: rt_msghdr = unsafe { zeroed() }; - #[allow(clippy::cast_possible_truncation)] - // Structs len is <= u8::MAX per `const_assert!`s above; `aligned_by` returns max. 16 for IPv6. - let rtm_msglen = (size_of::() + aligned_by(dst.ss_len.into(), ALIGN)) as u16; // Length includes sockaddr - query.rtm_msglen = rtm_msglen; - query.rtm_version = RTM_VERSION_U8; - query.rtm_type = RTM_GET_U8; - query.rtm_seq = fd.as_raw_fd(); // Abuse file descriptor as sequence number, since it's unique - query.rtm_addrs = RTM_ADDRS; - #[cfg(target_os = "openbsd")] - { - query.rtm_hdrlen = size_of::() as libc::c_ushort; + + const fn version(&self) -> u8 { + self.rtm.rtm_version } - // Copy route message and destination `sockaddr` into message buffer. - let mut msg: Vec = vec![0; query.rtm_msglen.into()]; - unsafe { - ptr::copy_nonoverlapping( - ptr::from_ref(&query).cast(), - msg.as_mut_ptr(), - size_of::(), - ); - ptr::copy_nonoverlapping( - ptr::from_ref(&dst).cast(), - msg.as_mut_ptr() - .add(aligned_by(size_of::(), ALIGN)), - dst.ss_len.into(), - ); + const fn seq(&self) -> i32 { + self.rtm.rtm_seq } - // Send route message. - let res = unsafe { write(fd.as_raw_fd(), msg.as_ptr().cast(), msg.len()) }; - if res == -1 { - return Err(Error::last_os_error()); + const fn kind(&self) -> u8 { + self.rtm.rtm_type + } +} + +impl From for &[u8] { + fn from(value: RouteMessage) -> Self { + unsafe { + slice::from_raw_parts( + ptr::from_ref(&value).cast(), + size_of::() + aligned_by(value.sa.len().into(), ALIGN), + ) + } } +} + +impl From> for rt_msghdr { + fn from(value: Vec) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { ptr::read_unaligned(value.as_ptr().cast()) } + } +} + +fn if_index(remote: IpAddr) -> Result { + // Open route socket. + let mut fd = RouteSocket::new(PF_ROUTE, AF_UNSPEC)?; + + // Send route message. + let query = RouteMessage::new(remote, fd.as_raw_fd()); + let query_version = query.version(); + let query_seq = query.seq(); + let query_type = query.kind(); + fd.write_all(query.into())?; // Read route messages. - let mut buf = vec![ - 0u8; - size_of::() + + let pid = unsafe { getpid() }; + loop { + let mut buf = vec![ + 0u8; + size_of::() + // There will never be `RTAX_MAX` sockaddrs attached, but it's a safe upper bound. (RTAX_MAX as usize * size_of::()) - ]; - loop { - let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; - if len <= 0 { - return Err(Error::last_os_error()); + ]; + let len = fd.read(buf.as_mut_slice())?; + if len < size_of::() { + return Err(default_err()); } - let reply = unsafe { ptr::read_unaligned(buf.as_ptr().cast::()) }; - if reply.rtm_version == query.rtm_version - && reply.rtm_pid == unsafe { getpid() } - && reply.rtm_seq == query.rtm_seq + let reply: rt_msghdr = buf.into(); + if reply.rtm_version == query_version && reply.rtm_pid == pid && reply.rtm_seq == query_seq { // This is a reply to our query. - return if reply.rtm_type == query.rtm_type { - // This is *the* reply we are looking for. + return if reply.rtm_type == query_type { + // This is the reply we are looking for. Ok(reply.rtm_index) } else { Err(default_err()) diff --git a/src/lib.rs b/src/lib.rs index 93fec11c..de2d2419 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,6 @@ use bsd::interface_and_mtu_impl; use linux::interface_and_mtu_impl; #[cfg(target_os = "windows")] use windows::interface_and_mtu_impl; - #[cfg(any(apple, bsd))] mod bsd; @@ -63,11 +62,20 @@ mod linux; #[cfg(target_os = "windows")] mod windows; +#[cfg(not(target_os = "windows"))] +mod routesocket; + /// Prepare a default error. fn default_err() -> Error { Error::new(ErrorKind::NotFound, "Local interface MTU not found") } +/// Prepare an error for cases that "should never happen". +fn unlikely_err(msg: String) -> Error { + debug_assert!(false, "{msg}"); + Error::new(ErrorKind::Other, msg) +} + /// Align `size` to the next multiple of `align` (which needs to be a power of two). #[cfg(not(target_os = "windows"))] const fn aligned_by(size: usize, align: usize) -> usize { diff --git a/src/linux.rs b/src/linux.rs index 0f834820..814c2966 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -4,28 +4,33 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![allow(clippy::struct_field_names)] -include!(concat!(env!("OUT_DIR"), "/route.rs")); - use std::{ ffi::CStr, - io::{Error, ErrorKind}, - mem::{size_of, zeroed}, + io::{Error, ErrorKind, Read, Write}, + mem::size_of, net::IpAddr, num::TryFromIntError, - os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, - ptr, + ptr, slice, }; +use bindings::{ifinfomsg, nlmsghdr, rtattr, rtmsg}; use libc::{ - c_int, c_uint, c_ushort, nlmsghdr, read, socket, write, AF_INET, AF_INET6, AF_NETLINK, - AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLMSG_ERROR, NLM_F_ACK, - NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, - RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, SOCK_RAW, + c_int, AF_INET, AF_INET6, AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, + NETLINK_ROUTE, NLMSG_ERROR, NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, + RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, }; use static_assertions::{const_assert, const_assert_eq}; -use crate::{aligned_by, default_err}; +use crate::{aligned_by, default_err, routesocket::RouteSocket, unlikely_err}; + +#[allow( + clippy::struct_field_names, + non_camel_case_types, + clippy::too_many_lines +)] +mod bindings { + include!(env!("BINDINGS")); +} #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. const AF_INET_U8: u8 = AF_INET as u8; @@ -58,266 +63,326 @@ const_assert!(size_of::() <= u8::MAX as usize); const NETLINK_BUFFER_SIZE: usize = 8192; // See netlink(7) man page. -/// Prepare an error for cases that "should never happen". -fn unlikely_err(msg: String) -> Error { - debug_assert!(false, "{msg}"); - Error::new(ErrorKind::Other, msg) +#[repr(C)] +enum AddrBytes { + V4([u8; 4]), + V6([u8; 16]), } -const fn addr_len(remote: &IpAddr) -> u8 { - match remote { - IpAddr::V4(_) => 32, - IpAddr::V6(_) => 128, +impl AddrBytes { + const fn new(ip: IpAddr) -> Self { + match ip { + IpAddr::V4(ip) => Self::V4(ip.octets()), + IpAddr::V6(ip) => Self::V6(ip.octets()), + } + } + + const fn len(&self) -> usize { + match self { + Self::V4(_) => 4, + Self::V6(_) => 16, + } } } -fn addr_bytes(remote: &IpAddr) -> Vec { - match remote { - IpAddr::V4(ip) => ip.octets().to_vec(), - IpAddr::V6(ip) => ip.octets().to_vec(), +impl From for [u8; 16] { + fn from(addr: AddrBytes) -> Self { + match addr { + AddrBytes::V4(bytes) => { + let mut v6 = [0; 16]; + v6[..4].copy_from_slice(&bytes); + v6 + } + AddrBytes::V6(bytes) => bytes, + } } } -const fn prepare_nlmsg(nlmsg_type: c_ushort, nlmsg_len: u32, nlmsg_seq: u32) -> libc::nlmsghdr { - let mut nlm = unsafe { zeroed::() }; - nlm.nlmsg_len = nlmsg_len; - nlm.nlmsg_type = nlmsg_type; - nlm.nlmsg_flags = NLM_F_REQUEST_U16 | NLM_F_ACK_U16; - nlm.nlmsg_seq = nlmsg_seq; - nlm +impl Default for AddrBytes { + fn default() -> Self { + Self::V6([0; 16]) + } } -fn if_index(remote: IpAddr, fd: BorrowedFd) -> Result { - // Prepare RTM_GETROUTE message. - #[allow(clippy::cast_possible_truncation)] - // Structs lens are <= u8::MAX per `const_assert!`s above; `addr_bytes` is max. 16 for IPv6. - let nlmsg_len = (size_of::() - + size_of::() - + size_of::() - + addr_bytes(&remote).len()) as u32; - #[allow(clippy::cast_sign_loss)] - // OK, because they are the same length and we just care about a unique value here. - let nlmsg_seq = fd.as_raw_fd() as u32; - let hdr = prepare_nlmsg(RTM_GETROUTE, nlmsg_len, nlmsg_seq); - - let mut rtm = unsafe { zeroed::() }; - rtm.rtm_family = match remote { - IpAddr::V4(_) => AF_INET_U8, - IpAddr::V6(_) => AF_INET6_U8, - }; - rtm.rtm_dst_len = addr_len(&remote); - rtm.rtm_table = RT_TABLE_MAIN; - rtm.rtm_scope = RT_SCOPE_UNIVERSE; - rtm.rtm_type = RTN_UNICAST; - - let mut attr = unsafe { zeroed::() }; - #[allow(clippy::cast_possible_truncation)] - // Structs len is <= u8::MAX per `const_assert!` above; `addr_bytes` is max. 16 for IPv6. - let rta_len = (size_of::() + addr_bytes(&remote).len()) as u16; - attr.rta_len = rta_len; - attr.rta_type = RTA_DST; - - let mut buf = vec![0u8; nlmsg_len as usize]; - unsafe { - ptr::copy_nonoverlapping( - ptr::from_ref(&hdr).cast(), - buf.as_mut_ptr(), - size_of::(), - ); - ptr::copy_nonoverlapping( - ptr::from_ref(&rtm).cast(), - buf.as_mut_ptr().add(size_of::()), - size_of::(), - ); - ptr::copy_nonoverlapping( - ptr::from_ref(&attr).cast(), - buf.as_mut_ptr() - .add(size_of::() + size_of::()), - size_of::(), - ); - ptr::copy_nonoverlapping( - addr_bytes(&remote).as_ptr(), - buf.as_mut_ptr() - .add(size_of::() + size_of::() + size_of::()), - addr_bytes(&remote).len(), - ); - }; +#[repr(C)] +#[derive(Default)] +struct IfIndexMsg { + nlmsg: nlmsghdr, + rtm: rtmsg, + rt: rtattr, + addr: [u8; 16], +} - // Send RTM_GETROUTE message to get the interface index associated with the destination. - // Accesses the initialized memory in `buf`. - if unsafe { write(fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) } < 0 { - return Err(Error::last_os_error()); +impl IfIndexMsg { + fn new(remote: IpAddr, nlmsg_seq: u32) -> Self { + let addr = AddrBytes::new(remote); + #[allow(clippy::cast_possible_truncation)] + // Structs lens are <= u8::MAX per `const_assert!`s above; `addr_bytes` is max. 16 for IPv6. + let nlmsg_len = + (size_of::() + size_of::() + size_of::() + addr.len()) as u32; + Self { + nlmsg: nlmsghdr { + nlmsg_len, + nlmsg_type: RTM_GETROUTE, + nlmsg_flags: NLM_F_REQUEST_U16 | NLM_F_ACK_U16, + nlmsg_seq, + ..Default::default() + }, + rtm: rtmsg { + rtm_family: match remote { + IpAddr::V4(_) => AF_INET_U8, + IpAddr::V6(_) => AF_INET6_U8, + }, + rtm_dst_len: match remote { + IpAddr::V4(_) => 32, + IpAddr::V6(_) => 128, + }, + rtm_table: RT_TABLE_MAIN, + rtm_scope: RT_SCOPE_UNIVERSE, + rtm_type: RTN_UNICAST, + ..Default::default() + }, + rt: rtattr { + #[allow(clippy::cast_possible_truncation)] + // Structs len is <= u8::MAX per `const_assert!` above; `addr_bytes` is max. 16 for IPv6. + rta_len: (size_of::() + addr.len()) as u16, + rta_type: RTA_DST, + }, + addr: addr.into(), + } } - // Receive RTM_GETROUTE response. + const fn len(&self) -> usize { + self.nlmsg.nlmsg_len as usize + } + + const fn seq(&self) -> u32 { + self.nlmsg.nlmsg_seq + } +} + +impl From for &[u8] { + fn from(value: IfIndexMsg) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } + } +} + +// FIXME: Is there a way to avoid all of these similar `From` functions for each type? + +impl From<&[u8]> for nlmsghdr { + fn from(value: &[u8]) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { ptr::read_unaligned(value.as_ptr().cast()) } + } +} + +impl From> for rtattr { + fn from(value: Vec) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { ptr::read_unaligned(value.as_ptr().cast()) } + } +} + +fn parse_c_int(buf: &[u8]) -> Result { + // TODO: Use `split_at_checked` when our MSRV is >= 1.80. + if buf.len() < size_of::() { + return Err(default_err()); + } + let (bytes, _) = buf.split_at(size_of::()); + let i = c_int::from_ne_bytes(bytes.try_into().map_err(|_| default_err())?); + Ok(i) +} + +fn read_msg_with_seq( + fd: &mut RouteSocket, + seq: u32, + kind: u16, + buf: &mut Vec, +) -> Result { loop { - let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; - if len < 0 { - return Err(Error::last_os_error()); - } - #[allow(clippy::cast_sign_loss)] // We handled negative sizes above, so this is OK. - let len = len as usize; - - let mut offset = 0; - // All `unsafe` blocks are accessing memory initialized by `read` above. - while offset < len { - let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; - if hdr.nlmsg_seq == nlmsg_seq { - match hdr.nlmsg_type { - NLMSG_ERROR_U16 => { - // Extract the error code and return it. - let err: c_int = unsafe { - ptr::read_unaligned( - buf.as_ptr().add(offset + size_of::()).cast(), - ) - }; - return Err(Error::from_raw_os_error(-err)); - } - RTM_NEWROUTE => { - // This is the response, parse through the attributes to find the interface - // index. - let mut attr_ptr = unsafe { - buf.as_ptr() - .add(offset + size_of::() + size_of::()) - }; - let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; - while attr_ptr < attr_end { - let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; - if attr.rta_type == RTA_OIF { - // We have our interface index. - let idx = unsafe { - ptr::read_unaligned(attr_ptr.add(size_of::()).cast()) - }; - return Ok(idx); - } - attr_ptr = unsafe { attr_ptr.add(attr.rta_len as usize) }; - } - } - _ => (), + let len = fd.read(buf.as_mut_slice())?; + let mut next = buf.as_slice().split_at(len).0; + while size_of::() <= next.len() { + let (hdr, mut msg) = next.split_at(size_of::()); + let hdr: nlmsghdr = hdr.into(); + // `msg` has the remainder of this message plus any following messages. + // Strip those it off and assign them to `next`. + debug_assert!(size_of::() <= hdr.nlmsg_len as usize); + (msg, next) = msg.split_at(hdr.nlmsg_len as usize - size_of::()); + + if hdr.nlmsg_seq != seq { + continue; + } + + if hdr.nlmsg_type == NLMSG_ERROR_U16 { + // Extract the error code and return it. + let err = parse_c_int(msg)?; + if err != 0 { + return Err(Error::from_raw_os_error(-err)); } + } else if hdr.nlmsg_type == kind { + // Return the header and the message. + *buf = msg.to_vec(); + return Ok(hdr); } - offset += hdr.nlmsg_len as usize; } } } -fn if_name_mtu(if_index: i32, fd: BorrowedFd) -> Result<(String, usize), Error> { - // Prepare RTM_GETLINK message to get the interface name and MTU for the interface with the - // obtained index. - #[allow(clippy::cast_possible_truncation)] - // Structs lens are <= u8::MAX per `const_assert!`s above. - let nlmsg_len = (size_of::() + size_of::()) as u32; - let nlmsg_seq = 2; - let hdr = prepare_nlmsg(RTM_GETLINK, nlmsg_len, nlmsg_seq); - - let mut ifim: ifinfomsg = unsafe { zeroed() }; - ifim.ifi_family = AF_UNSPEC_U8; - ifim.ifi_type = ARPHRD_NONE; - ifim.ifi_index = if_index; - - let mut buf = vec![0u8; nlmsg_len as usize]; - unsafe { - ptr::copy_nonoverlapping( - ptr::from_ref(&hdr).cast(), - buf.as_mut_ptr(), - size_of::(), - ); - ptr::copy_nonoverlapping( - ptr::from_ref(&ifim).cast(), - buf.as_mut_ptr().add(size_of::()), - size_of::(), - ); +impl From<&[u8]> for rtattr { + fn from(value: &[u8]) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { ptr::read_unaligned(value.as_ptr().cast()) } } +} - // Send RTM_GETLINK message. Accesses the initialized memory in `buf`. - if unsafe { write(fd.as_raw_fd(), buf.as_ptr().cast(), buf.len()) } < 0 { - return Err(Error::last_os_error()); +struct RtAttr<'a> { + hdr: rtattr, + msg: &'a [u8], +} + +impl<'a> RtAttr<'a> { + fn new(bytes: &'a [u8]) -> Self { + debug_assert!(bytes.len() >= size_of::()); + let (hdr, mut msg) = bytes.split_at(size_of::()); + let hdr: rtattr = hdr.into(); + let aligned_len = aligned_by(hdr.rta_len.into(), 4); + debug_assert!(size_of::() <= aligned_len); + (msg, _) = msg.split_at(aligned_len - size_of::()); + Self { hdr, msg } + } +} + +struct RtAttrs<'a>(&'a [u8]); + +impl<'a> Iterator for RtAttrs<'a> { + type Item = RtAttr<'a>; + + fn next(&mut self) -> Option { + if size_of::() <= self.0.len() { + let attr = RtAttr::new(self.0); + let aligned_len = aligned_by(attr.hdr.rta_len.into(), 4); + debug_assert!(self.0.len() >= aligned_len); + self.0 = self.0.split_at(aligned_len).1; + Some(attr) + } else { + None + } + } +} + +fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { + // Send RTM_GETROUTE message to get the interface index associated with the destination. + let msg = IfIndexMsg::new(remote, 1); + let msg_seq = msg.seq(); + fd.write_all(msg.into())?; + + // Receive RTM_GETROUTE response. + let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; + read_msg_with_seq(fd, msg_seq, RTM_NEWROUTE, &mut buf)?; + debug_assert!(size_of::() <= buf.len()); + let buf = buf.split_off(size_of::()); + + // Parse through the attributes to find the interface index. + for attr in RtAttrs(buf.as_slice()).by_ref() { + if attr.hdr.rta_type == RTA_OIF { + // We have our interface index. + return parse_c_int(attr.msg); + } + } + Err(default_err()) +} + +#[repr(C)] +struct IfInfoMsg { + nlmsg: nlmsghdr, + ifim: ifinfomsg, +} + +impl IfInfoMsg { + fn new(if_index: i32, nlmsg_seq: u32) -> Self { + #[allow(clippy::cast_possible_truncation)] + // Structs lens are <= u8::MAX per `const_assert!`s above. + let nlmsg_len = (size_of::() + size_of::()) as u32; + Self { + nlmsg: nlmsghdr { + nlmsg_len, + nlmsg_type: RTM_GETLINK, + nlmsg_flags: NLM_F_REQUEST_U16 | NLM_F_ACK_U16, + nlmsg_seq, + ..Default::default() + }, + ifim: ifinfomsg { + ifi_family: AF_UNSPEC_U8, + ifi_type: ARPHRD_NONE, + ifi_index: if_index, + ..Default::default() + }, + } + } + + const fn len(&self) -> usize { + self.nlmsg.nlmsg_len as usize + } + + const fn seq(&self) -> u32 { + self.nlmsg.nlmsg_seq + } +} + +impl From for &[u8] { + fn from(value: IfInfoMsg) -> Self { + unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } } +} + +fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize), Error> { + // Send RTM_GETLINK message to get interface information for the given interface index. + let msg = IfInfoMsg::new(if_index, 2); + let msg_seq = msg.seq(); + fd.write_all(msg.into())?; // Receive RTM_GETLINK response. + let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; + read_msg_with_seq(fd, msg_seq, RTM_NEWLINK, &mut buf)?; + debug_assert!(size_of::() <= buf.len()); + let buf = buf.split_off(size_of::()); + + // Parse through the attributes to find the interface name and MTU. let mut ifname = None; let mut mtu = None; - 'recv: loop { - let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - let len = unsafe { read(fd.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; - if len < 0 { - return Err(Error::last_os_error()); - } - #[allow(clippy::cast_sign_loss)] // We handled negative sizes above, so this is OK. - let len = len as usize; - - let mut offset = 0; - // All `unsafe` blocks are accessing memory initialized by `read` above. - while offset < len { - let hdr = unsafe { ptr::read_unaligned(buf.as_ptr().add(offset).cast::()) }; - if hdr.nlmsg_seq == nlmsg_seq { - match hdr.nlmsg_type { - NLMSG_ERROR_U16 => { - // Extract the error code and return it. - let err: c_int = unsafe { - ptr::read_unaligned( - buf.as_ptr().add(offset + size_of::()).cast(), - ) - }; - return Err(Error::from_raw_os_error(-err)); - } - RTM_NEWLINK => { - let mut attr_ptr = unsafe { - buf.as_ptr() - .add(offset + size_of::() + size_of::()) - }; - let attr_end = unsafe { buf.as_ptr().add(offset + hdr.nlmsg_len as usize) }; - while attr_ptr < attr_end { - let attr = unsafe { ptr::read_unaligned(attr_ptr.cast::()) }; - if attr.rta_type == IFLA_IFNAME { - let name = unsafe { - CStr::from_ptr(attr_ptr.add(size_of::()).cast()) - }; - if let Ok(name) = name.to_str() { - // We have our interface name. - ifname = Some(name.to_string()); - } - } else if attr.rta_type == IFLA_MTU { - mtu = Some( - unsafe { - ptr::read_unaligned( - attr_ptr.add(size_of::()).cast::(), - ) - } - .try_into() - .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, - ); - } - if ifname.is_some() && mtu.is_some() { - break 'recv; - } - // See https://github.com/torvalds/linux/blob/98f7e32f20d28ec452afb208f9cffc08448a2652/include/uapi/linux/rtnetlink.h#L218 - let incr = aligned_by(attr.rta_len as usize, 4); - attr_ptr = unsafe { attr_ptr.add(incr) }; - } - } - _ => (), - } + for attr in RtAttrs(buf.as_slice()).by_ref() { + match attr.hdr.rta_type { + IFLA_IFNAME => { + let name = CStr::from_bytes_until_nul(attr.msg) + .map_err(|err| Error::new(ErrorKind::Other, err))?; + ifname = Some( + name.to_str() + .map_err(|err| Error::new(ErrorKind::Other, err))? + .to_string(), + ); } - offset += hdr.nlmsg_len as usize; + IFLA_MTU => { + mtu = Some( + parse_c_int(attr.msg)? + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?, + ); + } + _ => (), + } + if let (Some(ifname), Some(mtu)) = (ifname.as_ref(), mtu.as_ref()) { + return Ok((ifname.clone(), *mtu)); } } - let name = ifname.ok_or_else(default_err)?; - let mtu = mtu.ok_or_else(default_err)?; - Ok((name, mtu)) + Err(default_err()) } pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Create a netlink socket. - let fd = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) }; - if fd == -1 { - return Err(Error::last_os_error()); - } - // Let `OwnedFd` take care of closing the socket. - let fd = unsafe { OwnedFd::from_raw_fd(fd) }; - - let if_index = if_index(remote, fd.as_fd())?; - if_name_mtu(if_index, fd.as_fd()) + let mut fd = RouteSocket::new(AF_NETLINK, NETLINK_ROUTE)?; + let if_index = if_index(remote, &mut fd)?; + if_name_mtu(if_index, &mut fd) } diff --git a/src/routesocket.rs b/src/routesocket.rs new file mode 100644 index 00000000..6d39722c --- /dev/null +++ b/src/routesocket.rs @@ -0,0 +1,61 @@ +// 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, Read, Write}, + num::TryFromIntError, + os::fd::{AsRawFd, FromRawFd, OwnedFd}, +}; + +use libc::{read, socket, write, SOCK_RAW}; + +use crate::unlikely_err; + +pub struct RouteSocket(OwnedFd); + +impl RouteSocket { + pub fn new(domain: libc::c_int, protocol: libc::c_int) -> Result { + let fd = unsafe { socket(domain, SOCK_RAW, protocol) }; + if fd == -1 { + return Err(Error::last_os_error()); + } + Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) + } +} + +impl AsRawFd for RouteSocket { + fn as_raw_fd(&self) -> i32 { + self.0.as_raw_fd() + } +} + +fn check_result(res: isize) -> Result { + if res == -1 { + Err(Error::last_os_error()) + } else { + Ok(res + .try_into() + .map_err(|e: TryFromIntError| unlikely_err(e.to_string()))?) + } +} + +impl Write for RouteSocket { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let res = unsafe { write(self.as_raw_fd(), buf.as_ptr().cast(), buf.len()) }; + check_result(res) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl Read for RouteSocket { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let res = unsafe { read(self.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; + check_result(res) + } +} diff --git a/src/windows.rs b/src/windows.rs index f48004aa..20684fe4 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -7,60 +7,80 @@ use std::{ ffi::{c_void, CStr}, io::Error, - mem, net::IpAddr, ptr, slice, }; use bindings::{ if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, - AF_UNSPEC, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, + AF_UNSPEC, IF_MAX_STRING_SIZE, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_INET, }; use crate::default_err; -#[allow(non_camel_case_types, non_snake_case)] +#[allow( + non_camel_case_types, + non_snake_case, + clippy::semicolon_if_nothing_returned, + clippy::missing_transmute_annotations, + clippy::upper_case_acronyms, + clippy::struct_field_names +)] mod bindings { include!(env!("BINDINGS")); } struct MibTablePtr(*mut MIB_IPINTERFACE_TABLE); +impl Default for MibTablePtr { + fn default() -> Self { + Self(ptr::null_mut()) + } +} + impl Drop for MibTablePtr { fn drop(&mut self) { - // Free the memory allocated by GetIpInterfaceTable. - unsafe { FreeMibTable(self.0 as *const c_void) }; + if !self.0.is_null() { + // Free the memory allocated by GetIpInterfaceTable. + unsafe { FreeMibTable(self.0 as *const c_void) }; + } } } pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { // Convert remote to Windows SOCKADDR_INET format. The SOCKADDR_INET union contains an IPv4 or - // an IPv6 address. We allocate and zero-initialize it here. + // an IPv6 address. // // See https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-sockaddr_inet - - let mut dst: SOCKADDR_INET = unsafe { mem::zeroed() }; - match remote { + let dst = match remote { IpAddr::V4(ip) => { // Initialize the `SOCKADDR_IN` variant of `SOCKADDR_INET` based on `ip`. - let sin = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; - sin.sin_family = AF_INET; - sin.sin_addr = IN_ADDR { - S_un: IN_ADDR_0 { - S_addr: u32::to_be(ip.into()), + SOCKADDR_INET { + Ipv4: SOCKADDR_IN { + sin_family: AF_INET, + sin_addr: IN_ADDR { + S_un: IN_ADDR_0 { + S_addr: u32::to_be(ip.into()), + }, + }, + ..Default::default() }, } } IpAddr::V6(ip) => { // Initialize the `SOCKADDR_IN6` variant of `SOCKADDR_INET` based on `ip`. - let sin6 = unsafe { &mut *ptr::from_mut(&mut dst).cast::() }; - sin6.sin6_family = AF_INET6; - sin6.sin6_addr = IN6_ADDR { - u: IN6_ADDR_0 { Byte: ip.octets() }, - }; + SOCKADDR_INET { + Ipv6: SOCKADDR_IN6 { + sin6_family: AF_INET6, + sin6_addr: IN6_ADDR { + u: IN6_ADDR_0 { Byte: ip.octets() }, + }, + ..Default::default() + }, + } } - } + }; // Get the interface index of the best outbound interface towards `dst`. let mut idx = 0; @@ -79,7 +99,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> } // Get a list of all interfaces with associated metadata. - let mut if_table = MibTablePtr(ptr::null_mut()); + let mut if_table = MibTablePtr::default(); // GetIpInterfaceTable allocates memory, which MibTablePtr::drop will free. if unsafe { GetIpInterfaceTable(AF_UNSPEC, ptr::from_mut(&mut if_table.0)) } != NO_ERROR { return Err(Error::last_os_error()); @@ -95,19 +115,22 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> // Find the local interface matching `idx`. for iface in ifaces { if iface.InterfaceIndex == idx { - if let Ok(mtu) = iface.NlMtu.try_into() { - let mut name = [0u8; 256]; // IF_NAMESIZE not available? - // if_indextoname writes into the provided buffer. - if unsafe { !if_indextoname(iface.InterfaceIndex, &mut name).is_null() } { - if let Ok(name) = CStr::from_bytes_until_nul(&name) { - if let Ok(name) = name.to_str() { - // We found our interface information. - return Ok((name.to_string(), mtu)); - } - } - } + // Get the MTU. + let mtu: usize = iface.NlMtu.try_into().or(Err(default_err()))?; + // Get the interface name. + let mut interfacename = [0u8; IF_MAX_STRING_SIZE as usize]; + // if_indextoname writes into the provided buffer. + if unsafe { if_indextoname(iface.InterfaceIndex, &mut interfacename).is_null() } { + return Err(default_err()); } - break; + // Convert the interface name to a Rust string. + let name = CStr::from_bytes_until_nul(interfacename.as_ref()) + .or(Err(default_err()))? + .to_str() + .map_err(|err| Error::new(ErrorKind::Other, err))? + .to_string(); + // We found our interface information. + return Ok((name, mtu)); } } Err(default_err()) From e02ccc215151c2f6a167b1285ad23f83352e33cc Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 10:44:20 +0200 Subject: [PATCH 098/128] Iterator --- src/bsd.rs | 72 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 00c76005..01b1612e 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -71,15 +71,31 @@ const_assert!(size_of::() + ALIGN <= u8::MAX as usize); const_assert!(size_of::() + ALIGN <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); -struct IfAddrPtr(*mut ifaddrs); +struct IfAddrs(*mut ifaddrs); -impl Default for IfAddrPtr { +impl Default for IfAddrs { fn default() -> Self { Self(ptr::null_mut()) } } -impl Drop for IfAddrPtr { +impl IfAddrs { + fn new() -> Result { + let mut ifap = Self::default(); + // getifaddrs allocates memory for the linked list of interfaces that is freed by + // `IfAddrs::drop`. + if unsafe { getifaddrs(ptr::from_mut(&mut ifap.0)) } != 0 { + return Err(Error::last_os_error()); + } + Ok(ifap) + } + + const fn iter(&self) -> IfAddrPtr { + IfAddrPtr(self.0) + } +} + +impl Drop for IfAddrs { fn drop(&mut self) { if !self.0.is_null() { // Free the memory allocated by `getifaddrs`. @@ -88,6 +104,21 @@ impl Drop for IfAddrPtr { } } +struct IfAddrPtr(*mut ifaddrs); + +impl Iterator for IfAddrPtr { + type Item = ifaddrs; + + fn next(&mut self) -> Option { + if self.0.is_null() { + return None; + } + let ifa = unsafe { *self.0 }; + self.0 = ifa.ifa_next; + Some(ifa) + } +} + fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { let mut name = [0; libc::IF_NAMESIZE]; // if_indextoname writes into the provided buffer. @@ -101,32 +132,19 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { .map_err(|err| Error::new(ErrorKind::Other, err))? }; - let mut ifap = IfAddrPtr::default(); - // getifaddrs allocates memory for the linked list of interfaces that is freed by - // `IfAddrPtr::drop`. - if unsafe { getifaddrs(ptr::from_mut(&mut ifap.0)) } != 0 { - return Err(Error::last_os_error()); - } - - let mut ifa_next = ifap.0; - // All `unsafe` statements in this loop access memory initialized by `getifaddrs`. - while !ifa_next.is_null() { - let ifa = unsafe { *ifa_next }; - if !ifa.ifa_addr.is_null() { - let ifa_addr = unsafe { *ifa.ifa_addr }; - let ifa_name = unsafe { - CStr::from_ptr(ifa.ifa_name) - .to_str() - .map_err(|err| Error::new(ErrorKind::Other, err))? - }; - if ifa_addr.sa_family == AF_LINK_U8 && !ifa.ifa_data.is_null() && ifa_name == name { - let ifa_data = unsafe { *(ifa.ifa_data as *const if_data) }; - if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { - return Ok((name.to_string(), mtu)); - } + for ifa in IfAddrs::new()?.iter() { + let ifa_addr = unsafe { *ifa.ifa_addr }; + let ifa_name = unsafe { + CStr::from_ptr(ifa.ifa_name) + .to_str() + .map_err(|err| Error::new(ErrorKind::Other, err))? + }; + if ifa_addr.sa_family == AF_LINK_U8 && !ifa.ifa_data.is_null() && ifa_name == name { + let ifa_data = unsafe { *(ifa.ifa_data as *const if_data) }; + if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { + return Ok((name.to_string(), mtu)); } } - ifa_next = ifa.ifa_next; } Err(default_err()) } From 01ab445c99c968f701e1da8b907fbf7ba47edd16 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 10:49:48 +0200 Subject: [PATCH 099/128] Final fixes --- src/lib.rs | 1 + src/routesocket.rs | 2 ++ src/windows.rs | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index de2d2419..836bc6b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,7 @@ fn default_err() -> Error { } /// Prepare an error for cases that "should never happen". +#[cfg(not(target_os = "windows"))] fn unlikely_err(msg: String) -> Error { debug_assert!(false, "{msg}"); Error::new(ErrorKind::Other, msg) diff --git a/src/routesocket.rs b/src/routesocket.rs index 6d39722c..6b65616b 100644 --- a/src/routesocket.rs +++ b/src/routesocket.rs @@ -55,6 +55,8 @@ impl Write for RouteSocket { impl Read for RouteSocket { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // If we've written a well-formed message into the kernel via `write`, we should be able to + // read a well-formed message back out, and not block. let res = unsafe { read(self.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; check_result(res) } diff --git a/src/windows.rs b/src/windows.rs index 20684fe4..cba7bb1b 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -6,7 +6,7 @@ use std::{ ffi::{c_void, CStr}, - io::Error, + io::{Error, ErrorKind}, net::IpAddr, ptr, slice, }; From e6324137e1a25621b0882be4531a7c88eba453ab Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 11:03:49 +0200 Subject: [PATCH 100/128] Install llvm --- .github/workflows/check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index eed9ab1b..ae8dc41c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -260,7 +260,7 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg install -y curl + pkg install -y curl llvm run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" @@ -276,7 +276,7 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg_add rust # rustup doesn't support OpenBSD at all + pkg_add rust llvm # rustup doesn't support OpenBSD at all run: | ifconfig cargo check --all-targets @@ -289,7 +289,7 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - /usr/sbin/pkg_add curl + /usr/sbin/pkg_add curl llvm run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" From cccc097dcc7fdcbd02afbe85f48182988061d94d Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 11:13:05 +0200 Subject: [PATCH 101/128] Workflow fixes --- .github/workflows/check.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ae8dc41c..e85b045b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -268,7 +268,9 @@ jobs: ifconfig cargo check --all-targets cargo clippy - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + export RUST_LOG=trace + cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + cargo test --no-fail-fast --release - if: matrix.os == 'openbsd' uses: vmactions/openbsd-vm@0cfe06e734a0ea3a546fca7ebf200b984b94d58a @@ -281,7 +283,9 @@ jobs: ifconfig cargo check --all-targets cargo clippy - RUST_LOG=trace cargo test --no-fail-fast + export RUST_LOG=trace + cargo test --no-fail-fast + cargo test --no-fail-fast --release - if: matrix.os == 'netbsd' uses: vmactions/netbsd-vm@7c9086fdb4cc1aa814cda6e305390c2b966551a9 @@ -297,9 +301,12 @@ jobs: /sbin/ifconfig cargo check --all-targets cargo clippy - RUST_LOG=trace cargo test --no-fail-fast + export LIBCLANG_PATH=/usr/local/llvm16/lib + export RUST_LOG=trace + cargo test --no-fail-fast # FIXME: error[E0463]: can't find crate for `profiler_builtins` - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info || true + cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info || true + cargo test --no-fail-fast --release - if: matrix.os == 'solaris' uses: vmactions/solaris-vm@a89b9438868c70db27e41625f0a5de6ff5e90809 @@ -315,7 +322,9 @@ jobs: ifconfig cargo check --all-targets cargo clippy - RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + export RUST_LOG=trace + cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info + cargo test --no-fail-fast --release - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: From 7f70fa9df85d373c97eaedc8092a221a7aa03226 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 13:58:53 +0200 Subject: [PATCH 102/128] Hack --- src/bsd.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 01b1612e..5a52ba87 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -232,16 +232,18 @@ impl RouteMessage { } } -impl From for &[u8] { - fn from(value: RouteMessage) -> Self { - unsafe { - slice::from_raw_parts( - ptr::from_ref(&value).cast(), - size_of::() + aligned_by(value.sa.len().into(), ALIGN), - ) - } - } -} +// See FIXME below. +// +// impl From for &[u8] { +// fn from(value: RouteMessage) -> Self { +// unsafe { +// slice::from_raw_parts( +// ptr::from_ref(&value).cast(), +// size_of::() + aligned_by(value.sa.len().into(), ALIGN), +// ) +// } +// } +// } impl From> for rt_msghdr { fn from(value: Vec) -> Self { @@ -259,7 +261,15 @@ fn if_index(remote: IpAddr) -> Result { let query_version = query.version(); let query_seq = query.seq(); let query_type = query.kind(); - fd.write_all(query.into())?; + // FIXME: Why is this not working in in release mode? + // fd.write_all(query.into())?; + // Why does this work? + fd.write_all(unsafe { + slice::from_raw_parts( + ptr::from_ref(&query).cast(), + size_of::() + aligned_by(query.sa.len().into(), ALIGN), + ) + })?; // Read route messages. let pid = unsafe { getpid() }; From acda937f926bde967501dd9c6e6a08356c71f6ec Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 15:26:06 +0200 Subject: [PATCH 103/128] Fix? --- .github/workflows/check.yml | 3 ++- src/bsd.rs | 34 ++++++++++++---------------------- src/linux.rs | 17 +++++++++-------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e85b045b..cafae702 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -283,6 +283,7 @@ jobs: ifconfig cargo check --all-targets cargo clippy + export LIBCLANG_PATH=/usr/local/llvm16/lib export RUST_LOG=trace cargo test --no-fail-fast cargo test --no-fail-fast --release @@ -301,7 +302,7 @@ jobs: /sbin/ifconfig cargo check --all-targets cargo clippy - export LIBCLANG_PATH=/usr/local/llvm16/lib + export LIBCLANG_PATH=/usr/pkg/lib export RUST_LOG=trace cargo test --no-fail-fast # FIXME: error[E0463]: can't find crate for `profiler_builtins` diff --git a/src/bsd.rs b/src/bsd.rs index 5a52ba87..53fce8e1 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -230,20 +230,18 @@ impl RouteMessage { const fn kind(&self) -> u8 { self.rtm.rtm_type } + + const fn len(&self) -> usize { + self.rtm.rtm_msglen as usize + } } -// See FIXME below. -// -// impl From for &[u8] { -// fn from(value: RouteMessage) -> Self { -// unsafe { -// slice::from_raw_parts( -// ptr::from_ref(&value).cast(), -// size_of::() + aligned_by(value.sa.len().into(), ALIGN), -// ) -// } -// } -// } +impl From<&RouteMessage> for &[u8] { + fn from(value: &RouteMessage) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } + } +} impl From> for rt_msghdr { fn from(value: Vec) -> Self { @@ -257,19 +255,11 @@ fn if_index(remote: IpAddr) -> Result { let mut fd = RouteSocket::new(PF_ROUTE, AF_UNSPEC)?; // Send route message. - let query = RouteMessage::new(remote, fd.as_raw_fd()); + let query = &RouteMessage::new(remote, fd.as_raw_fd()); let query_version = query.version(); let query_seq = query.seq(); let query_type = query.kind(); - // FIXME: Why is this not working in in release mode? - // fd.write_all(query.into())?; - // Why does this work? - fd.write_all(unsafe { - slice::from_raw_parts( - ptr::from_ref(&query).cast(), - size_of::() + aligned_by(query.sa.len().into(), ALIGN), - ) - })?; + fd.write_all(query.into())?; // Read route messages. let pid = unsafe { getpid() }; diff --git a/src/linux.rs b/src/linux.rs index 814c2966..94b7416b 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -161,10 +161,10 @@ impl IfIndexMsg { } } -impl From for &[u8] { - fn from(value: IfIndexMsg) -> Self { +impl From<&IfIndexMsg> for &[u8] { + fn from(value: &IfIndexMsg) -> Self { debug_assert!(value.len() >= size_of::()); - unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } + unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } } } @@ -274,7 +274,7 @@ impl<'a> Iterator for RtAttrs<'a> { fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { // Send RTM_GETROUTE message to get the interface index associated with the destination. - let msg = IfIndexMsg::new(remote, 1); + let msg = &IfIndexMsg::new(remote, 1); let msg_seq = msg.seq(); fd.write_all(msg.into())?; @@ -331,15 +331,16 @@ impl IfInfoMsg { } } -impl From for &[u8] { - fn from(value: IfInfoMsg) -> Self { - unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } +impl From<&IfInfoMsg> for &[u8] { + fn from(value: &IfInfoMsg) -> Self { + debug_assert!(value.len() >= size_of::()); + unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } } } fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize), Error> { // Send RTM_GETLINK message to get interface information for the given interface index. - let msg = IfInfoMsg::new(if_index, 2); + let msg = &IfInfoMsg::new(if_index, 2); let msg_seq = msg.seq(); fd.write_all(msg.into())?; From a0f8aac579502b2cdf8d0c8ec397920824775128 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 15:36:00 +0200 Subject: [PATCH 104/128] CI --- .github/workflows/check.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index cafae702..2cdb3aef 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -278,12 +278,12 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg_add rust llvm # rustup doesn't support OpenBSD at all + pkg_add rust llvm-17 # rustup doesn't support OpenBSD at all run: | ifconfig cargo check --all-targets cargo clippy - export LIBCLANG_PATH=/usr/local/llvm16/lib + export LIBCLANG_PATH=/usr/local/llvm17/lib export RUST_LOG=trace cargo test --no-fail-fast cargo test --no-fail-fast --release @@ -303,6 +303,7 @@ jobs: cargo check --all-targets cargo clippy export LIBCLANG_PATH=/usr/pkg/lib + find /usr/pkg/lib export RUST_LOG=trace cargo test --no-fail-fast # FIXME: error[E0463]: can't find crate for `profiler_builtins` From 7aa13953238e32fe3e5fef9a5fea55ffdb906208 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 15:40:30 +0200 Subject: [PATCH 105/128] llvm-16.0.6p30 --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2cdb3aef..fb09e523 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -278,12 +278,12 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - pkg_add rust llvm-17 # rustup doesn't support OpenBSD at all + pkg_add rust llvm-16.0.6p30 # rustup doesn't support OpenBSD at all run: | ifconfig cargo check --all-targets cargo clippy - export LIBCLANG_PATH=/usr/local/llvm17/lib + export LIBCLANG_PATH=/usr/local/llvm16/lib export RUST_LOG=trace cargo test --no-fail-fast cargo test --no-fail-fast --release From 76010698b1f5e7cc236895b6e91710c47b20e8f0 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 15:55:49 +0200 Subject: [PATCH 106/128] netbsd --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index fb09e523..d3b282e1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -294,7 +294,8 @@ jobs: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | - /usr/sbin/pkg_add curl llvm + /usr/sbin/pkg_add pkgin + pkgin install curl llvm run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" From cefe001233fd54b7584c5d50331db488240496e2 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 16:00:44 +0200 Subject: [PATCH 107/128] netbsd --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d3b282e1..26b09284 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -295,7 +295,7 @@ jobs: envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | /usr/sbin/pkg_add pkgin - pkgin install curl llvm + pkgin -y install curl llvm run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" From a5b279509c0f568100da86f4fb680f0a3ae1ddf5 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 16:10:38 +0200 Subject: [PATCH 108/128] clang --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 26b09284..dd0e9713 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -295,7 +295,7 @@ jobs: envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" prepare: | /usr/sbin/pkg_add pkgin - pkgin -y install curl llvm + pkgin -y install curl clang run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" From c372658aff206f2ba2704f99afd24ef39486bc11 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 16:28:59 +0200 Subject: [PATCH 109/128] Remove unused code. --- src/linux.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 94b7416b..1c420067 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -98,12 +98,6 @@ impl From for [u8; 16] { } } -impl Default for AddrBytes { - fn default() -> Self { - Self::V6([0; 16]) - } -} - #[repr(C)] #[derive(Default)] struct IfIndexMsg { @@ -177,13 +171,6 @@ impl From<&[u8]> for nlmsghdr { } } -impl From> for rtattr { - fn from(value: Vec) -> Self { - debug_assert!(value.len() >= size_of::()); - unsafe { ptr::read_unaligned(value.as_ptr().cast()) } - } -} - fn parse_c_int(buf: &[u8]) -> Result { // TODO: Use `split_at_checked` when our MSRV is >= 1.80. if buf.len() < size_of::() { From fc4793f1ea5afa2c8846342eec2600ba25e49eb9 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 1 Nov 2024 16:47:57 +0200 Subject: [PATCH 110/128] More helpers --- src/bsd.rs | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 53fce8e1..46c68a2a 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -11,6 +11,7 @@ use std::{ net::IpAddr, os::fd::AsRawFd, ptr, slice, + str::Utf8Error, }; use libc::{ @@ -106,16 +107,35 @@ impl Drop for IfAddrs { struct IfAddrPtr(*mut ifaddrs); +impl IfAddrPtr { + fn addr(&self) -> libc::sockaddr { + unsafe { *(*self.0).ifa_addr } + } + + fn name(&self) -> Result<&str, Utf8Error> { + unsafe { CStr::from_ptr((*self.0).ifa_name).to_str() } + } + + fn data(&self) -> Option { + let ifa_data = unsafe { (*self.0).ifa_data }; + if ifa_data.is_null() { + None + } else { + Some(unsafe { *(ifa_data as *const if_data) }) + } + } +} + impl Iterator for IfAddrPtr { - type Item = ifaddrs; + type Item = Self; fn next(&mut self) -> Option { if self.0.is_null() { return None; } - let ifa = unsafe { *self.0 }; - self.0 = ifa.ifa_next; - Some(ifa) + let ifa = self.0; + self.0 = unsafe { (*ifa).ifa_next }; + Some(Self(ifa)) } } @@ -133,17 +153,16 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { }; for ifa in IfAddrs::new()?.iter() { - let ifa_addr = unsafe { *ifa.ifa_addr }; - let ifa_name = unsafe { - CStr::from_ptr(ifa.ifa_name) - .to_str() - .map_err(|err| Error::new(ErrorKind::Other, err))? - }; - if ifa_addr.sa_family == AF_LINK_U8 && !ifa.ifa_data.is_null() && ifa_name == name { - let ifa_data = unsafe { *(ifa.ifa_data as *const if_data) }; - if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { - return Ok((name.to_string(), mtu)); + let ifa_name = ifa + .name() + .map_err(|err| Error::new(ErrorKind::Other, err))?; + if ifa.addr().sa_family == AF_LINK_U8 && ifa_name == name { + if let Some(ifa_data) = ifa.data() { + if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { + return Ok((name.to_string(), mtu)); + } } + return Err(default_err()); } } Err(default_err()) From 91786da3310cb927d8fc3af13cd0a20163d33f7d Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Sat, 2 Nov 2024 13:32:20 +0000 Subject: [PATCH 111/128] Tweaks --- .codecov.yml | 1 - .github/workflows/check.yml | 30 ++++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index eb2806ac..a04e2e31 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,5 +1,4 @@ ignore: - - "src/windows/win_bindings.rs" # Do not notify until at least three results have been uploaded from the CI pipeline. # (This corresponds to the three main platforms we support: Linux, macOS, and Windows.) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index dd0e9713..3209a933 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -87,16 +87,22 @@ jobs: - name: Check run: | - # shellcheck disable=SC2086 - cargo +${{ matrix.rust-toolchain }} check $BUILD_TYPE --all-targets + OPTIONS=(--all-targets) + if [ "$BUILD_TYPE" ]; then + OPTIONS+=("$BUILD_TYPE") + fi + cargo +${{ matrix.rust-toolchain }} check "${OPTIONS[@]}" - name: Run tests and determine coverage env: RUST_LOG: trace run: | - # shellcheck disable=SC2086 + OPTIONS=(--no-fail-fast --lcov --output-path lcov.info) + if [ "$BUILD_TYPE" ]; then + OPTIONS+=("$BUILD_TYPE") + fi if [ "${{ matrix.rust-toolchain }}" == "stable" ] && [ "${{ matrix.type }}" == "debug" ] && [ "${{endsWith(matrix.os, '-latest') && 'latest' || '' }}" == "latest" ]; then - cargo +${{ matrix.rust-toolchain }} llvm-cov test $BUILD_TYPE --no-fail-fast --lcov --output-path lcov.info + cargo +${{ matrix.rust-toolchain }} llvm-cov test "${OPTIONS[@]}" else if [ "${{ startsWith(matrix.os, 'windows') && 'windows' || '' }}" == "windows" ]; then # The codegen_windows_bindings test only succeeds when run via llvm-cov?! @@ -264,11 +270,10 @@ jobs: run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" + export RUST_LOG=trace cargo install cargo-llvm-cov --locked - ifconfig cargo check --all-targets cargo clippy - export RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info cargo test --no-fail-fast --release @@ -280,11 +285,10 @@ jobs: prepare: | pkg_add rust llvm-16.0.6p30 # rustup doesn't support OpenBSD at all run: | - ifconfig - cargo check --all-targets - cargo clippy export LIBCLANG_PATH=/usr/local/llvm16/lib export RUST_LOG=trace + cargo check --all-targets + cargo clippy cargo test --no-fail-fast cargo test --no-fail-fast --release @@ -299,13 +303,12 @@ jobs: run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" + export LIBCLANG_PATH=/usr/pkg/lib + export RUST_LOG=trace cargo install cargo-llvm-cov --locked /sbin/ifconfig cargo check --all-targets cargo clippy - export LIBCLANG_PATH=/usr/pkg/lib - find /usr/pkg/lib - export RUST_LOG=trace cargo test --no-fail-fast # FIXME: error[E0463]: can't find crate for `profiler_builtins` cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info || true @@ -321,11 +324,10 @@ jobs: run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" + export RUST_LOG=trace cargo install cargo-llvm-cov --locked - ifconfig cargo check --all-targets cargo clippy - export RUST_LOG=trace cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info cargo test --no-fail-fast --release From 6bb132486a3b5d7eea5cb59ab386eeeb8805fce2 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Sat, 2 Nov 2024 13:45:07 +0000 Subject: [PATCH 112/128] Fix --- .github/workflows/check.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3209a933..bb8d95f4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -97,18 +97,18 @@ jobs: env: RUST_LOG: trace run: | - OPTIONS=(--no-fail-fast --lcov --output-path lcov.info) + OPTIONS=(--no-fail-fast) if [ "$BUILD_TYPE" ]; then OPTIONS+=("$BUILD_TYPE") fi if [ "${{ matrix.rust-toolchain }}" == "stable" ] && [ "${{ matrix.type }}" == "debug" ] && [ "${{endsWith(matrix.os, '-latest') && 'latest' || '' }}" == "latest" ]; then - cargo +${{ matrix.rust-toolchain }} llvm-cov test "${OPTIONS[@]}" + cargo +${{ matrix.rust-toolchain }} llvm-cov test "${OPTIONS[@]}" --lcov --output-path lcov.info else if [ "${{ startsWith(matrix.os, 'windows') && 'windows' || '' }}" == "windows" ]; then # The codegen_windows_bindings test only succeeds when run via llvm-cov?! - export FILTER="-- --skip codegen_windows_bindings" + OPTIONS+=(-- --skip codegen_windows_bindings) fi - cargo +${{ matrix.rust-toolchain }} test $BUILD_TYPE --no-fail-fast $FILTER + cargo +${{ matrix.rust-toolchain }} test "${OPTIONS[@]}" fi cargo +${{ matrix.rust-toolchain }} bench --no-run From 15ccf0b22cf7cde2093b61145c61fd678a54f1c1 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 8 Nov 2024 11:10:45 +0000 Subject: [PATCH 113/128] Address code review --- src/linux.rs | 52 +++++++++++++++++++++++++--------------------- src/routesocket.rs | 5 +++-- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 1c420067..8fc7010c 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -147,7 +147,9 @@ impl IfIndexMsg { } const fn len(&self) -> usize { - self.nlmsg.nlmsg_len as usize + let len = self.nlmsg.nlmsg_len as usize; + debug_assert!(len <= size_of::()); + len } const fn seq(&self) -> u32 { @@ -157,17 +159,18 @@ impl IfIndexMsg { impl From<&IfIndexMsg> for &[u8] { fn from(value: &IfIndexMsg) -> Self { - debug_assert!(value.len() >= size_of::()); unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } } } -// FIXME: Is there a way to avoid all of these similar `From` functions for each type? +impl TryFrom<&[u8]> for nlmsghdr { + type Error = Error; -impl From<&[u8]> for nlmsghdr { - fn from(value: &[u8]) -> Self { - debug_assert!(value.len() >= size_of::()); - unsafe { ptr::read_unaligned(value.as_ptr().cast()) } + fn try_from(value: &[u8]) -> Result { + if value.len() < size_of::() { + return Err(default_err()); + } + Ok(unsafe { ptr::read_unaligned(value.as_ptr().cast()) }) } } @@ -185,14 +188,14 @@ fn read_msg_with_seq( fd: &mut RouteSocket, seq: u32, kind: u16, - buf: &mut Vec, -) -> Result { +) -> Result<(nlmsghdr, Vec), Error> { loop { + let buf = &mut [0u8; NETLINK_BUFFER_SIZE]; let len = fd.read(buf.as_mut_slice())?; let mut next = buf.as_slice().split_at(len).0; while size_of::() <= next.len() { let (hdr, mut msg) = next.split_at(size_of::()); - let hdr: nlmsghdr = hdr.into(); + let hdr: nlmsghdr = hdr.try_into()?; // `msg` has the remainder of this message plus any following messages. // Strip those it off and assign them to `next`. debug_assert!(size_of::() <= hdr.nlmsg_len as usize); @@ -210,17 +213,20 @@ fn read_msg_with_seq( } } else if hdr.nlmsg_type == kind { // Return the header and the message. - *buf = msg.to_vec(); - return Ok(hdr); + return Ok((hdr, msg.to_vec())); } } } } -impl From<&[u8]> for rtattr { - fn from(value: &[u8]) -> Self { - debug_assert!(value.len() >= size_of::()); - unsafe { ptr::read_unaligned(value.as_ptr().cast()) } +impl TryFrom<&[u8]> for rtattr { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + if value.len() < size_of::() { + return Err(default_err()); + } + Ok(unsafe { ptr::read_unaligned(value.as_ptr().cast()) }) } } @@ -230,14 +236,14 @@ struct RtAttr<'a> { } impl<'a> RtAttr<'a> { - fn new(bytes: &'a [u8]) -> Self { + fn new(bytes: &'a [u8]) -> Result { debug_assert!(bytes.len() >= size_of::()); let (hdr, mut msg) = bytes.split_at(size_of::()); - let hdr: rtattr = hdr.into(); + let hdr: rtattr = hdr.try_into()?; let aligned_len = aligned_by(hdr.rta_len.into(), 4); debug_assert!(size_of::() <= aligned_len); (msg, _) = msg.split_at(aligned_len - size_of::()); - Self { hdr, msg } + Ok(Self { hdr, msg }) } } @@ -248,7 +254,7 @@ impl<'a> Iterator for RtAttrs<'a> { fn next(&mut self) -> Option { if size_of::() <= self.0.len() { - let attr = RtAttr::new(self.0); + let attr = RtAttr::new(self.0).ok()?; let aligned_len = aligned_by(attr.hdr.rta_len.into(), 4); debug_assert!(self.0.len() >= aligned_len); self.0 = self.0.split_at(aligned_len).1; @@ -266,8 +272,7 @@ fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { fd.write_all(msg.into())?; // Receive RTM_GETROUTE response. - let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - read_msg_with_seq(fd, msg_seq, RTM_NEWROUTE, &mut buf)?; + let (_hdr, mut buf) = read_msg_with_seq(fd, msg_seq, RTM_NEWROUTE)?; debug_assert!(size_of::() <= buf.len()); let buf = buf.split_off(size_of::()); @@ -332,8 +337,7 @@ fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize), E fd.write_all(msg.into())?; // Receive RTM_GETLINK response. - let mut buf = vec![0u8; NETLINK_BUFFER_SIZE]; - read_msg_with_seq(fd, msg_seq, RTM_NEWLINK, &mut buf)?; + let (_hdr, mut buf) = read_msg_with_seq(fd, msg_seq, RTM_NEWLINK)?; debug_assert!(size_of::() <= buf.len()); let buf = buf.split_off(size_of::()); diff --git a/src/routesocket.rs b/src/routesocket.rs index 6b65616b..0fa578dd 100644 --- a/src/routesocket.rs +++ b/src/routesocket.rs @@ -10,7 +10,7 @@ use std::{ os::fd::{AsRawFd, FromRawFd, OwnedFd}, }; -use libc::{read, socket, write, SOCK_RAW}; +use libc::{fsync, read, socket, write, SOCK_RAW}; use crate::unlikely_err; @@ -49,7 +49,8 @@ impl Write for RouteSocket { } fn flush(&mut self) -> std::io::Result<()> { - Ok(()) + let res = unsafe { fsync(self.as_raw_fd()) }; + check_result(res as isize).and(Ok(())) } } From 6b3dd15a7a2ba0e567bb1e47ecbbe0125467ba2e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 11 Nov 2024 17:42:12 +0200 Subject: [PATCH 114/128] Update .github/workflows/check.yml Co-authored-by: Martin Thomson --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 071dd906..3df1a98e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -143,7 +143,7 @@ jobs: echo "leak:fetchInitializingClassList" } > suppressions.txt PWD=$(pwd) - export LSAN_OPTIONS="suppressions=$PWD/suppressions.txt" + export LSAN_OPTIONS="suppressions=$(pwd)/suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." From fd20bfe31ef98c3bd8871ed77d016373852d634d Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 11 Nov 2024 17:42:20 +0200 Subject: [PATCH 115/128] Update .github/workflows/check.yml Co-authored-by: Martin Thomson --- .github/workflows/check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3df1a98e..295a3d54 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -142,7 +142,6 @@ jobs: echo "leak:dyld4::RuntimeState" echo "leak:fetchInitializingClassList" } > suppressions.txt - PWD=$(pwd) export LSAN_OPTIONS="suppressions=$(pwd)/suppressions.txt" fi for sanitizer in $SANITIZERS; do From 23867de32323190a34a4e984acb87b9f4addb075 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 11 Nov 2024 17:42:29 +0200 Subject: [PATCH 116/128] Update .github/workflows/check.yml Co-authored-by: Martin Thomson --- .github/workflows/check.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 295a3d54..e10da096 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -318,8 +318,6 @@ jobs: with: usesh: true envs: "CARGO_TERM_COLOR RUST_BACKTRACE GITHUB_ACTIONS" - # prepare: | - # pkg install curl run: | sh rustup.sh --default-toolchain stable --component llvm-tools -y . "$HOME/.cargo/env" From c27bbce269843ddaa1ee66132e7a2949594c2b61 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 12 Nov 2024 13:39:37 +0200 Subject: [PATCH 117/128] Update src/bsd.rs Co-authored-by: Martin Thomson --- src/bsd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bsd.rs b/src/bsd.rs index 46c68a2a..788aa3af 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -289,7 +289,7 @@ fn if_index(remote: IpAddr) -> Result { // There will never be `RTAX_MAX` sockaddrs attached, but it's a safe upper bound. (RTAX_MAX as usize * size_of::()) ]; - let len = fd.read(buf.as_mut_slice())?; + let len = fd.read(&mut buf[..])?; if len < size_of::() { return Err(default_err()); } From 226a16def85277d99d415eb3b3d08a0c477a763e Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 12 Nov 2024 13:54:05 +0200 Subject: [PATCH 118/128] Update src/linux.rs Co-authored-by: Martin Thomson --- src/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux.rs b/src/linux.rs index 8fc7010c..42ae1a40 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -192,7 +192,7 @@ fn read_msg_with_seq( loop { let buf = &mut [0u8; NETLINK_BUFFER_SIZE]; let len = fd.read(buf.as_mut_slice())?; - let mut next = buf.as_slice().split_at(len).0; + let mut next = &buf[..len]; while size_of::() <= next.len() { let (hdr, mut msg) = next.split_at(size_of::()); let hdr: nlmsghdr = hdr.try_into()?; From 58f761b13136d710a1635eaff885225d83b0ff4b Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 12 Nov 2024 14:11:10 +0000 Subject: [PATCH 119/128] Address first batch of comments from @martinthomson --- .github/workflows/check.yml | 6 +++-- src/bsd.rs | 49 +++++++++++++++++++++++++------------ src/lib.rs | 13 +++++----- src/linux.rs | 6 ++--- src/windows.rs | 12 ++++----- 5 files changed, 54 insertions(+), 32 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e10da096..51cb071f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -142,7 +142,8 @@ jobs: echo "leak:dyld4::RuntimeState" echo "leak:fetchInitializingClassList" } > suppressions.txt - export LSAN_OPTIONS="suppressions=$(pwd)/suppressions.txt" + PWD=$(pwd) + export LSAN_OPTIONS="suppressions=$PWD/suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." @@ -309,7 +310,8 @@ jobs: cargo check --all-targets cargo clippy cargo test --no-fail-fast - # FIXME: error[E0463]: can't find crate for `profiler_builtins` + # FIXME: error[E0463]: can't find crate for `profiler_builtins`, + # so don't fail the workflow when that happens. cargo llvm-cov test --no-fail-fast --lcov --output-path lcov.info || true cargo test --no-fail-fast --release diff --git a/src/bsd.rs b/src/bsd.rs index 788aa3af..b0ab4ead 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -7,8 +7,10 @@ use std::{ ffi::CStr, io::{Error, ErrorKind, Read, Write}, + marker::PhantomData, mem::size_of, net::IpAddr, + ops::Deref, os::fd::AsRawFd, ptr, slice, str::Utf8Error, @@ -21,8 +23,6 @@ use libc::{ }; use static_assertions::{const_assert, const_assert_eq}; -use crate::{bsd::bindings::rt_msghdr, routesocket::RouteSocket}; - #[allow( non_camel_case_types, clippy::struct_field_names, @@ -32,6 +32,8 @@ mod bindings { include!(env!("BINDINGS")); } +use crate::{bsd::bindings::rt_msghdr, routesocket::RouteSocket}; + #[cfg(any(apple, target_os = "freebsd", target_os = "openbsd"))] const RTM_ADDRS: i32 = libc::RTA_DST; @@ -92,7 +94,10 @@ impl IfAddrs { } const fn iter(&self) -> IfAddrPtr { - IfAddrPtr(self.0) + IfAddrPtr { + ptr: self.0, + _ref: PhantomData, + } } } @@ -105,37 +110,51 @@ impl Drop for IfAddrs { } } -struct IfAddrPtr(*mut ifaddrs); +struct IfAddrPtr<'a> { + ptr: *mut ifaddrs, + _ref: PhantomData<&'a ifaddrs>, +} -impl IfAddrPtr { +impl IfAddrPtr<'_> { fn addr(&self) -> libc::sockaddr { - unsafe { *(*self.0).ifa_addr } + unsafe { *self.ifa_addr } } fn name(&self) -> Result<&str, Utf8Error> { - unsafe { CStr::from_ptr((*self.0).ifa_name).to_str() } + unsafe { CStr::from_ptr(self.ifa_name).to_str() } } fn data(&self) -> Option { - let ifa_data = unsafe { (*self.0).ifa_data }; - if ifa_data.is_null() { + if self.ifa_data.is_null() { None } else { - Some(unsafe { *(ifa_data as *const if_data) }) + Some(unsafe { *(self.ifa_data as *const if_data) }) } } } -impl Iterator for IfAddrPtr { +impl Deref for IfAddrPtr<'_> { + type Target = ifaddrs; + + fn deref(&self) -> &Self::Target { + unsafe { self.ptr.as_ref().unwrap() } + } +} + +impl Iterator for IfAddrPtr<'_> { type Item = Self; fn next(&mut self) -> Option { - if self.0.is_null() { + // std::ptr::NonNull::new(self.0).map(|p| unsafe { p.as_ref() }) + if self.ptr.is_null() { return None; } - let ifa = self.0; - self.0 = unsafe { (*ifa).ifa_next }; - Some(Self(ifa)) + let ifa = self.ptr; + self.ptr = unsafe { (*ifa).ifa_next }; + Some(IfAddrPtr { + ptr: ifa, + _ref: PhantomData, + }) } } diff --git a/src/lib.rs b/src/lib.rs index 836bc6b9..e00ffacd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,12 +47,6 @@ use std::{ net::IpAddr, }; -#[cfg(any(apple, bsd))] -use bsd::interface_and_mtu_impl; -#[cfg(target_os = "linux")] -use linux::interface_and_mtu_impl; -#[cfg(target_os = "windows")] -use windows::interface_and_mtu_impl; #[cfg(any(apple, bsd))] mod bsd; @@ -65,6 +59,13 @@ mod windows; #[cfg(not(target_os = "windows"))] mod routesocket; +#[cfg(any(apple, bsd))] +use bsd::interface_and_mtu_impl; +#[cfg(target_os = "linux")] +use linux::interface_and_mtu_impl; +#[cfg(target_os = "windows")] +use windows::interface_and_mtu_impl; + /// Prepare a default error. fn default_err() -> Error { Error::new(ErrorKind::NotFound, "Local interface MTU not found") diff --git a/src/linux.rs b/src/linux.rs index 42ae1a40..b13cfd5a 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -13,7 +13,6 @@ use std::{ ptr, slice, }; -use bindings::{ifinfomsg, nlmsghdr, rtattr, rtmsg}; use libc::{ c_int, AF_INET, AF_INET6, AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, NLMSG_ERROR, NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, @@ -32,6 +31,8 @@ mod bindings { include!(env!("BINDINGS")); } +use bindings::{ifinfomsg, nlmsghdr, rtattr, rtmsg}; + #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. const AF_INET_U8: u8 = AF_INET as u8; const_assert_eq!(AF_INET_U8 as i32, AF_INET); @@ -175,11 +176,10 @@ impl TryFrom<&[u8]> for nlmsghdr { } fn parse_c_int(buf: &[u8]) -> Result { - // TODO: Use `split_at_checked` when our MSRV is >= 1.80. if buf.len() < size_of::() { return Err(default_err()); } - let (bytes, _) = buf.split_at(size_of::()); + let bytes = &buf[..size_of::()]; let i = c_int::from_ne_bytes(bytes.try_into().map_err(|_| default_err())?); Ok(i) } diff --git a/src/windows.rs b/src/windows.rs index cba7bb1b..8c94a92c 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -11,12 +11,6 @@ use std::{ ptr, slice, }; -use bindings::{ - if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, - AF_UNSPEC, IF_MAX_STRING_SIZE, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, - MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_INET, -}; - use crate::default_err; #[allow( @@ -31,6 +25,12 @@ mod bindings { include!(env!("BINDINGS")); } +use bindings::{ + if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, AF_INET, AF_INET6, + AF_UNSPEC, IF_MAX_STRING_SIZE, IN6_ADDR, IN6_ADDR_0, IN_ADDR, IN_ADDR_0, MIB_IPINTERFACE_ROW, + MIB_IPINTERFACE_TABLE, NO_ERROR, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_INET, +}; + struct MibTablePtr(*mut MIB_IPINTERFACE_TABLE); impl Default for MibTablePtr { From a6f8773b94b980b5b46ebc31fb199574e28db09a Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 12 Nov 2024 14:34:24 +0000 Subject: [PATCH 120/128] Suppress shellcheck warning --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 51cb071f..a0d8b337 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -142,8 +142,8 @@ jobs: echo "leak:dyld4::RuntimeState" echo "leak:fetchInitializingClassList" } > suppressions.txt - PWD=$(pwd) - export LSAN_OPTIONS="suppressions=$PWD/suppressions.txt" + # shellcheck disable=SC2155 + export LSAN_OPTIONS="suppressions=$(pwd)/suppressions.txt" fi for sanitizer in $SANITIZERS; do echo "Running tests with $sanitizer sanitizer..." From 26ea760b31cf2388cb80a295ab5a6b1c15c190a2 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 12 Nov 2024 15:06:32 +0000 Subject: [PATCH 121/128] More suggestions from @martinthomson --- src/bsd.rs | 15 ++++++--------- src/linux.rs | 9 +++------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index b0ab4ead..29a36541 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -145,15 +145,12 @@ impl Iterator for IfAddrPtr<'_> { type Item = Self; fn next(&mut self) -> Option { - // std::ptr::NonNull::new(self.0).map(|p| unsafe { p.as_ref() }) - if self.ptr.is_null() { - return None; - } - let ifa = self.ptr; - self.ptr = unsafe { (*ifa).ifa_next }; - Some(IfAddrPtr { - ptr: ifa, - _ref: PhantomData, + ptr::NonNull::new(self.ptr).map(|p| { + self.ptr = unsafe { p.as_ref().ifa_next }; + IfAddrPtr { + ptr: p.as_ptr(), + _ref: PhantomData, + } }) } } diff --git a/src/linux.rs b/src/linux.rs index b13cfd5a..179f6f02 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -176,12 +176,9 @@ impl TryFrom<&[u8]> for nlmsghdr { } fn parse_c_int(buf: &[u8]) -> Result { - if buf.len() < size_of::() { - return Err(default_err()); - } - let bytes = &buf[..size_of::()]; - let i = c_int::from_ne_bytes(bytes.try_into().map_err(|_| default_err())?); - Ok(i) + let bytes = <&[u8] as TryInto<[u8; size_of::()]>>::try_into(&buf[..size_of::()]) + .map_err(|_| default_err())?; + Ok(c_int::from_ne_bytes(bytes)) } fn read_msg_with_seq( From 269ff3416ae090c65c915383c7a7e9efcfd98d4f Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Tue, 12 Nov 2024 15:54:24 +0000 Subject: [PATCH 122/128] Use `std::io::Result` --- src/bsd.rs | 20 ++++++++------------ src/lib.rs | 4 ++-- src/linux.rs | 22 +++++++++------------- src/routesocket.rs | 6 +++--- src/windows.rs | 4 ++-- 5 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 29a36541..5507258f 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -6,14 +6,13 @@ use std::{ ffi::CStr, - io::{Error, ErrorKind, Read, Write}, + io::{Error, ErrorKind, Read, Result, Write}, marker::PhantomData, mem::size_of, net::IpAddr, ops::Deref, os::fd::AsRawFd, ptr, slice, - str::Utf8Error, }; use libc::{ @@ -83,7 +82,7 @@ impl Default for IfAddrs { } impl IfAddrs { - fn new() -> Result { + fn new() -> Result { let mut ifap = Self::default(); // getifaddrs allocates memory for the linked list of interfaces that is freed by // `IfAddrs::drop`. @@ -120,8 +119,8 @@ impl IfAddrPtr<'_> { unsafe { *self.ifa_addr } } - fn name(&self) -> Result<&str, Utf8Error> { - unsafe { CStr::from_ptr(self.ifa_name).to_str() } + fn name(&self) -> String { + unsafe { CStr::from_ptr(self.ifa_name).to_string_lossy().to_string() } } fn data(&self) -> Option { @@ -155,7 +154,7 @@ impl Iterator for IfAddrPtr<'_> { } } -fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { +fn if_name_mtu(idx: u32) -> Result<(String, usize)> { let mut name = [0; libc::IF_NAMESIZE]; // if_indextoname writes into the provided buffer. if unsafe { if_indextoname(idx, name.as_mut_ptr()).is_null() } { @@ -169,10 +168,7 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize), Error> { }; for ifa in IfAddrs::new()?.iter() { - let ifa_name = ifa - .name() - .map_err(|err| Error::new(ErrorKind::Other, err))?; - if ifa.addr().sa_family == AF_LINK_U8 && ifa_name == name { + if ifa.addr().sa_family == AF_LINK_U8 && ifa.name() == name { if let Some(ifa_data) = ifa.data() { if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { return Ok((name.to_string(), mtu)); @@ -285,7 +281,7 @@ impl From> for rt_msghdr { } } -fn if_index(remote: IpAddr) -> Result { +fn if_index(remote: IpAddr) -> Result { // Open route socket. let mut fd = RouteSocket::new(PF_ROUTE, AF_UNSPEC)?; @@ -323,7 +319,7 @@ fn if_index(remote: IpAddr) -> Result { } } -pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { let if_index = if_index(remote)?; if_name_mtu(if_index.into()) } diff --git a/src/lib.rs b/src/lib.rs index e00ffacd..d9e9b644 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ //! guidelines](CODE_OF_CONDUCT.md) beforehand. use std::{ - io::{Error, ErrorKind}, + io::{Error, ErrorKind, Result}, net::IpAddr, }; @@ -99,7 +99,7 @@ const fn aligned_by(size: usize, align: usize) -> usize { /// # Errors /// /// This function returns an error if the local interface MTU cannot be determined. -pub fn interface_and_mtu(remote: IpAddr) -> Result<(String, usize), Error> { +pub fn interface_and_mtu(remote: IpAddr) -> Result<(String, usize)> { interface_and_mtu_impl(remote) } diff --git a/src/linux.rs b/src/linux.rs index 179f6f02..50ba552c 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -6,7 +6,7 @@ use std::{ ffi::CStr, - io::{Error, ErrorKind, Read, Write}, + io::{Error, ErrorKind, Read, Result, Write}, mem::size_of, net::IpAddr, num::TryFromIntError, @@ -167,7 +167,7 @@ impl From<&IfIndexMsg> for &[u8] { impl TryFrom<&[u8]> for nlmsghdr { type Error = Error; - fn try_from(value: &[u8]) -> Result { + fn try_from(value: &[u8]) -> Result { if value.len() < size_of::() { return Err(default_err()); } @@ -175,17 +175,13 @@ impl TryFrom<&[u8]> for nlmsghdr { } } -fn parse_c_int(buf: &[u8]) -> Result { +fn parse_c_int(buf: &[u8]) -> Result { let bytes = <&[u8] as TryInto<[u8; size_of::()]>>::try_into(&buf[..size_of::()]) .map_err(|_| default_err())?; Ok(c_int::from_ne_bytes(bytes)) } -fn read_msg_with_seq( - fd: &mut RouteSocket, - seq: u32, - kind: u16, -) -> Result<(nlmsghdr, Vec), Error> { +fn read_msg_with_seq(fd: &mut RouteSocket, seq: u32, kind: u16) -> Result<(nlmsghdr, Vec)> { loop { let buf = &mut [0u8; NETLINK_BUFFER_SIZE]; let len = fd.read(buf.as_mut_slice())?; @@ -219,7 +215,7 @@ fn read_msg_with_seq( impl TryFrom<&[u8]> for rtattr { type Error = Error; - fn try_from(value: &[u8]) -> Result { + fn try_from(value: &[u8]) -> Result { if value.len() < size_of::() { return Err(default_err()); } @@ -233,7 +229,7 @@ struct RtAttr<'a> { } impl<'a> RtAttr<'a> { - fn new(bytes: &'a [u8]) -> Result { + fn new(bytes: &'a [u8]) -> Result { debug_assert!(bytes.len() >= size_of::()); let (hdr, mut msg) = bytes.split_at(size_of::()); let hdr: rtattr = hdr.try_into()?; @@ -262,7 +258,7 @@ impl<'a> Iterator for RtAttrs<'a> { } } -fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { +fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { // Send RTM_GETROUTE message to get the interface index associated with the destination. let msg = &IfIndexMsg::new(remote, 1); let msg_seq = msg.seq(); @@ -327,7 +323,7 @@ impl From<&IfInfoMsg> for &[u8] { } } -fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize), Error> { +fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize)> { // Send RTM_GETLINK message to get interface information for the given interface index. let msg = &IfInfoMsg::new(if_index, 2); let msg_seq = msg.seq(); @@ -369,7 +365,7 @@ fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize), E Err(default_err()) } -pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { // Create a netlink socket. let mut fd = RouteSocket::new(AF_NETLINK, NETLINK_ROUTE)?; let if_index = if_index(remote, &mut fd)?; diff --git a/src/routesocket.rs b/src/routesocket.rs index 0fa578dd..6708f4f9 100644 --- a/src/routesocket.rs +++ b/src/routesocket.rs @@ -5,7 +5,7 @@ // except according to those terms. use std::{ - io::{Error, Read, Write}, + io::{Error, Read, Result, Write}, num::TryFromIntError, os::fd::{AsRawFd, FromRawFd, OwnedFd}, }; @@ -17,7 +17,7 @@ use crate::unlikely_err; pub struct RouteSocket(OwnedFd); impl RouteSocket { - pub fn new(domain: libc::c_int, protocol: libc::c_int) -> Result { + pub fn new(domain: libc::c_int, protocol: libc::c_int) -> Result { let fd = unsafe { socket(domain, SOCK_RAW, protocol) }; if fd == -1 { return Err(Error::last_os_error()); @@ -32,7 +32,7 @@ impl AsRawFd for RouteSocket { } } -fn check_result(res: isize) -> Result { +fn check_result(res: isize) -> Result { if res == -1 { Err(Error::last_os_error()) } else { diff --git a/src/windows.rs b/src/windows.rs index 8c94a92c..cd371be6 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -6,7 +6,7 @@ use std::{ ffi::{c_void, CStr}, - io::{Error, ErrorKind}, + io::{Error, ErrorKind, Result}, net::IpAddr, ptr, slice, }; @@ -48,7 +48,7 @@ impl Drop for MibTablePtr { } } -pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize), Error> { +pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { // Convert remote to Windows SOCKADDR_INET format. The SOCKADDR_INET union contains an IPv4 or // an IPv6 address. // From dfdf14a41b7c7d228d64cd222868e649c9d9da47 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 15 Nov 2024 17:12:54 +0000 Subject: [PATCH 123/128] clippy --- src/windows.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/windows.rs b/src/windows.rs index cd371be6..6edeee51 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -116,7 +116,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { for iface in ifaces { if iface.InterfaceIndex == idx { // Get the MTU. - let mtu: usize = iface.NlMtu.try_into().or(Err(default_err()))?; + let mtu: usize = iface.NlMtu.try_into().map_err(|_| default_err())?; // Get the interface name. let mut interfacename = [0u8; IF_MAX_STRING_SIZE as usize]; // if_indextoname writes into the provided buffer. @@ -125,7 +125,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { } // Convert the interface name to a Rust string. let name = CStr::from_bytes_until_nul(interfacename.as_ref()) - .or(Err(default_err()))? + .map_err(|_| default_err())? .to_str() .map_err(|err| Error::new(ErrorKind::Other, err))? .to_string(); From bd0305da539686a5e6fa5d004597c4a69501aca6 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Fri, 15 Nov 2024 17:45:32 +0200 Subject: [PATCH 124/128] Nits --- .github/workflows/check.yml | 1 - Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a0d8b337..dd5ae91f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -306,7 +306,6 @@ jobs: export LIBCLANG_PATH=/usr/pkg/lib export RUST_LOG=trace cargo install cargo-llvm-cov --locked - /sbin/ifconfig cargo check --all-targets cargo clippy cargo test --no-fail-fast diff --git a/Cargo.toml b/Cargo.toml index e14f7313..c605b532 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ edition = "2021" license = "MIT OR Apache-2.0" # Don't increase beyond what Firefox is currently using: # https://searchfox.org/mozilla-central/search?q=MINIMUM_RUST_VERSION&path=python/mozboot/mozboot/util.py -# Also keep in sync with .github/workflows/check.yml rust-version = "1.76.0" [badges] From 0a07c86c1f7c454003b89872602ad95b89bffef0 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 21 Nov 2024 14:33:35 +0200 Subject: [PATCH 125/128] Suggestions from @martinthomson --- src/bsd.rs | 50 +++++++++++++++------------------ src/linux.rs | 70 ++++++++++++++++++++-------------------------- src/routesocket.rs | 17 +++++++++++ src/windows.rs | 12 ++++++-- 4 files changed, 79 insertions(+), 70 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index 5507258f..dee79e97 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -11,14 +11,12 @@ use std::{ mem::size_of, net::IpAddr, ops::Deref, - os::fd::AsRawFd, ptr, slice, }; use libc::{ freeifaddrs, getifaddrs, getpid, if_data, if_indextoname, ifaddrs, in6_addr, in_addr, - sockaddr_in, sockaddr_in6, sockaddr_storage, AF_INET, AF_INET6, AF_LINK, AF_UNSPEC, PF_ROUTE, - RTAX_MAX, RTM_GET, RTM_VERSION, + sockaddr_in, sockaddr_in6, sockaddr_storage, AF_UNSPEC, PF_ROUTE, RTAX_MAX, }; use static_assertions::{const_assert, const_assert_eq}; @@ -50,24 +48,24 @@ const ALIGN: usize = size_of::(); use crate::{aligned_by, default_err}; #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET_U8: u8 = AF_INET as u8; -const_assert_eq!(AF_INET_U8 as i32, AF_INET); +const AF_INET: u8 = libc::AF_INET as u8; +const_assert_eq!(AF_INET as i32, libc::AF_INET); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET6_U8: u8 = AF_INET6 as u8; -const_assert_eq!(AF_INET6_U8 as i32, AF_INET6); +const AF_INET6: u8 = libc::AF_INET6 as u8; +const_assert_eq!(AF_INET6 as i32, libc::AF_INET6); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_LINK_U8: u8 = AF_LINK as u8; -const_assert_eq!(AF_LINK_U8 as i32, AF_LINK); +const AF_LINK: u8 = libc::AF_LINK as u8; +const_assert_eq!(AF_LINK as i32, libc::AF_LINK); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const RTM_VERSION_U8: u8 = RTM_VERSION as u8; -const_assert_eq!(RTM_VERSION_U8 as i32, RTM_VERSION); +const RTM_VERSION: u8 = libc::RTM_VERSION as u8; +const_assert_eq!(RTM_VERSION as i32, libc::RTM_VERSION); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const RTM_GET_U8: u8 = RTM_GET as u8; -const_assert_eq!(RTM_GET_U8 as i32, RTM_GET); +const RTM_GET: u8 = libc::RTM_GET as u8; +const_assert_eq!(RTM_GET as i32, libc::RTM_GET); const_assert!(size_of::() + ALIGN <= u8::MAX as usize); const_assert!(size_of::() + ALIGN <= u8::MAX as usize); @@ -127,7 +125,7 @@ impl IfAddrPtr<'_> { if self.ifa_data.is_null() { None } else { - Some(unsafe { *(self.ifa_data as *const if_data) }) + Some(unsafe { self.ifa_data.cast::().read() }) } } } @@ -168,7 +166,7 @@ fn if_name_mtu(idx: u32) -> Result<(String, usize)> { }; for ifa in IfAddrs::new()?.iter() { - if ifa.addr().sa_family == AF_LINK_U8 && ifa.name() == name { + if ifa.addr().sa_family == AF_LINK && ifa.name() == name { if let Some(ifa_data) = ifa.data() { if let Ok(mtu) = usize::try_from(ifa_data.ifi_mtu) { return Ok((name.to_string(), mtu)); @@ -200,7 +198,7 @@ impl From for SockaddrStorage { #[allow(clippy::cast_possible_truncation)] // `sockaddr_in` len is <= u8::MAX per `const_assert!` above. sin_len: size_of::() as u8, - sin_family: AF_INET_U8, + sin_family: AF_INET, sin_addr: in_addr { s_addr: u32::from_ne_bytes(ip.octets()), }, @@ -213,7 +211,7 @@ impl From for SockaddrStorage { #[allow(clippy::cast_possible_truncation)] // `sockaddr_in6` len is <= u8::MAX per `const_assert!` above. sin6_len: size_of::() as u8, - sin6_family: AF_INET6_U8, + sin6_family: AF_INET6, sin6_addr: in6_addr { s6_addr: ip.octets(), }, @@ -240,8 +238,8 @@ impl RouteMessage { #[allow(clippy::cast_possible_truncation)] // `rt_msghdr` len + `ALIGN` is <= u8::MAX per `const_assert!` above. rtm_msglen: (size_of::() + aligned_by(sa.len().into(), ALIGN)) as u16, - rtm_version: RTM_VERSION_U8, - rtm_type: RTM_GET_U8, + rtm_version: RTM_VERSION, + rtm_type: RTM_GET, rtm_seq: seq, rtm_addrs: RTM_ADDRS, ..Default::default() @@ -254,10 +252,6 @@ impl RouteMessage { self.rtm.rtm_version } - const fn seq(&self) -> i32 { - self.rtm.rtm_seq - } - const fn kind(&self) -> u8 { self.rtm.rtm_type } @@ -267,10 +261,10 @@ impl RouteMessage { } } -impl From<&RouteMessage> for &[u8] { - fn from(value: &RouteMessage) -> Self { +impl From for &[u8] { + fn from(value: RouteMessage) -> Self { debug_assert!(value.len() >= size_of::()); - unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } + unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } } } @@ -286,9 +280,9 @@ fn if_index(remote: IpAddr) -> Result { let mut fd = RouteSocket::new(PF_ROUTE, AF_UNSPEC)?; // Send route message. - let query = &RouteMessage::new(remote, fd.as_raw_fd()); + let query_seq = RouteSocket::new_seq(); + let query = RouteMessage::new(remote, query_seq); let query_version = query.version(); - let query_seq = query.seq(); let query_type = query.kind(); fd.write_all(query.into())?; diff --git a/src/linux.rs b/src/linux.rs index 50ba552c..676478b6 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -14,9 +14,9 @@ use std::{ }; use libc::{ - c_int, AF_INET, AF_INET6, AF_NETLINK, AF_UNSPEC, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, - NETLINK_ROUTE, NLMSG_ERROR, NLM_F_ACK, NLM_F_REQUEST, RTA_DST, RTA_OIF, RTM_GETLINK, - RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, + c_int, AF_NETLINK, ARPHRD_NONE, IFLA_IFNAME, IFLA_MTU, NETLINK_ROUTE, RTA_DST, RTA_OIF, + RTM_GETLINK, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWROUTE, RTN_UNICAST, RT_SCOPE_UNIVERSE, + RT_TABLE_MAIN, }; use static_assertions::{const_assert, const_assert_eq}; @@ -34,28 +34,28 @@ mod bindings { use bindings::{ifinfomsg, nlmsghdr, rtattr, rtmsg}; #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET_U8: u8 = AF_INET as u8; -const_assert_eq!(AF_INET_U8 as i32, AF_INET); +const AF_INET: u8 = libc::AF_INET as u8; +const_assert_eq!(AF_INET as i32, libc::AF_INET); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET6_U8: u8 = AF_INET6 as u8; -const_assert_eq!(AF_INET6_U8 as i32, AF_INET6); +const AF_INET6: u8 = libc::AF_INET6 as u8; +const_assert_eq!(AF_INET6 as i32, libc::AF_INET6); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_UNSPEC_U8: u8 = AF_UNSPEC as u8; -const_assert_eq!(AF_UNSPEC_U8 as i32, AF_UNSPEC); +const AF_UNSPEC: u8 = libc::AF_UNSPEC as u8; +const_assert_eq!(AF_UNSPEC as i32, libc::AF_UNSPEC); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const NLM_F_REQUEST_U16: u16 = NLM_F_REQUEST as u16; -const_assert_eq!(NLM_F_REQUEST_U16 as c_int, NLM_F_REQUEST); +const NLM_F_REQUEST: u16 = libc::NLM_F_REQUEST as u16; +const_assert_eq!(NLM_F_REQUEST as c_int, libc::NLM_F_REQUEST); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const NLM_F_ACK_U16: u16 = NLM_F_ACK as u16; -const_assert_eq!(NLM_F_ACK_U16 as c_int, NLM_F_ACK); +const NLM_F_ACK: u16 = libc::NLM_F_ACK as u16; +const_assert_eq!(NLM_F_ACK as c_int, libc::NLM_F_ACK); #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const NLMSG_ERROR_U16: u16 = NLMSG_ERROR as u16; -const_assert_eq!(NLMSG_ERROR_U16 as c_int, NLMSG_ERROR); +const NLMSG_ERROR: u16 = libc::NLMSG_ERROR as u16; +const_assert_eq!(NLMSG_ERROR as c_int, libc::NLMSG_ERROR); const_assert!(size_of::() <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); @@ -119,14 +119,14 @@ impl IfIndexMsg { nlmsg: nlmsghdr { nlmsg_len, nlmsg_type: RTM_GETROUTE, - nlmsg_flags: NLM_F_REQUEST_U16 | NLM_F_ACK_U16, + nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK, nlmsg_seq, ..Default::default() }, rtm: rtmsg { rtm_family: match remote { - IpAddr::V4(_) => AF_INET_U8, - IpAddr::V6(_) => AF_INET6_U8, + IpAddr::V4(_) => AF_INET, + IpAddr::V6(_) => AF_INET6, }, rtm_dst_len: match remote { IpAddr::V4(_) => 32, @@ -152,15 +152,11 @@ impl IfIndexMsg { debug_assert!(len <= size_of::()); len } - - const fn seq(&self) -> u32 { - self.nlmsg.nlmsg_seq - } } -impl From<&IfIndexMsg> for &[u8] { - fn from(value: &IfIndexMsg) -> Self { - unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } +impl From for &[u8] { + fn from(value: IfIndexMsg) -> Self { + unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } } } @@ -198,7 +194,7 @@ fn read_msg_with_seq(fd: &mut RouteSocket, seq: u32, kind: u16) -> Result<(nlmsg continue; } - if hdr.nlmsg_type == NLMSG_ERROR_U16 { + if hdr.nlmsg_type == NLMSG_ERROR { // Extract the error code and return it. let err = parse_c_int(msg)?; if err != 0 { @@ -260,8 +256,8 @@ impl<'a> Iterator for RtAttrs<'a> { fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { // Send RTM_GETROUTE message to get the interface index associated with the destination. - let msg = &IfIndexMsg::new(remote, 1); - let msg_seq = msg.seq(); + let msg_seq = RouteSocket::new_seq(); + let msg = IfIndexMsg::new(remote, msg_seq); fd.write_all(msg.into())?; // Receive RTM_GETROUTE response. @@ -294,12 +290,12 @@ impl IfInfoMsg { nlmsg: nlmsghdr { nlmsg_len, nlmsg_type: RTM_GETLINK, - nlmsg_flags: NLM_F_REQUEST_U16 | NLM_F_ACK_U16, + nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK, nlmsg_seq, ..Default::default() }, ifim: ifinfomsg { - ifi_family: AF_UNSPEC_U8, + ifi_family: AF_UNSPEC, ifi_type: ARPHRD_NONE, ifi_index: if_index, ..Default::default() @@ -310,23 +306,19 @@ impl IfInfoMsg { const fn len(&self) -> usize { self.nlmsg.nlmsg_len as usize } - - const fn seq(&self) -> u32 { - self.nlmsg.nlmsg_seq - } } -impl From<&IfInfoMsg> for &[u8] { - fn from(value: &IfInfoMsg) -> Self { +impl From for &[u8] { + fn from(value: IfInfoMsg) -> Self { debug_assert!(value.len() >= size_of::()); - unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } + unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } } } fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize)> { // Send RTM_GETLINK message to get interface information for the given interface index. - let msg = &IfInfoMsg::new(if_index, 2); - let msg_seq = msg.seq(); + let msg_seq = RouteSocket::new_seq(); + let msg = IfInfoMsg::new(if_index, msg_seq); fd.write_all(msg.into())?; // Receive RTM_GETLINK response. diff --git a/src/routesocket.rs b/src/routesocket.rs index 6708f4f9..ab992419 100644 --- a/src/routesocket.rs +++ b/src/routesocket.rs @@ -8,12 +8,25 @@ use std::{ io::{Error, Read, Result, Write}, num::TryFromIntError, os::fd::{AsRawFd, FromRawFd, OwnedFd}, + sync::atomic::Ordering, }; use libc::{fsync, read, socket, write, SOCK_RAW}; use crate::unlikely_err; +#[cfg(target_os = "linux")] +type AtomicRouteSocketSeq = std::sync::atomic::AtomicU32; +#[cfg(target_os = "linux")] +type RouteSocketSeq = u32; + +#[cfg(not(target_os = "linux"))] +type AtomicRouteSocketSeq = std::sync::atomic::AtomicI32; +#[cfg(not(target_os = "linux"))] +type RouteSocketSeq = i32; + +static SEQ: AtomicRouteSocketSeq = AtomicRouteSocketSeq::new(0); + pub struct RouteSocket(OwnedFd); impl RouteSocket { @@ -24,6 +37,10 @@ impl RouteSocket { } Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) } + + pub fn new_seq() -> RouteSocketSeq { + SEQ.fetch_add(1, Ordering::Relaxed) + } } impl AsRawFd for RouteSocket { diff --git a/src/windows.rs b/src/windows.rs index 6edeee51..1c76f26a 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -5,7 +5,7 @@ // except according to those terms. use std::{ - ffi::{c_void, CStr}, + ffi::CStr, io::{Error, ErrorKind, Result}, net::IpAddr, ptr, slice, @@ -33,6 +33,12 @@ use bindings::{ struct MibTablePtr(*mut MIB_IPINTERFACE_TABLE); +impl MibTablePtr { + fn mut_ptr_ptr(&mut self) -> *mut *mut MIB_IPINTERFACE_TABLE { + ptr::from_mut(&mut self.0) + } +} + impl Default for MibTablePtr { fn default() -> Self { Self(ptr::null_mut()) @@ -43,7 +49,7 @@ impl Drop for MibTablePtr { fn drop(&mut self) { if !self.0.is_null() { // Free the memory allocated by GetIpInterfaceTable. - unsafe { FreeMibTable(self.0 as *const c_void) }; + unsafe { FreeMibTable(self.0.cast()) }; } } } @@ -101,7 +107,7 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { // Get a list of all interfaces with associated metadata. let mut if_table = MibTablePtr::default(); // GetIpInterfaceTable allocates memory, which MibTablePtr::drop will free. - if unsafe { GetIpInterfaceTable(AF_UNSPEC, ptr::from_mut(&mut if_table.0)) } != NO_ERROR { + if unsafe { GetIpInterfaceTable(AF_UNSPEC, if_table.mut_ptr_ptr()) } != NO_ERROR { return Err(Error::last_os_error()); } // Make a slice From 766a0751061f30cdb775b6a77f1117509b16f672 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 21 Nov 2024 14:52:47 +0200 Subject: [PATCH 126/128] Fix --- src/bsd.rs | 8 ++++---- src/linux.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index dee79e97..cf039512 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -261,10 +261,10 @@ impl RouteMessage { } } -impl From for &[u8] { - fn from(value: RouteMessage) -> Self { +impl From<&RouteMessage> for &[u8] { + fn from(value: &RouteMessage) -> Self { debug_assert!(value.len() >= size_of::()); - unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } + unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } } } @@ -284,7 +284,7 @@ fn if_index(remote: IpAddr) -> Result { let query = RouteMessage::new(remote, query_seq); let query_version = query.version(); let query_type = query.kind(); - fd.write_all(query.into())?; + fd.write_all((&query).into())?; // Read route messages. let pid = unsafe { getpid() }; diff --git a/src/linux.rs b/src/linux.rs index 676478b6..1d290a74 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -154,9 +154,9 @@ impl IfIndexMsg { } } -impl From for &[u8] { - fn from(value: IfIndexMsg) -> Self { - unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } +impl From<&IfIndexMsg> for &[u8] { + fn from(value: &IfIndexMsg) -> Self { + unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } } } @@ -258,7 +258,7 @@ fn if_index(remote: IpAddr, fd: &mut RouteSocket) -> Result { // Send RTM_GETROUTE message to get the interface index associated with the destination. let msg_seq = RouteSocket::new_seq(); let msg = IfIndexMsg::new(remote, msg_seq); - fd.write_all(msg.into())?; + fd.write_all((&msg).into())?; // Receive RTM_GETROUTE response. let (_hdr, mut buf) = read_msg_with_seq(fd, msg_seq, RTM_NEWROUTE)?; @@ -308,10 +308,10 @@ impl IfInfoMsg { } } -impl From for &[u8] { - fn from(value: IfInfoMsg) -> Self { +impl From<&IfInfoMsg> for &[u8] { + fn from(value: &IfInfoMsg) -> Self { debug_assert!(value.len() >= size_of::()); - unsafe { slice::from_raw_parts(ptr::from_ref(&value).cast(), value.len()) } + unsafe { slice::from_raw_parts(ptr::from_ref(value).cast(), value.len()) } } } @@ -319,7 +319,7 @@ fn if_name_mtu(if_index: i32, fd: &mut RouteSocket) -> Result<(String, usize)> { // Send RTM_GETLINK message to get interface information for the given interface index. let msg_seq = RouteSocket::new_seq(); let msg = IfInfoMsg::new(if_index, msg_seq); - fd.write_all(msg.into())?; + fd.write_all((&msg).into())?; // Receive RTM_GETLINK response. let (_hdr, mut buf) = read_msg_with_seq(fd, msg_seq, RTM_NEWLINK)?; From bc104e9898964f7b0ade1f92514259318288b2bb Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 21 Nov 2024 15:15:36 +0200 Subject: [PATCH 127/128] Macro --- src/bsd.rs | 24 +++++------------------- src/lib.rs | 8 ++++++++ src/linux.rs | 29 ++++++----------------------- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/src/bsd.rs b/src/bsd.rs index cf039512..ebe48997 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -47,25 +47,11 @@ const ALIGN: usize = size_of::(); use crate::{aligned_by, default_err}; -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET: u8 = libc::AF_INET as u8; -const_assert_eq!(AF_INET as i32, libc::AF_INET); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET6: u8 = libc::AF_INET6 as u8; -const_assert_eq!(AF_INET6 as i32, libc::AF_INET6); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_LINK: u8 = libc::AF_LINK as u8; -const_assert_eq!(AF_LINK as i32, libc::AF_LINK); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const RTM_VERSION: u8 = libc::RTM_VERSION as u8; -const_assert_eq!(RTM_VERSION as i32, libc::RTM_VERSION); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const RTM_GET: u8 = libc::RTM_GET as u8; -const_assert_eq!(RTM_GET as i32, libc::RTM_GET); +asserted_const_with_type!(AF_INET, u8, libc::AF_INET, i32); +asserted_const_with_type!(AF_INET6, u8, libc::AF_INET6, i32); +asserted_const_with_type!(AF_LINK, u8, libc::AF_LINK, i32); +asserted_const_with_type!(RTM_VERSION, u8, libc::RTM_VERSION, i32); +asserted_const_with_type!(RTM_GET, u8, libc::RTM_GET, i32); const_assert!(size_of::() + ALIGN <= u8::MAX as usize); const_assert!(size_of::() + ALIGN <= u8::MAX as usize); diff --git a/src/lib.rs b/src/lib.rs index d9e9b644..7571dba6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,14 @@ use std::{ net::IpAddr, }; +macro_rules! asserted_const_with_type { + ($name:ident, $t1:ty, $e:expr, $t2:ty) => { + #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. + const $name: $t1 = $e as $t1; + const_assert_eq!($name as $t2, $e); + }; +} + #[cfg(any(apple, bsd))] mod bsd; diff --git a/src/linux.rs b/src/linux.rs index 1d290a74..bb5e4b09 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -33,29 +33,12 @@ mod bindings { use bindings::{ifinfomsg, nlmsghdr, rtattr, rtmsg}; -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET: u8 = libc::AF_INET as u8; -const_assert_eq!(AF_INET as i32, libc::AF_INET); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_INET6: u8 = libc::AF_INET6 as u8; -const_assert_eq!(AF_INET6 as i32, libc::AF_INET6); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const AF_UNSPEC: u8 = libc::AF_UNSPEC as u8; -const_assert_eq!(AF_UNSPEC as i32, libc::AF_UNSPEC); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const NLM_F_REQUEST: u16 = libc::NLM_F_REQUEST as u16; -const_assert_eq!(NLM_F_REQUEST as c_int, libc::NLM_F_REQUEST); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const NLM_F_ACK: u16 = libc::NLM_F_ACK as u16; -const_assert_eq!(NLM_F_ACK as c_int, libc::NLM_F_ACK); - -#[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`. -const NLMSG_ERROR: u16 = libc::NLMSG_ERROR as u16; -const_assert_eq!(NLMSG_ERROR as c_int, libc::NLMSG_ERROR); +asserted_const_with_type!(AF_INET, u8, libc::AF_INET, i32); +asserted_const_with_type!(AF_INET6, u8, libc::AF_INET6, i32); +asserted_const_with_type!(AF_UNSPEC, u8, libc::AF_UNSPEC, i32); +asserted_const_with_type!(NLM_F_REQUEST, u16, libc::NLM_F_REQUEST, c_int); +asserted_const_with_type!(NLM_F_ACK, u16, libc::NLM_F_ACK, c_int); +asserted_const_with_type!(NLMSG_ERROR, u16, libc::NLMSG_ERROR, c_int); const_assert!(size_of::() <= u8::MAX as usize); const_assert!(size_of::() <= u8::MAX as usize); From 0f1c742c81f43bb72ecac082973bbc2605784e05 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Thu, 21 Nov 2024 15:21:34 +0200 Subject: [PATCH 128/128] Not Windows --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 7571dba6..5f856955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,7 @@ use std::{ net::IpAddr, }; +#[cfg(not(target_os = "windows"))] macro_rules! asserted_const_with_type { ($name:ident, $t1:ty, $e:expr, $t2:ty) => { #[allow(clippy::cast_possible_truncation)] // Guarded by the following `const_assert_eq!`.