From f24c12d887ab719d58f978a8ac4f70e42dbaa5b5 Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Sun, 9 Jun 2024 22:30:52 +0200 Subject: [PATCH 1/5] Add ways to set Path Discovery MTU for Linux on a Socket. This contribution use setsocketops to configure Dont Fragment (DF) bit and OS behavior related to PMTU. It introduce PathMtuDiscoveringMode enum to manage it rust-way and 4 functions respectively for ipv4/v6 and set/get. Omit and Interface exist but are not widely documented. --- src/sys/unix.rs | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 51ef4a5d..2438a9e6 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -1391,6 +1391,39 @@ pub(crate) const fn to_mreqn( } } +/// Possibles state for Path Maximum Transmission Unit (PMTU) Discovering of packets received on a socket. +/// +/// It set in the IP packet Header the flag DF also known as "do not fragment". +/// +/// For UDP it's particullary important to note that during Path MTU discovery is done by the OS, +/// incomming datagram may be dropped. +/// +/// ## Portability +/// +/// It's Linux only way of managing the Path MTU. +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +#[repr(C)] +#[derive(Debug)] +pub enum PathMtuDiscoveringMode { + /// Path MTU discovery is not done. + Dont = 0, + /// Path MTU discovery is done according to route setting. + Want = 1, + /// Path MTU discovery + Do = 2, + /// Set DF bit but ignore Path MTDU + Probe = 3, + /// Use interface MTU. + /// It ignore destination PMTU and does not set DF flag. + /// Incomming ICMP frag_needed notifications on this socket will be ignored in + /// order to prevent accepting spoofed ones. + Interface = 4, + /// Almost like [MtuDiscoveringMode::Interface] but authorize fragmented packet + /// if they do not saturate the interface MTU. + Omit = 5, +} + /// Unix only API. impl crate::Socket { /// Accept a new incoming connection from this listener. @@ -3063,6 +3096,94 @@ impl crate::Socket { ) } } + + /// Get the value of the `IP_MTU_DISCOVER` of this socket for IPv4. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] + pub fn mtu_discover(&self) -> io::Result { + unsafe { + use PathMtuDiscoveringMode as MTU; + let mtu_discovering = + getsockopt::(self.as_raw(), libc::SOL_IP, libc::IP_MTU_DISCOVER)?; + + Ok(match mtu_discovering { + libc::IP_PMTUDISC_DONT => MTU::Dont, + libc::IP_PMTUDISC_WANT => MTU::Want, + libc::IP_PMTUDISC_DO => MTU::Do, + libc::IP_PMTUDISC_PROBE => MTU::Probe, + #[cfg(all(feature = "all", target_os = "linux"))] + libc::IP_PMTUDISC_INTERFACE => MTU::Interface, + #[cfg(all(feature = "all", target_os = "linux"))] + libc::IP_PMTUDISC_OMIT => MTU::Omit, + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "IP_PMTUDISC value not yet implemented by socket2", + )); + } + }) + } + } + + /// Set value for the `IP_MTU_DISCOVER` of this socket for IPv4. + /// Used to configure Dont fragment DF bit and OS behaviour related to Path MTU discovery. + /// See [PathMtuDiscoveringMode] for details. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] + pub fn set_mtu_discover(&self, mtu_discovery: PathMtuDiscoveringMode) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_IP, + libc::IP_MTU_DISCOVER, + mtu_discovery as c_int, + ) + } + } + + /// Get the value of the `IP_MTU_DISCOVER` of this socket for IPv6. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] + pub fn mtu_discover_ipv6(&self) -> io::Result { + unsafe { + use PathMtuDiscoveringMode as MTU; + let mtu_discovering = + getsockopt::(self.as_raw(), libc::SOL_IPV6, libc::IPV6_MTU_DISCOVER)?; + + Ok(match mtu_discovering { + libc::IPV6_PMTUDISC_DONT => MTU::Dont, + libc::IPV6_PMTUDISC_WANT => MTU::Want, + libc::IPV6_PMTUDISC_DO => MTU::Do, + libc::IPV6_PMTUDISC_PROBE => MTU::Probe, + #[cfg(all(feature = "all", target_os = "linux"))] + libc::IPV6_PMTUDISC_INTERFACE => MTU::Interface, + #[cfg(all(feature = "all", target_os = "linux"))] + libc::IPV6_PMTUDISC_OMIT => MTU::Omit, + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "IP_PMTUDISC value not yet implemented by socket2", + )); + } + }) + } + } + + /// Set value for the `IP_MTU_DISCOVER` of this socket for IPv6. + /// Used to configure Dont fragment DF bit and OS behaviour related to Path MTU discovery. + /// See [PathMtuDiscoveringMode] for details. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] + pub fn set_mtu_discover_ipv6(&self, mtu_discovery: PathMtuDiscoveringMode) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_IPV6, + libc::IPV6_MTU_DISCOVER, + mtu_discovery as c_int, + ) + } + } } /// See [`Socket::dccp_available_ccids`]. From 42861345c6e039bbda24bf32e29fabee989c2776 Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Wed, 19 Jun 2024 14:28:17 +0200 Subject: [PATCH 2/5] !fixup review: portability statement already elsewhere in doc Co-authored-by: Thomas de Zeeuw --- src/sys/unix.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 2438a9e6..4d4fdda0 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -1397,10 +1397,6 @@ pub(crate) const fn to_mreqn( /// /// For UDP it's particullary important to note that during Path MTU discovery is done by the OS, /// incomming datagram may be dropped. -/// -/// ## Portability -/// -/// It's Linux only way of managing the Path MTU. #[cfg(all(feature = "all", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] #[repr(C)] From 06ceefb5cd4d8eb1ffa2489e6e9009ac699639e0 Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Wed, 19 Jun 2024 15:28:00 +0200 Subject: [PATCH 3/5] !fixup Rework to avoid making a specific enum reuse Type(c_int) pattern. I reused the pattern used elsewhere to have handful short hands documented, while yet permiting futur constant not exposed in the lib. Also expose MtuDiscovering Mode types.. --- src/lib.rs | 3 + src/sys/unix.rs | 171 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 122 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3161d63..a06bb798 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,6 +196,9 @@ pub use sockref::SockRef; )))] pub use socket::InterfaceIndexOrAddress; +#[cfg(all(feature = "all", target_os = "linux"))] +pub use sys::{PathMtuDiscoveringModeV4, PathMtuDiscoveringModeV6}; + /// Specification of the communication domain for a socket. /// /// This is a newtype wrapper around an integer which provides a nicer API in diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 4d4fdda0..261b10ee 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -1391,35 +1391,134 @@ pub(crate) const fn to_mreqn( } } -/// Possibles state for Path Maximum Transmission Unit (PMTU) Discovering of packets received on a socket. +/// Possibles state for Path Maximum Transmission Unit (PMTU) Discovering of packets received on an Ipv4 socket. +/// +/// In the IP packet Header it set the flag DF also known as "do not fragment". +/// +/// For UDP it's particullary important to note that during Path MTU discovery is done by the OS, +/// incomming datagram may be dropped. +/// +/// This type is freely interconvertible with C's `int` type, however, if a raw +/// value needs to be provided. +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PathMtuDiscoveringModeV4(libc::c_int); + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl PathMtuDiscoveringModeV4 { + /// Path MTU discovery is not done. + pub const DONT: Self = Self(libc::IP_PMTUDISC_DONT); + /// Path MTU discovery is done according to route setting. + pub const WANT: Self = Self(libc::IP_PMTUDISC_WANT); + /// Path MTU discovery + pub const DO: Self = Self(libc::IP_PMTUDISC_DO); + /// Set DF bit but ignore Path MTDU + pub const PROBE: Self = Self(libc::IP_PMTUDISC_PROBE); + /// Use interface MTU. + /// It ignore destination PMTU and does not set DF flag. + /// Incomming ICMP frag_needed notifications on this socket will be ignored in + /// order to prevent accepting spoofed ones. + pub const INTERFACE: Self = Self(libc::IP_PMTUDISC_INTERFACE); + /// Almost like [MtuDiscoveringModeV4::Interface] but authorize fragmented packet + /// if they do not saturate the interface MTU. + pub const OMIT: Self = Self(libc::IP_PMTUDISC_OMIT); +} + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl From for PathMtuDiscoveringModeV4 { + fn from(t: c_int) -> PathMtuDiscoveringModeV4 { + Self(t) + } +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))] +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl From for c_int { + fn from(t: PathMtuDiscoveringModeV4) -> c_int { + t.0 + } +} + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl_debug!( + PathMtuDiscoveringModeV4, + libc::IP_PMTUDISC_DONT, + libc::IP_PMTUDISC_WANT, + libc::IP_PMTUDISC_DO, + libc::IP_PMTUDISC_PROBE, + libc::IP_PMTUDISC_INTERFACE, + libc::IP_PMTUDISC_OMIT +); + +/// Possibles state for Path Maximum Transmission Unit (PMTU) Discovering of packets received on an IPV6 socket. /// /// It set in the IP packet Header the flag DF also known as "do not fragment". /// /// For UDP it's particullary important to note that during Path MTU discovery is done by the OS, /// incomming datagram may be dropped. +/// +/// This type is freely interconvertible with C's `int` type, however, if a raw +/// value needs to be provided. #[cfg(all(feature = "all", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] -#[repr(C)] -#[derive(Debug)] -pub enum PathMtuDiscoveringMode { +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PathMtuDiscoveringModeV6(libc::c_int); + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl PathMtuDiscoveringModeV6 { /// Path MTU discovery is not done. - Dont = 0, + pub const DONT: Self = Self(libc::IPV6_PMTUDISC_DONT); /// Path MTU discovery is done according to route setting. - Want = 1, + pub const WANT: Self = Self(libc::IPV6_PMTUDISC_WANT); /// Path MTU discovery - Do = 2, + pub const DO: Self = Self(libc::IPV6_PMTUDISC_DO); /// Set DF bit but ignore Path MTDU - Probe = 3, + pub const PROBE: Self = Self(libc::IPV6_PMTUDISC_PROBE); /// Use interface MTU. /// It ignore destination PMTU and does not set DF flag. /// Incomming ICMP frag_needed notifications on this socket will be ignored in /// order to prevent accepting spoofed ones. - Interface = 4, - /// Almost like [MtuDiscoveringMode::Interface] but authorize fragmented packet + pub const INTERFACE: Self = Self(libc::IPV6_PMTUDISC_INTERFACE); + /// Almost like [MtuDiscoveringModeV6::Interface] but authorize fragmented packet /// if they do not saturate the interface MTU. - Omit = 5, + pub const OMIT: Self = Self(libc::IPV6_PMTUDISC_OMIT); +} + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl From for PathMtuDiscoveringModeV6 { + fn from(t: c_int) -> PathMtuDiscoveringModeV6 { + Self(t) + } } +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl From for c_int { + fn from(t: PathMtuDiscoveringModeV6) -> c_int { + t.0 + } +} + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] +impl_debug!( + PathMtuDiscoveringModeV6, + libc::IPV6_PMTUDISC_DONT, + libc::IPV6_PMTUDISC_WANT, + libc::IPV6_PMTUDISC_DO, + libc::IPV6_PMTUDISC_PROBE, + libc::IPV6_PMTUDISC_INTERFACE, + libc::IPV6_PMTUDISC_OMIT +); + /// Unix only API. impl crate::Socket { /// Accept a new incoming connection from this listener. @@ -3096,43 +3195,27 @@ impl crate::Socket { /// Get the value of the `IP_MTU_DISCOVER` of this socket for IPv4. #[cfg(all(feature = "all", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] - pub fn mtu_discover(&self) -> io::Result { + pub fn mtu_discover(&self) -> io::Result { unsafe { - use PathMtuDiscoveringMode as MTU; let mtu_discovering = getsockopt::(self.as_raw(), libc::SOL_IP, libc::IP_MTU_DISCOVER)?; - Ok(match mtu_discovering { - libc::IP_PMTUDISC_DONT => MTU::Dont, - libc::IP_PMTUDISC_WANT => MTU::Want, - libc::IP_PMTUDISC_DO => MTU::Do, - libc::IP_PMTUDISC_PROBE => MTU::Probe, - #[cfg(all(feature = "all", target_os = "linux"))] - libc::IP_PMTUDISC_INTERFACE => MTU::Interface, - #[cfg(all(feature = "all", target_os = "linux"))] - libc::IP_PMTUDISC_OMIT => MTU::Omit, - _ => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "IP_PMTUDISC value not yet implemented by socket2", - )); - } - }) + Ok(PathMtuDiscoveringModeV4(mtu_discovering)) } } /// Set value for the `IP_MTU_DISCOVER` of this socket for IPv4. /// Used to configure Dont fragment DF bit and OS behaviour related to Path MTU discovery. - /// See [PathMtuDiscoveringMode] for details. + /// See [PathMtuDiscoveringModeV4] for details. #[cfg(all(feature = "all", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] - pub fn set_mtu_discover(&self, mtu_discovery: PathMtuDiscoveringMode) -> io::Result<()> { + pub fn set_mtu_discover(&self, mtu_discovery: PathMtuDiscoveringModeV4) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_IP, libc::IP_MTU_DISCOVER, - mtu_discovery as c_int, + mtu_discovery.0, ) } } @@ -3140,28 +3223,12 @@ impl crate::Socket { /// Get the value of the `IP_MTU_DISCOVER` of this socket for IPv6. #[cfg(all(feature = "all", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] - pub fn mtu_discover_ipv6(&self) -> io::Result { + pub fn mtu_discover_ipv6(&self) -> io::Result { unsafe { - use PathMtuDiscoveringMode as MTU; let mtu_discovering = getsockopt::(self.as_raw(), libc::SOL_IPV6, libc::IPV6_MTU_DISCOVER)?; - Ok(match mtu_discovering { - libc::IPV6_PMTUDISC_DONT => MTU::Dont, - libc::IPV6_PMTUDISC_WANT => MTU::Want, - libc::IPV6_PMTUDISC_DO => MTU::Do, - libc::IPV6_PMTUDISC_PROBE => MTU::Probe, - #[cfg(all(feature = "all", target_os = "linux"))] - libc::IPV6_PMTUDISC_INTERFACE => MTU::Interface, - #[cfg(all(feature = "all", target_os = "linux"))] - libc::IPV6_PMTUDISC_OMIT => MTU::Omit, - _ => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "IP_PMTUDISC value not yet implemented by socket2", - )); - } - }) + Ok(PathMtuDiscoveringModeV6(mtu_discovering)) } } @@ -3170,13 +3237,13 @@ impl crate::Socket { /// See [PathMtuDiscoveringMode] for details. #[cfg(all(feature = "all", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "all", target_os = "linux"))))] - pub fn set_mtu_discover_ipv6(&self, mtu_discovery: PathMtuDiscoveringMode) -> io::Result<()> { + pub fn set_mtu_discover_ipv6(&self, mtu_discovery: PathMtuDiscoveringModeV6) -> io::Result<()> { unsafe { setsockopt( self.as_raw(), libc::SOL_IPV6, libc::IPV6_MTU_DISCOVER, - mtu_discovery as c_int, + mtu_discovery.0, ) } } From e95a481e0dcde4d6f1cd5c623d0a4b5fc78c4eae Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Wed, 19 Jun 2024 16:13:16 +0200 Subject: [PATCH 4/5] Add a test for MTU discovering mode set/get. --- tests/socket.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/socket.rs b/tests/socket.rs index 89b79f5f..20970186 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1696,3 +1696,52 @@ fn set_passcred() { socket.set_passcred(true).unwrap(); assert!(socket.passcred().unwrap()); } + +#[cfg(all(feature = "all", target_os = "linux"))] +#[test] +fn pmtu_discovery_v4() { + use socket2::PathMtuDiscoveringModeV4; + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); + // The current mtu discovery is system wide so let's assume it's not an err. + assert!(socket.mtu_discover().is_ok()); + + socket + .set_mtu_discover(PathMtuDiscoveringModeV4::DO) + .unwrap(); + assert_eq!(socket.mtu_discover().unwrap(), PathMtuDiscoveringModeV4::DO); + + let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); + assert!(socket.mtu_discover().is_ok()); + + socket + .set_mtu_discover(PathMtuDiscoveringModeV4::DO) + .unwrap(); + assert_eq!(socket.mtu_discover().unwrap(), PathMtuDiscoveringModeV4::DO); +} + +#[cfg(all(feature = "all", target_os = "linux"))] +#[test] +fn pmtu_discovery_v6() { + use socket2::PathMtuDiscoveringModeV6; + let socket = Socket::new(Domain::IPV6, Type::DGRAM, None).unwrap(); + assert!(socket.mtu_discover_ipv6().is_ok()); + + socket + .set_mtu_discover_ipv6(PathMtuDiscoveringModeV6::DO) + .unwrap(); + assert_eq!( + socket.mtu_discover_ipv6().unwrap(), + PathMtuDiscoveringModeV6::DO + ); + + let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); + assert!(socket.mtu_discover_ipv6().is_ok()); + + socket + .set_mtu_discover_ipv6(PathMtuDiscoveringModeV6::DO) + .unwrap(); + assert_eq!( + socket.mtu_discover_ipv6().unwrap(), + PathMtuDiscoveringModeV6::DO + ); +} From d35c931742a39959e252032abc197365b3efe94a Mon Sep 17 00:00:00 2001 From: Axel Viala Date: Wed, 19 Jun 2024 18:08:50 +0200 Subject: [PATCH 5/5] fix documentation problem seen in CI. --- src/sys/unix.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 261b10ee..d95d8801 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -1421,7 +1421,7 @@ impl PathMtuDiscoveringModeV4 { /// Incomming ICMP frag_needed notifications on this socket will be ignored in /// order to prevent accepting spoofed ones. pub const INTERFACE: Self = Self(libc::IP_PMTUDISC_INTERFACE); - /// Almost like [MtuDiscoveringModeV4::Interface] but authorize fragmented packet + /// Almost like `MtuDiscoveringModeV4::Interface` but authorize fragmented packet /// if they do not saturate the interface MTU. pub const OMIT: Self = Self(libc::IP_PMTUDISC_OMIT); } @@ -1486,7 +1486,7 @@ impl PathMtuDiscoveringModeV6 { /// Incomming ICMP frag_needed notifications on this socket will be ignored in /// order to prevent accepting spoofed ones. pub const INTERFACE: Self = Self(libc::IPV6_PMTUDISC_INTERFACE); - /// Almost like [MtuDiscoveringModeV6::Interface] but authorize fragmented packet + /// Almost like `MtuDiscoveringModeV6::Interface` but authorize fragmented packet /// if they do not saturate the interface MTU. pub const OMIT: Self = Self(libc::IPV6_PMTUDISC_OMIT); }