From e2de1474f1683c8bf83db39d8539e1c4d773c029 Mon Sep 17 00:00:00 2001 From: Skye Soss Date: Tue, 10 Feb 2026 13:00:12 -0600 Subject: [PATCH] Add `ipv6_checksum` socket options Add `set_ipv6_checksum` and `ipv6_checksum` for the `IPV6_CHECKSUM` socket option specified in RFC 3542 Section 3.1, which has the kernel compute and verify a checksum at a given offset in the payload of raw IPv6 sockets. The option value is exposed as `Option`, with `None` corresponding to `-1` which disables checksumming. --- src/backend/libc/net/sockopt.rs | 30 ++++++++++++++++ src/backend/linux_raw/c.rs | 2 +- src/backend/linux_raw/net/sockopt.rs | 14 ++++++++ src/net/sockopt.rs | 52 ++++++++++++++++++++++++++++ tests/net/sockopt.rs | 29 ++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/backend/libc/net/sockopt.rs b/src/backend/libc/net/sockopt.rs index 286adde62..3a75d8ba4 100644 --- a/src/backend/libc/net/sockopt.rs +++ b/src/backend/libc/net/sockopt.rs @@ -875,6 +875,36 @@ pub(crate) fn ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool) } +#[cfg(any( + apple, + freebsdlike, + linux_like, + target_os = "aix", + target_os = "cygwin", + target_os = "nto", +))] +#[inline] +pub(crate) fn set_ipv6_checksum(fd: BorrowedFd<'_>, offset: Option) -> io::Result<()> { + let offset: c::c_int = match offset { + Some(offset) => offset.try_into().map_err(|_| io::Errno::INVAL)?, + None => -1, + }; + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_CHECKSUM, offset) +} + +#[cfg(any( + apple, + freebsdlike, + linux_like, + target_os = "aix", + target_os = "cygwin", + target_os = "nto", +))] +#[inline] +pub(crate) fn ipv6_checksum(fd: BorrowedFd<'_>) -> io::Result> { + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_CHECKSUM).map(|offset: c::c_int| offset.try_into().ok()) +} + #[cfg(any(linux_kernel, target_os = "fuchsia"))] #[inline] pub(crate) fn ip_original_dst(fd: BorrowedFd<'_>) -> io::Result { diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index 762cdd479..f48740058 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -86,7 +86,7 @@ pub(crate) use linux_raw_sys::{ AF_LLC, AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PHONET, AF_PPPOX, AF_RDS, AF_ROSE, AF_RXRPC, AF_SECURITY, AF_SNA, AF_TIPC, AF_UNIX, AF_UNSPEC, AF_VSOCK, AF_WANPIPE, AF_X25, AF_XDP, IP6T_SO_ORIGINAL_DST, IPPROTO_FRAGMENT, IPPROTO_ICMPV6, IPPROTO_MH, - IPPROTO_ROUTING, IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_FREEBIND, + IPPROTO_ROUTING, IPV6_ADD_MEMBERSHIP, IPV6_CHECKSUM, IPV6_DROP_MEMBERSHIP, IPV6_FREEBIND, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_LOOP, IPV6_PMTUDISC_DO, IPV6_PMTUDISC_DONT, IPV6_PMTUDISC_INTERFACE, IPV6_PMTUDISC_OMIT, IPV6_PMTUDISC_PROBE, IPV6_PMTUDISC_WANT, IPV6_RECVTCLASS, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, diff --git a/src/backend/linux_raw/net/sockopt.rs b/src/backend/linux_raw/net/sockopt.rs index 33ed5c67e..d978bb47f 100644 --- a/src/backend/linux_raw/net/sockopt.rs +++ b/src/backend/linux_raw/net/sockopt.rs @@ -737,6 +737,20 @@ pub(crate) fn ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool) } +#[inline] +pub(crate) fn set_ipv6_checksum(fd: BorrowedFd<'_>, offset: Option) -> io::Result<()> { + let offset: c::c_int = match offset { + Some(offset) => offset.try_into().map_err(|_| io::Errno::INVAL)?, + None => -1, + }; + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_CHECKSUM, offset) +} + +#[inline] +pub(crate) fn ipv6_checksum(fd: BorrowedFd<'_>) -> io::Result> { + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_CHECKSUM).map(|offset: c::c_int| offset.try_into().ok()) +} + #[inline] pub(crate) fn ip_original_dst(fd: BorrowedFd<'_>) -> io::Result { let level = c::IPPROTO_IP; diff --git a/src/net/sockopt.rs b/src/net/sockopt.rs index 8faef756d..b5a40b50a 100644 --- a/src/net/sockopt.rs +++ b/src/net/sockopt.rs @@ -1360,6 +1360,58 @@ pub fn ipv6_freebind(fd: Fd) -> io::Result { backend::net::sockopt::ipv6_freebind(fd.as_fd()) } +/// `setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, value)` +/// +/// On a raw IPv6 socket, `Some(offset)` asks the kernel to compute and store +/// a checksum at byte offset `offset` in outgoing payloads, and to verify it +/// on incoming payloads. `None` disables this. ICMPv6 sockets always use the +/// kernel-computed checksum and setting this option on them fails. +/// +/// The behavior of this option is specified in [RFC 3542 Section 3.1]. +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +/// [RFC 3542 Section 3.1]: https://datatracker.ietf.org/doc/html/rfc3542#section-3.1 +#[cfg(any( + apple, + freebsdlike, + linux_like, + target_os = "aix", + target_os = "cygwin", + target_os = "nto", +))] +#[inline] +#[doc(alias = "IPV6_CHECKSUM")] +pub fn set_ipv6_checksum(fd: Fd, value: Option) -> io::Result<()> { + backend::net::sockopt::set_ipv6_checksum(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM)` +/// +/// Returns the checksum offset on a raw IPv6 socket, or `None` if checksum +/// computation is disabled. +/// +/// The behavior of this option is specified in [RFC 3542 Section 3.1]. +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +/// [RFC 3542 Section 3.1]: https://datatracker.ietf.org/doc/html/rfc3542#section-3.1 +#[cfg(any( + apple, + freebsdlike, + linux_like, + target_os = "aix", + target_os = "cygwin", + target_os = "nto", +))] +#[inline] +#[doc(alias = "IPV6_CHECKSUM")] +pub fn ipv6_checksum(fd: Fd) -> io::Result> { + backend::net::sockopt::ipv6_checksum(fd.as_fd()) +} + /// `getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST)` /// /// Even though this corresponds to a `SO_*` constant, it is an `IPPROTO_IP` diff --git a/tests/net/sockopt.rs b/tests/net/sockopt.rs index d79c93307..7d630f3df 100644 --- a/tests/net/sockopt.rs +++ b/tests/net/sockopt.rs @@ -707,3 +707,32 @@ fn test_sockopts_txtime() { Err(e) => panic!("{e}"), } } + +#[cfg(linux_kernel)] +#[test] +fn test_sockopts_ipv6_checksum() { + crate::init(); + + // Raw sockets require `CAP_NET_RAW`. + let s = match rustix::net::socket(AddressFamily::INET6, SocketType::RAW, Some(ipproto::UDP)) { + Ok(s) => s, + Err(io::Errno::PERM | io::Errno::ACCESS) => return, + Err(e) => panic!("{e}"), + }; + + assert_eq!(sockopt::ipv6_checksum(&s), Ok(None)); + + sockopt::set_ipv6_checksum(&s, Some(4)).unwrap(); + assert_eq!(sockopt::ipv6_checksum(&s), Ok(Some(4))); + + // Offsets which don't fit in a `c_int` are rejected rather than being + // treated as `-1`. + assert_eq!( + sockopt::set_ipv6_checksum(&s, Some(u32::MAX)), + Err(io::Errno::INVAL) + ); + assert_eq!(sockopt::ipv6_checksum(&s), Ok(Some(4))); + + sockopt::set_ipv6_checksum(&s, None).unwrap(); + assert_eq!(sockopt::ipv6_checksum(&s), Ok(None)); +}