From ff935f4c0a6315c3eaa5fda574c49c13167e4600 Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Sun, 28 Dec 2025 09:26:22 +0100 Subject: [PATCH 1/3] Fix multicast_ttl_v4 on non-Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On MacOS and FreeBSD, `multicast_ttl_v4` fails with: Os { code: 22, kind: InvalidInput, message: "Invalid argument" } According to FreeBSD’s ip(4), it is supposed to be u8: https://man.freebsd.org/cgi/man.cgi?query=ip&apropos=0&sektion=0&manpath=FreeBSD+15.0-RELEASE+and+Ports&arch=default&format=html Had to expand the test! macro to take SockType, since the multicast options work only over UDP. Without that, `set_multicast_ttl_v4` would fail even on Linux with: Os { code: 22, kind: InvalidInput, message: "Invalid argument" } --- src/socket.rs | 36 ++++++++++++++++++++++++++++++++++-- tests/socket.rs | 21 +++++++++++++++------ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/socket.rs b/src/socket.rs index b6cbcdb0..0113c86e 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -6,6 +6,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "nto" +))] +use std::ffi::c_uchar; use std::fmt; use std::io::{self, Read, Write}; #[cfg(not(target_os = "redox"))] @@ -27,6 +37,28 @@ use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type}; #[cfg(not(target_os = "redox"))] use crate::{MaybeUninitSlice, MsgHdr, RecvFlags}; +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "nto" +))] +type IpV4MultiCastType = c_uchar; + +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "nto" +)))] +type IpV4MultiCastType = c_int; + /// Owned wrapper around a system socket. /// /// This type simply wraps an instance of a file descriptor (`c_int`) on Unix @@ -1514,7 +1546,7 @@ impl Socket { /// [`set_multicast_ttl_v4`]: Socket::set_multicast_ttl_v4 pub fn multicast_ttl_v4(&self) -> io::Result { unsafe { - getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL) + getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL) .map(|ttl| ttl as u32) } } @@ -1532,7 +1564,7 @@ impl Socket { self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL, - ttl as c_int, + ttl as IpV4MultiCastType, ) } } diff --git a/tests/socket.rs b/tests/socket.rs index 3c263646..95e96304 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1352,29 +1352,36 @@ macro_rules! test { #[test] $( #[$attr] )* fn $get_fn() { - test!(__ Domain::IPV4, $get_fn, $set_fn($arg), $expected); + test!(__ Domain::IPV4, STREAM, $get_fn, $set_fn($arg), $expected); #[cfg(not(target_os = "vita"))] - test!(__ Domain::IPV6, $get_fn, $set_fn($arg), $expected); + test!(__ Domain::IPV6, STREAM, $get_fn, $set_fn($arg), $expected); + } + }; + // Only test using a IPv4 socket. + (IPv4:DGRAM $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { + #[test] + fn $get_fn() { + test!(__ Domain::IPV4, DGRAM, $get_fn, $set_fn($arg), $arg); } }; // Only test using a IPv4 socket. (IPv4 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] fn $get_fn() { - test!(__ Domain::IPV4, $get_fn, $set_fn($arg), $arg); + test!(__ Domain::IPV4, STREAM, $get_fn, $set_fn($arg), $arg); } }; // Only test using a IPv6 socket. (IPv6 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] fn $get_fn() { - test!(__ Domain::IPV6, $get_fn, $set_fn($arg), $arg); + test!(__ Domain::IPV6, STREAM, $get_fn, $set_fn($arg), $arg); } }; // Internal to this macro. - (__ $ty: expr, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { - let socket = Socket::new($ty, Type::STREAM, None).expect("failed to create `Socket`"); + (__ $dom: expr, $ty: ident, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { + let socket = Socket::new($dom, Type::$ty, None).expect("failed to create `Socket`"); let initial = socket.$get_fn().expect("failed to get initial value"); let arg = $arg; @@ -1582,6 +1589,8 @@ test!(IPv4 multicast_all_v4, set_multicast_all_v4(false)); #[cfg(all(feature = "all", target_os = "linux"))] test!(IPv6 multicast_all_v6, set_multicast_all_v6(false)); +test!(IPv4:DGRAM multicast_ttl_v4, set_multicast_ttl_v4(40)); + #[test] #[cfg(not(any( target_os = "haiku", From 2a96c82e33db80e9980e72902e7b91876c027bdd Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Sun, 28 Dec 2025 17:03:34 +0100 Subject: [PATCH 2/3] undo impl for test --- src/socket.rs | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/src/socket.rs b/src/socket.rs index 0113c86e..b6cbcdb0 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -6,16 +6,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "nto" -))] -use std::ffi::c_uchar; use std::fmt; use std::io::{self, Read, Write}; #[cfg(not(target_os = "redox"))] @@ -37,28 +27,6 @@ use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type}; #[cfg(not(target_os = "redox"))] use crate::{MaybeUninitSlice, MsgHdr, RecvFlags}; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "nto" -))] -type IpV4MultiCastType = c_uchar; - -#[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "nto" -)))] -type IpV4MultiCastType = c_int; - /// Owned wrapper around a system socket. /// /// This type simply wraps an instance of a file descriptor (`c_int`) on Unix @@ -1546,7 +1514,7 @@ impl Socket { /// [`set_multicast_ttl_v4`]: Socket::set_multicast_ttl_v4 pub fn multicast_ttl_v4(&self) -> io::Result { unsafe { - getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL) + getsockopt::(self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL) .map(|ttl| ttl as u32) } } @@ -1564,7 +1532,7 @@ impl Socket { self.as_raw(), sys::IPPROTO_IP, sys::IP_MULTICAST_TTL, - ttl as IpV4MultiCastType, + ttl as c_int, ) } } From 4833f83e4b549444f382a4de35e87c0d8a02cbcd Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Fri, 2 Jan 2026 17:52:59 +0100 Subject: [PATCH 3/3] Try if binding the socket makes a difference. This is the main difference from tokio, where `UspSocket` can be only constructed by binding to a specific address. --- tests/socket.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/socket.rs b/tests/socket.rs index 95e96304..1be3ae72 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1352,36 +1352,40 @@ macro_rules! test { #[test] $( #[$attr] )* fn $get_fn() { - test!(__ Domain::IPV4, STREAM, $get_fn, $set_fn($arg), $expected); + test!(__ Domain::IPV4, STREAM, None, $get_fn, $set_fn($arg), $expected); #[cfg(not(target_os = "vita"))] - test!(__ Domain::IPV6, STREAM, $get_fn, $set_fn($arg), $expected); + test!(__ Domain::IPV6, STREAM, None, $get_fn, $set_fn($arg), $expected); } }; // Only test using a IPv4 socket. (IPv4:DGRAM $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] fn $get_fn() { - test!(__ Domain::IPV4, DGRAM, $get_fn, $set_fn($arg), $arg); + test!(__ Domain::IPV4, DGRAM, Some("127.0.0.1:0"), $get_fn, $set_fn($arg), $arg); } }; // Only test using a IPv4 socket. (IPv4 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] fn $get_fn() { - test!(__ Domain::IPV4, STREAM, $get_fn, $set_fn($arg), $arg); + test!(__ Domain::IPV4, STREAM, None, $get_fn, $set_fn($arg), $arg); } }; // Only test using a IPv6 socket. (IPv6 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { #[test] fn $get_fn() { - test!(__ Domain::IPV6, STREAM, $get_fn, $set_fn($arg), $arg); + test!(__ Domain::IPV6, STREAM, None, $get_fn, $set_fn($arg), $arg); } }; // Internal to this macro. - (__ $dom: expr, $ty: ident, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { + (__ $dom: expr, $ty: ident, $bind_addr:expr, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { let socket = Socket::new($dom, Type::$ty, None).expect("failed to create `Socket`"); + if let Some(addr) = $bind_addr { + use std::str::FromStr as _; + socket.bind(&SocketAddr::from_str(addr).unwrap().into()).expect("Failed to bind"); + } let initial = socket.$get_fn().expect("failed to get initial value"); let arg = $arg;