From bdda377a1c058cc19a146137dab144598a567456 Mon Sep 17 00:00:00 2001 From: Raja Babu <100289530+RajaBabu15@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:31:18 +0530 Subject: [PATCH 1/2] net: use IPPROTO_IPV6 for IPV6_MULTICAST_HOPS Both backends were passing IPPROTO_IP. Add a UDP set/get round-trip so this can't hide behind NOPROTOOPT on a stream socket. Fixes #1660 --- src/backend/libc/net/sockopt.rs | 4 ++-- src/backend/linux_raw/net/sockopt.rs | 4 ++-- tests/net/sockopt.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/backend/libc/net/sockopt.rs b/src/backend/libc/net/sockopt.rs index 0a8541461..9241fdf74 100644 --- a/src/backend/libc/net/sockopt.rs +++ b/src/backend/libc/net/sockopt.rs @@ -616,12 +616,12 @@ pub(crate) fn ipv6_multicast_loop(fd: BorrowedFd<'_>) -> io::Result { #[inline] pub(crate) fn set_ipv6_multicast_hops(fd: BorrowedFd<'_>, multicast_hops: u32) -> io::Result<()> { - setsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS, multicast_hops) + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_MULTICAST_HOPS, multicast_hops) } #[inline] pub(crate) fn ipv6_multicast_hops(fd: BorrowedFd<'_>) -> io::Result { - getsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS) + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_MULTICAST_HOPS) } #[inline] diff --git a/src/backend/linux_raw/net/sockopt.rs b/src/backend/linux_raw/net/sockopt.rs index 85c65b085..98cda240c 100644 --- a/src/backend/linux_raw/net/sockopt.rs +++ b/src/backend/linux_raw/net/sockopt.rs @@ -567,12 +567,12 @@ pub(crate) fn ipv6_multicast_loop(fd: BorrowedFd<'_>) -> io::Result { #[inline] pub(crate) fn set_ipv6_multicast_hops(fd: BorrowedFd<'_>, multicast_hops: u32) -> io::Result<()> { - setsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS, multicast_hops) + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_MULTICAST_HOPS, multicast_hops) } #[inline] pub(crate) fn ipv6_multicast_hops(fd: BorrowedFd<'_>) -> io::Result { - getsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS) + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_MULTICAST_HOPS) } #[inline] diff --git a/tests/net/sockopt.rs b/tests/net/sockopt.rs index d139d9a5c..7293c172d 100644 --- a/tests/net/sockopt.rs +++ b/tests/net/sockopt.rs @@ -429,11 +429,23 @@ fn test_sockopts_ipv6() { #[cfg(not(target_os = "netbsd"))] match sockopt::ipv6_multicast_hops(&s) { Ok(hops) => assert_eq!(hops, 0), + Err(io::Errno::OPNOTSUPP) => (), Err(io::Errno::NOPROTOOPT) => (), Err(io::Errno::INVAL) => (), Err(err) => panic!("{:?}", err), } + match sockopt::set_ipv6_multicast_hops(&s, 8) { + Ok(()) => match sockopt::ipv6_multicast_hops(&s) { + Ok(hops) => assert_eq!(hops, 8), + Err(err) => panic!("{:?}", err), + }, + Err(io::Errno::OPNOTSUPP) => (), + Err(io::Errno::INVAL) => (), + Err(io::Errno::NOPROTOOPT) => (), + Err(err) => panic!("{:?}", err), + } + // Set the IPv4 V6OONLY value. let v6only = rustix::net::sockopt::ipv6_v6only(&s).unwrap(); sockopt::set_ipv6_v6only(&s, !v6only).unwrap(); @@ -516,6 +528,20 @@ fn test_sockopts_ipv6() { test_sockopts_tcp(&s); } +#[test] +fn test_ipv6_multicast_hops_dgram() { + crate::init(); + + let s = rustix::net::socket(AddressFamily::INET6, SocketType::DGRAM, None).unwrap(); + match sockopt::set_ipv6_multicast_hops(&s, 8) { + Ok(()) => assert_eq!(sockopt::ipv6_multicast_hops(&s).unwrap(), 8), + Err(io::Errno::OPNOTSUPP) => (), + Err(io::Errno::INVAL) => (), + Err(io::Errno::NOPROTOOPT) => (), + Err(err) => panic!("{:?}", err), + } +} + #[cfg(linux_kernel)] #[test] fn test_socket_passcred() { From a98a0b60ddcbe296a2bb5571c8765d2b4deeef1b Mon Sep 17 00:00:00 2001 From: Raja Babu <100289530+RajaBabu15@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:36:57 +0530 Subject: [PATCH 2/2] test: default IPV6_MULTICAST_HOPS is 1 The old assert expected 0 because get used the wrong protocol level. Linux (and NetBSD) report 1 once the option is queried correctly, same as IP_MULTICAST_TTL. --- tests/net/sockopt.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/net/sockopt.rs b/tests/net/sockopt.rs index 7293c172d..0ad741118 100644 --- a/tests/net/sockopt.rs +++ b/tests/net/sockopt.rs @@ -424,11 +424,8 @@ fn test_sockopts_ipv6() { } assert_ne!(sockopt::ipv6_unicast_hops(&s).unwrap(), 0); - // On NetBSD, `get_ipv6_multicasthops` returns 1 here. It's not evident - // why it differs from other OS's. - #[cfg(not(target_os = "netbsd"))] match sockopt::ipv6_multicast_hops(&s) { - Ok(hops) => assert_eq!(hops, 0), + Ok(hops) => assert_eq!(hops, 1), Err(io::Errno::OPNOTSUPP) => (), Err(io::Errno::NOPROTOOPT) => (), Err(io::Errno::INVAL) => (),