From 8ab8358f636b10fdabf7ce59d5ee254fc29a970a Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Fri, 19 Dec 2025 06:41:24 +0100 Subject: [PATCH 1/4] net: fix affect/effect typo --- tokio/src/net/udp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio/src/net/udp.rs b/tokio/src/net/udp.rs index 44fa9ba3596..3fc754c26bc 100644 --- a/tokio/src/net/udp.rs +++ b/tokio/src/net/udp.rs @@ -1908,7 +1908,7 @@ impl UdpSocket { /// /// # Note /// - /// This may not have any affect on IPv6 sockets. + /// This may not have any effect on IPv6 sockets. pub fn set_multicast_loop_v4(&self, on: bool) -> io::Result<()> { self.io.set_multicast_loop_v4(on) } @@ -1930,7 +1930,7 @@ impl UdpSocket { /// /// # Note /// - /// This may not have any affect on IPv6 sockets. + /// This may not have any effect on IPv6 sockets. pub fn set_multicast_ttl_v4(&self, ttl: u32) -> io::Result<()> { self.io.set_multicast_ttl_v4(ttl) } @@ -1950,7 +1950,7 @@ impl UdpSocket { /// /// # Note /// - /// This may not have any affect on IPv4 sockets. + /// This may not have any effect on IPv4 sockets. pub fn set_multicast_loop_v6(&self, on: bool) -> io::Result<()> { self.io.set_multicast_loop_v6(on) } From 16042430f7e8a697fde88f5bb40ffa3268d6486c Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Fri, 19 Dec 2025 06:17:36 +0100 Subject: [PATCH 2/4] net: rename `{Tcp,Udp}Socket::tos` to `tos_v4` It is IPv4-specific. Follow the rename from socket2 0.6.0. --- tokio/src/net/tcp/socket.rs | 76 ++++++++++++++++++++++++++++++++----- tokio/src/net/udp.rs | 76 ++++++++++++++++++++++++++++++++----- 2 files changed, 132 insertions(+), 20 deletions(-) diff --git a/tokio/src/net/tcp/socket.rs b/tokio/src/net/tcp/socket.rs index e941c5c3a82..725237b4b87 100644 --- a/tokio/src/net/tcp/socket.rs +++ b/tokio/src/net/tcp/socket.rs @@ -491,13 +491,38 @@ impl TcpSocket { /// Gets the value of the `IP_TOS` option for this socket. /// - /// For more information about this option, see [`set_tos`]. + /// For more information about this option, see [`set_tos_v4`]. /// - /// **NOTE:** On Windows, `IP_TOS` is only supported on [Windows 8+ or - /// Windows Server 2012+.](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) + /// [`set_tos_v4`]: Self::set_tos_v4 + // https://docs.rs/socket2/0.6.1/src/socket2/socket.rs.html#1585 + #[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))) + )] + pub fn tos_v4(&self) -> io::Result { + self.inner.tos_v4() + } + + /// Deprecated. Use [`tos_v4()`] instead. /// - /// [`set_tos`]: Self::set_tos - // https://docs.rs/socket2/0.5.3/src/socket2/socket.rs.html#1464 + /// [`tos_v4()`]: Self::tos_v4 + #[deprecated( + note = "`tos` related methods have been renamed `tos_v4` since they are IPv4-specific." + )] + #[doc(hidden)] #[cfg(not(any( target_os = "fuchsia", target_os = "redox", @@ -516,7 +541,7 @@ impl TcpSocket { )))) )] pub fn tos(&self) -> io::Result { - self.inner.tos_v4() + self.tos_v4() } /// Sets the value for the `IP_TOS` option on this socket. @@ -524,9 +549,12 @@ impl TcpSocket { /// This value sets the type-of-service field that is used in every packet /// sent from this socket. /// - /// **NOTE:** On Windows, `IP_TOS` is only supported on [Windows 8+ or - /// Windows Server 2012+.](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) - // https://docs.rs/socket2/0.5.3/src/socket2/socket.rs.html#1446 + /// # Note + /// + /// - This may not have any effect on IPv6 sockets. + /// - On Windows, `IP_TOS` is only supported on [Windows 8+ or + /// Windows Server 2012+.](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) + // https://docs.rs/socket2/0.6.1/src/socket2/socket.rs.html#1566 #[cfg(not(any( target_os = "fuchsia", target_os = "redox", @@ -544,10 +572,38 @@ impl TcpSocket { target_os = "haiku" )))) )] - pub fn set_tos(&self, tos: u32) -> io::Result<()> { + pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> { self.inner.set_tos_v4(tos) } + /// Deprecated. Use [`set_tos_v4()`] instead. + /// + /// [`set_tos_v4()`]: Self::set_tos_v4 + #[deprecated( + note = "`tos` related methods have been renamed `tos_v4` since they are IPv4-specific." + )] + #[doc(hidden)] + #[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))) + )] + pub fn set_tos(&self, tos: u32) -> io::Result<()> { + self.set_tos_v4(tos) + } + /// Gets the value for the `SO_BINDTODEVICE` option on this socket /// /// This value gets the socket binded device's interface name. diff --git a/tokio/src/net/udp.rs b/tokio/src/net/udp.rs index 3fc754c26bc..7caafc5f44f 100644 --- a/tokio/src/net/udp.rs +++ b/tokio/src/net/udp.rs @@ -2002,13 +2002,38 @@ impl UdpSocket { /// Gets the value of the `IP_TOS` option for this socket. /// - /// For more information about this option, see [`set_tos`]. + /// For more information about this option, see [`set_tos_v4`]. /// - /// **NOTE:** On Windows, `IP_TOS` is only supported on [Windows 8+ or - /// Windows Server 2012+.](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) + /// [`set_tos_v4`]: Self::set_tos_v4 + // https://docs.rs/socket2/0.6.1/src/socket2/socket.rs.html#1585 + #[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))) + )] + pub fn tos_v4(&self) -> io::Result { + self.as_socket().tos_v4() + } + + /// Deprecated. Use [`tos_v4()`] instead. /// - /// [`set_tos`]: Self::set_tos - // https://docs.rs/socket2/0.5.3/src/socket2/socket.rs.html#1464 + /// [`tos_v4()`]: Self::tos_v4 + #[deprecated( + note = "`tos` related methods have been renamed `tos_v4` since they are IPv4-specific." + )] + #[doc(hidden)] #[cfg(not(any( target_os = "fuchsia", target_os = "redox", @@ -2027,7 +2052,7 @@ impl UdpSocket { )))) )] pub fn tos(&self) -> io::Result { - self.as_socket().tos_v4() + self.tos_v4() } /// Sets the value for the `IP_TOS` option on this socket. @@ -2035,9 +2060,12 @@ impl UdpSocket { /// This value sets the type-of-service field that is used in every packet /// sent from this socket. /// - /// **NOTE:** On Windows, `IP_TOS` is only supported on [Windows 8+ or - /// Windows Server 2012+.](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) - // https://docs.rs/socket2/0.5.3/src/socket2/socket.rs.html#1446 + /// # Note + /// + /// - This may not have any effect on IPv6 sockets. + /// - On Windows, `IP_TOS` is only supported on [Windows 8+ or + /// Windows Server 2012+.](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) + // https://docs.rs/socket2/0.6.1/src/socket2/socket.rs.html#1566 #[cfg(not(any( target_os = "fuchsia", target_os = "redox", @@ -2055,10 +2083,38 @@ impl UdpSocket { target_os = "haiku" )))) )] - pub fn set_tos(&self, tos: u32) -> io::Result<()> { + pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> { self.as_socket().set_tos_v4(tos) } + /// Deprecated. Use [`set_tos_v4()`] instead. + /// + /// [`set_tos_v4()`]: Self::set_tos_v4 + #[deprecated( + note = "`tos` related methods have been renamed `tos_v4` since they are IPv4-specific." + )] + #[doc(hidden)] + #[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" + )))) + )] + pub fn set_tos(&self, tos: u32) -> io::Result<()> { + self.set_tos_v4(tos) + } + /// Gets the value for the `SO_BINDTODEVICE` option on this socket /// /// This value gets the socket-bound device's interface name. From c84b6378e731ff3f4a2a0054cf1fed81098fe3bd Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Sat, 27 Dec 2025 18:27:20 +0100 Subject: [PATCH 3/4] net: Add tests for socket options The macro is based on the one from socket2 library: https://github.com/rust-lang/socket2/blob/a18be6a302b7f9c127c3593edec5d8d2690839a7/tests/socket.rs#L1345 Co-authored-by: Thomas de Zeeuw --- tokio/tests/tcp_socket.rs | 96 +++++++++++++++++++++++++++++++++++++++ tokio/tests/udp.rs | 65 ++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/tokio/tests/tcp_socket.rs b/tokio/tests/tcp_socket.rs index 8c01b86e4a1..e34ff94423b 100644 --- a/tokio/tests/tcp_socket.rs +++ b/tokio/tests/tcp_socket.rs @@ -74,3 +74,99 @@ async fn basic_linger() { srv.set_linger(Some(Duration::new(0, 0))).unwrap(); assert_eq!(srv.linger().unwrap(), Some(Duration::new(0, 0))); } + +/// Macro to create a simple test to set and get a socket option. +macro_rules! test { + // Test using the `arg`ument as expected return value. + ($( #[ $attr: meta ] )* $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { + test!($( #[$attr] )* $get_fn, $set_fn($arg), $arg); + }; + ($( #[ $attr: meta ] )* $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { + #[test] + $( #[$attr] )* + fn $get_fn() { + test!(__ new_v4, $get_fn, $set_fn($arg), $expected); + #[cfg(not(target_os = "vita"))] + test!(__ new_v6, $get_fn, $set_fn($arg), $expected); + } + }; + // Only test using a IPv4 socket. + (IPv4 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { + #[test] + fn $get_fn() { + test!(__ new_v4, $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!(__ new_v6, $get_fn, $set_fn($arg), $arg); + } + }; + + // Internal to this macro. + (__ $constructor: ident, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { + let socket = TcpSocket::$constructor().expect("failed to create `TcpSocket`"); + + let initial = socket.$get_fn().expect("failed to get initial value"); + let arg = $arg; + assert_ne!(initial, arg, "initial value and argument are the same"); + + socket.$set_fn(arg).expect("failed to set option"); + let got = socket.$get_fn().expect("failed to get value"); + let expected = $expected; + assert_eq!(got, expected, "set and get values differ"); + }; +} + +const SET_BUF_SIZE: u32 = 4096; +// Linux doubles the buffer size for kernel usage, and exposes that when +// retrieving the buffer size. + +#[cfg(not(target_os = "linux"))] +const GET_BUF_SIZE: u32 = SET_BUF_SIZE; + +#[cfg(target_os = "linux")] +const GET_BUF_SIZE: u32 = 2 * SET_BUF_SIZE; + +test!(keepalive, set_keepalive(true)); + +test!(reuseaddr, set_reuseaddr(true)); + +#[cfg(all( + unix, + not(target_os = "solaris"), + not(target_os = "illumos"), + not(target_os = "cygwin"), +))] +test!(reuseport, set_reuseport(true)); + +test!( + send_buffer_size, + set_send_buffer_size(SET_BUF_SIZE), + GET_BUF_SIZE +); + +test!( + recv_buffer_size, + set_recv_buffer_size(SET_BUF_SIZE), + GET_BUF_SIZE +); + +test!( + #[expect(deprecated, reason = "set_linger is deprecated")] + linger, + set_linger(Some(Duration::from_secs(10))) +); + +test!(nodelay, set_nodelay(true)); + +#[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" +)))] +test!(IPv4 tos_v4, set_tos_v4(96)); diff --git a/tokio/tests/udp.rs b/tokio/tests/udp.rs index df6f34fb8e8..e230ceffe06 100644 --- a/tokio/tests/udp.rs +++ b/tokio/tests/udp.rs @@ -644,3 +644,68 @@ async fn poll_ready() { } } } + +/// Macro to create a simple test to set and get a socket option. +macro_rules! test { + // Test using the `arg`ument as expected return value. + ($( #[ $attr: meta ] )* $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { + test!($( #[$attr] )* $get_fn, $set_fn($arg), $arg); + }; + ($( #[ $attr: meta ] )* $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { + #[tokio::test] + $( #[$attr] )* + async fn $get_fn() { + test!(__ "127.0.0.1:0", $get_fn, $set_fn($arg), $expected); + #[cfg(not(target_os = "vita"))] + test!(__ "[::1]:0", $get_fn, $set_fn($arg), $expected); + } + }; + // Only test using a IPv4 socket. + (IPv4 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { + #[tokio::test] + async fn $get_fn() { + test!(__ "127.0.0.1:0", $get_fn, $set_fn($arg), $arg); + } + }; + // Only test using a IPv6 socket. + (IPv6 $get_fn: ident, $set_fn: ident ( $arg: expr ) ) => { + #[tokio::test] + async fn $get_fn() { + test!(__ "[::1]:0", $get_fn, $set_fn($arg), $arg); + } + }; + + // Internal to this macro. + (__ $addr: literal, $get_fn: ident, $set_fn: ident ( $arg: expr ), $expected: expr ) => { + let socket = UdpSocket::bind($addr).await.expect("failed to create `UdpSocket`"); + + let initial = socket.$get_fn().expect("failed to get initial value"); + let arg = $arg; + assert_ne!(initial, arg, "initial value and argument are the same"); + + socket.$set_fn(arg).expect("failed to set option"); + let got = socket.$get_fn().expect("failed to get value"); + let expected = $expected; + assert_eq!(got, expected, "set and get values differ"); + }; +} + +test!(broadcast, set_broadcast(true)); + +test!(IPv4 multicast_loop_v4, set_multicast_loop_v4(false)); + +#[cfg(target_os = "linux")] // broken on non-Linux platforms https://github.com/rust-lang/socket2/pull/630 +test!(multicast_ttl_v4, set_multicast_ttl_v4(40)); + +test!(IPv6 multicast_loop_v6, set_multicast_loop_v6(false)); + +test!(IPv4 ttl, set_ttl(40)); + +#[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku" +)))] +test!(IPv4 tos_v4, set_tos_v4(96)); From 29ba9df87158c3bfc8b84b529c6d9ba111c151de Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Fri, 19 Dec 2025 06:07:18 +0100 Subject: [PATCH 4/4] net: Add `{Tcp,Udp}Socket::tclass_v6` This allows setting `Traffic Class` field, which is the equivalent of IPv4 `Type of Service` field: https://man.openbsd.org/ip6#IPV6_TCLASS Not supported on Windows: https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ipv6-socket-options --- tokio/src/net/tcp/socket.rs | 73 +++++++++++++++++++++++++++++++++++++ tokio/src/net/udp.rs | 73 +++++++++++++++++++++++++++++++++++++ tokio/tests/tcp_socket.rs | 13 +++++++ tokio/tests/udp.rs | 13 +++++++ 4 files changed, 172 insertions(+) diff --git a/tokio/src/net/tcp/socket.rs b/tokio/src/net/tcp/socket.rs index 725237b4b87..43c5011cbb1 100644 --- a/tokio/src/net/tcp/socket.rs +++ b/tokio/src/net/tcp/socket.rs @@ -489,6 +489,79 @@ impl TcpSocket { self.inner.tcp_nodelay() } + /// Gets the value of the `IPV6_TCLASS` option for this socket. + /// + /// For more information about this option, see [`set_tclass_v6`]. + /// + /// [`set_tclass_v6`]: Self::set_tclass_v6 + // https://docs.rs/socket2/0.6.1/src/socket2/sys/unix.rs.html#2541 + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))) + )] + pub fn tclass_v6(&self) -> io::Result { + self.inner.tclass_v6() + } + + /// Sets the value for the `IPV6_TCLASS` option on this socket. + /// + /// Specifies the traffic class field that is used in every packet + /// sent from this socket. + /// + /// # Note + /// + /// This may not have any effect on IPv4 sockets. + // https://docs.rs/socket2/0.6.1/src/socket2/sys/unix.rs.html#2566 + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))) + )] + pub fn set_tclass_v6(&self, tclass: u32) -> io::Result<()> { + self.inner.set_tclass_v6(tclass) + } + /// Gets the value of the `IP_TOS` option for this socket. /// /// For more information about this option, see [`set_tos_v4`]. diff --git a/tokio/src/net/udp.rs b/tokio/src/net/udp.rs index 7caafc5f44f..a858dad29c0 100644 --- a/tokio/src/net/udp.rs +++ b/tokio/src/net/udp.rs @@ -1955,6 +1955,79 @@ impl UdpSocket { self.io.set_multicast_loop_v6(on) } + /// Gets the value of the `IPV6_TCLASS` option for this socket. + /// + /// For more information about this option, see [`set_tclass_v6`]. + /// + /// [`set_tclass_v6`]: Self::set_tclass_v6 + // https://docs.rs/socket2/0.6.1/src/socket2/sys/unix.rs.html#2541 + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))) + )] + pub fn tclass_v6(&self) -> io::Result { + self.as_socket().tclass_v6() + } + + /// Sets the value for the `IPV6_TCLASS` option on this socket. + /// + /// Specifies the traffic class field that is used in every packet + /// sent from this socket. + /// + /// # Note + /// + /// This may not have any effect on IPv4 sockets. + // https://docs.rs/socket2/0.6.1/src/socket2/sys/unix.rs.html#2566 + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + ))) + )] + pub fn set_tclass_v6(&self, tclass: u32) -> io::Result<()> { + self.as_socket().set_tclass_v6(tclass) + } + /// Gets the value of the `IP_TTL` option for this socket. /// /// For more information about this option, see [`set_ttl`]. diff --git a/tokio/tests/tcp_socket.rs b/tokio/tests/tcp_socket.rs index e34ff94423b..e5f25dedf94 100644 --- a/tokio/tests/tcp_socket.rs +++ b/tokio/tests/tcp_socket.rs @@ -162,6 +162,19 @@ test!( test!(nodelay, set_nodelay(true)); +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", +))] +test!(IPv6 tclass_v6, set_tclass_v6(96)); + #[cfg(not(any( target_os = "fuchsia", target_os = "redox", diff --git a/tokio/tests/udp.rs b/tokio/tests/udp.rs index e230ceffe06..9bf3eb65dbd 100644 --- a/tokio/tests/udp.rs +++ b/tokio/tests/udp.rs @@ -699,6 +699,19 @@ test!(multicast_ttl_v4, set_multicast_ttl_v4(40)); test!(IPv6 multicast_loop_v6, set_multicast_loop_v6(false)); +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", +))] +test!(IPv6 tclass_v6, set_tclass_v6(96)); + test!(IPv4 ttl, set_ttl(40)); #[cfg(not(any(