Skip to content

Commit

Permalink
Merge pull request #11 from mozilla/feat-iface-id
Browse files Browse the repository at this point in the history
feat: Also return the interface name
  • Loading branch information
larseggert authored Sep 4, 2024
2 parents 655a937 + 6084033 commit 3ee7d47
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ homepage = "https://github.com/mozilla/mtu/"
repository = "https://github.com/mozilla/mtu/"
authors = ["The Mozilla Necko Team <[email protected]>"]
readme = "README.md"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
license = "MIT OR Apache-2.0"
# Don't increase beyond what Firefox is currently using:
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ A crate to return the maximum transmission unit (MTU) of the local network inter
This crate exports a single function

```rust
pub fn interface_mtu(remote: &SocketAddr) -> Result<usize, Error>
pub fn interface_and_mtu(remote: &SocketAddr) -> Result<(String, usize), Error>
```

that returns the MTU of the local network interface towards the `remote` destination, or an `Error` when the MTU could not be determined. It supports both IPv4 and IPv6.
that returns the interface name and MTU of the local network interface used for transmission towards the `remote` destination, or an `Error` when the MTU could not be determined. It supports both IPv4 and IPv6.

## Supported Platforms

Expand All @@ -22,6 +22,8 @@ that returns the MTU of the local network interface towards the `remote` destina

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.

## Contributing

We're happy to receive PRs that improve this crate. Please take a look at our [community guidelines](CODE_OF_CONDUCT.md) beforehand.
76 changes: 52 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,34 @@ use log::trace;
mod win_bindings;

/// Prepare a default error result.
fn default_result<T>() -> Result<T, Error> {
fn default_result<T>() -> Result<(String, T), Error> {
Err(Error::new(
ErrorKind::NotFound,
"Local interface MTU not found",
))
}

/// Return the maximum transmission unit (MTU) of the local network interface towards the
/// destination [`SocketAddr`] given in `remote`.
/// Return the interface name and the maximum transmission unit (MTU) of the local network
/// interface towards the destination [`SocketAddr`] given in `remote`.
///
/// 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.
///
/// # Examples
///
/// ```
/// let saddr = "127.0.0.1:443".parse().unwrap();
/// let mtu = mtu::interface_mtu(&saddr).unwrap();
/// println!("MTU towards {saddr:?} is {mtu}");
/// let (name, mtu) = mtu::interface_and_mtu(&saddr).unwrap();
/// println!("MTU towards {saddr:?} is {mtu} on {name}");
/// ```
///
/// # Errors
///
/// This function returns an error if the local interface MTU cannot be determined.
pub fn interface_mtu(remote: &SocketAddr) -> Result<usize, Error> {
pub fn interface_and_mtu(remote: &SocketAddr) -> Result<(String, usize), Error> {
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
#[allow(unused_assignments)] // Yes, res is reassigned in the platform-specific code.
let mut res = default_result();
Expand All @@ -64,27 +66,21 @@ pub fn interface_mtu(remote: &SocketAddr) -> Result<usize, Error> {

#[cfg(any(target_os = "macos", target_os = "linux"))]
{
res = interface_mtu_linux_macos(&socket);
res = interface_and_mtu_linux_macos(&socket);
}

#[cfg(target_os = "windows")]
{
res = interface_mtu_windows(&socket);
res = interface_and_mtu_windows(&socket);
}
}

trace!("MTU towards {remote:?} is {res:?}");
res
}

#[doc(hidden)]
#[deprecated(since = "0.1.2", note = "Use `interface_mtu()` instead")]
pub fn get_interface_mtu(remote: &SocketAddr) -> Result<usize, Error> {
interface_mtu(remote)
}

#[cfg(any(target_os = "macos", target_os = "linux"))]
fn interface_mtu_linux_macos(socket: &UdpSocket) -> Result<usize, Error> {
fn interface_and_mtu_linux_macos(socket: &UdpSocket) -> Result<(String, usize), Error> {
use std::ffi::{c_int, CStr};
#[cfg(target_os = "linux")]
use std::{ffi::c_char, mem, os::fd::AsRawFd};
Expand Down Expand Up @@ -158,7 +154,9 @@ fn interface_mtu_linux_macos(socket: &UdpSocket) -> Result<usize, Error> {
&& name == iface
{
let data = unsafe { &*(ifa.ifa_data as *const if_data) };
res = usize::try_from(data.ifi_mtu).or(res);
if let Ok(mtu) = usize::try_from(data.ifi_mtu) {
res = Ok((iface.to_string(), mtu));
}
break;
}
}
Expand All @@ -175,8 +173,8 @@ fn interface_mtu_linux_macos(socket: &UdpSocket) -> Result<usize, Error> {
});
if unsafe { ioctl(socket.as_raw_fd(), libc::SIOCGIFMTU, &ifr) } != 0 {
res = Err(Error::last_os_error());
} else {
res = unsafe { usize::try_from(ifr.ifr_ifru.ifru_mtu).or(res) };
} else if let Ok(mtu) = usize::try_from(unsafe { ifr.ifr_ifru.ifru_mtu }) {
res = Ok((iface.to_string(), mtu));
}
}
}
Expand All @@ -186,12 +184,13 @@ fn interface_mtu_linux_macos(socket: &UdpSocket) -> Result<usize, Error> {
}

#[cfg(target_os = "windows")]
fn interface_mtu_windows(socket: &UdpSocket) -> Result<usize, Error> {
fn interface_and_mtu_windows(socket: &UdpSocket) -> Result<(String, usize), Error> {
use core::str;
use std::{ffi::c_void, slice};

use win_bindings::{
FreeMibTable, GetIpInterfaceTable, GetUnicastIpAddressTable, AF_INET, AF_INET6, AF_UNSPEC,
MIB_IPINTERFACE_ROW, MIB_IPINTERFACE_TABLE, MIB_UNICASTIPADDRESS_ROW,
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,
};

Expand Down Expand Up @@ -243,7 +242,16 @@ fn interface_mtu_windows(socket: &UdpSocket) -> Result<usize, Error> {
// For the matching address, find local interface and its MTU.
for iface in ifaces {
if iface.InterfaceIndex == addr.InterfaceIndex {
res = iface.NlMtu.try_into().or(res);
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) = str::from_utf8(&name) {
res = Ok((name.to_string(), mtu));
}
} else {
res = Err(Error::last_os_error());
}
}
break 'addr_loop;
}
}
Expand All @@ -256,6 +264,18 @@ fn interface_mtu_windows(socket: &UdpSocket) -> Result<usize, Error> {
res
}

#[doc(hidden)]
#[deprecated(since = "0.1.2", note = "Use `interface_and_mtu()` instead")]
pub fn interface_mtu(remote: &SocketAddr) -> Result<usize, Error> {
interface_and_mtu(remote).map(|(_, mtu)| mtu)
}

#[doc(hidden)]
#[deprecated(since = "0.1.2", note = "Use `interface_and_mtu()` instead")]
pub fn get_interface_mtu(remote: &SocketAddr) -> Result<usize, Error> {
interface_and_mtu(remote).map(|(_, mtu)| mtu)
}

#[cfg(test)]
mod test {
use std::net::ToSocketAddrs;
Expand All @@ -268,8 +288,8 @@ mod test {
.unwrap()
.find(|a| a.is_ipv4() == ipv4);
if let Some(addr) = addr {
match super::interface_mtu(&addr) {
Ok(mtu) => assert_eq!(mtu, expected),
match super::interface_and_mtu(&addr) {
Ok((_, mtu)) => assert_eq!(mtu, expected),
Err(e) => {
// Some GitHub runners don't have IPv6. Just warn if we can't get the MTU.
assert!(addr.is_ipv6());
Expand Down Expand Up @@ -312,4 +332,12 @@ mod test {
fn default_interface_mtu_v6() {
check_mtu("ietf.org:443", false, 1500);
}

#[test]
#[allow(deprecated)] // Purpose of the test is to cover deprecated functions.
fn deprecated_functions() {
let addr = "localhost:443".to_socket_addrs().unwrap().next().unwrap();
assert!(super::interface_mtu(&addr).is_ok());
assert!(super::get_interface_mtu(&addr).is_ok());
}
}
8 changes: 8 additions & 0 deletions src/win_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ pub unsafe fn GetUnicastIpAddressTable(
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],
) -> 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);
Expand Down
1 change: 1 addition & 0 deletions tests/win_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn codegen_windows_bindings() {
"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",
Expand Down

0 comments on commit 3ee7d47

Please sign in to comment.