From ef35cff1114697f07eec96b5923206fd6e635dda Mon Sep 17 00:00:00 2001 From: Trey Aspelund Date: Wed, 30 Sep 2026 13:01:29 -0600 Subject: [PATCH] Add set_min_ttl_v4 and set_min_hopcount_v6 Adds linux/illumos Socket methods for setting IP_MINTTL and IPV6_MINHOPCOUNT along with associated unit tests. Signed-off-by: Trey Aspelund --- src/socket.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ src/sys/unix.rs | 2 ++ tests/socket.rs | 30 +++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/src/socket.rs b/src/socket.rs index da01a77a..929a1190 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1631,6 +1631,28 @@ impl Socket { unsafe { setsockopt(self.as_raw(), sys::IPPROTO_IP, sys::IP_TTL, ttl as c_int) } } + /// Get the value of the `IP_MINTTL` option for this socket. + /// + /// For more information about this option, see [`set_min_ttl_v4`]. + /// + /// [`set_min_ttl_v4`]: Socket::set_min_ttl_v4 + #[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] + pub fn min_ttl_v4(&self) -> io::Result { + unsafe { + getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MINTTL) + .map(|ttl| ttl as u32) + } + } + + /// Set the value of the `IP_MINTTL` option for this socket. + /// + /// If enabled, received IPv4 packets are discarded if the value of the + /// time-to-live field of the IPv4 header is lower than the option value. + #[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] + pub fn set_min_ttl_v4(&self, ttl: u32) -> io::Result<()> { + unsafe { setsockopt(self.as_raw(), sys::IPPROTO_IP, sys::IP_MINTTL, ttl as c_int) } + } + /// Set the value of the `IP_TOS` option for this socket. /// /// This value sets the type-of-service field that is used in every packet @@ -2057,6 +2079,35 @@ impl Socket { } } + /// Get the value of the `IPV6_MINHOPCOUNT` option for this socket. + /// + /// For more information about this option, see [`set_min_hopcount_v6`]. + /// + /// [`set_min_hopcount_v6`]: Socket::set_min_hopcount_v6 + #[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] + pub fn min_hopcount_v6(&self) -> io::Result { + unsafe { + getsockopt::(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_MINHOPCOUNT) + .map(|hops| hops as u32) + } + } + + /// Set the value of the `IPV6_MINHOPCOUNT` option for this socket. + /// + /// If enabled, received IPv6 packets are discarded if the value of the + /// hop limit field of the IPv6 header is lower than the option value. + #[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] + pub fn set_min_hopcount_v6(&self, hops: u32) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + sys::IPPROTO_IPV6, + sys::IPV6_MINHOPCOUNT, + hops as c_int, + ) + } + } + /// Get the value of the `IPV6_V6ONLY` option for this socket. /// /// For more information about this option, see [`set_only_v6`]. diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 9cb0462e..057a70cf 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -305,6 +305,8 @@ pub(crate) use libc::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP}; pub(crate) use libc::{ IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP, IPV6_LEAVE_GROUP as IPV6_DROP_MEMBERSHIP, }; +#[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] +pub(crate) use libc::{IPV6_MINHOPCOUNT, IP_MINTTL}; #[cfg(not(target_os = "wasi"))] pub(crate) use libc::{IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IP_MULTICAST_IF, MSG_OOB}; #[cfg(all( diff --git a/tests/socket.rs b/tests/socket.rs index 886a8eab..9459dc64 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1519,6 +1519,9 @@ test!(IPv6 freebind_v6, set_freebind_v6(true)); test!(IPv4 ttl_v4, set_ttl_v4(40)); +#[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] +test!(IPv4 min_ttl_v4, set_min_ttl_v4(50)); + #[cfg(not(any( target_os = "fuchsia", target_os = "redox", @@ -1552,6 +1555,9 @@ test!(IPv4 broadcast, set_broadcast(true)); #[cfg(not(target_os = "vita"))] test!(IPv6 unicast_hops_v6, set_unicast_hops_v6(20)); +#[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] +test!(IPv6 min_hopcount_v6, set_min_hopcount_v6(30)); + #[cfg(not(any( windows, target_os = "dragonfly", @@ -1970,3 +1976,27 @@ fn set_busy_poll() { assert!(socket.busy_poll().unwrap() == i); } } + +#[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] +#[test] +fn min_ttl_v4_preserves_outbound_ttl() { + let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); + + socket.set_ttl_v4(61).unwrap(); + socket.set_min_ttl_v4(197).unwrap(); + + assert_eq!(socket.min_ttl_v4().unwrap(), 197); + assert_eq!(socket.ttl_v4().unwrap(), 61); +} + +#[cfg(all(feature = "all", any(target_os = "linux", target_os = "illumos")))] +#[test] +fn min_hopcount_v6_preserves_outbound_hops() { + let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); + + socket.set_unicast_hops_v6(61).unwrap(); + socket.set_min_hopcount_v6(197).unwrap(); + + assert_eq!(socket.min_hopcount_v6().unwrap(), 197); + assert_eq!(socket.unicast_hops_v6().unwrap(), 61); +}