From 11f3da9a863228828e42dc5463d086c46d887d25 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Tue, 17 Mar 2026 16:22:11 -0700 Subject: [PATCH 1/6] Add `keepalive`, `set_keepalive` to `TcpStream` implementations --- library/std/src/net/tcp.rs | 51 +++++++++++++++++++ library/std/src/net/tcp/tests.rs | 16 ++++++ library/std/src/sys/net/connection/motor.rs | 10 +++- library/std/src/sys/net/connection/sgx.rs | 8 +++ .../src/sys/net/connection/socket/hermit.rs | 10 ++++ .../std/src/sys/net/connection/socket/mod.rs | 8 +++ .../src/sys/net/connection/socket/solid.rs | 9 ++++ .../std/src/sys/net/connection/socket/unix.rs | 9 ++++ .../src/sys/net/connection/socket/windows.rs | 9 ++++ .../std/src/sys/net/connection/uefi/mod.rs | 8 +++ .../std/src/sys/net/connection/unsupported.rs | 8 +++ library/std/src/sys/net/connection/wasip1.rs | 8 +++ .../src/sys/net/connection/xous/tcpstream.rs | 8 +++ 13 files changed, 161 insertions(+), 1 deletion(-) diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index a8046a5541c51..c6db2acf2e357 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -474,6 +474,57 @@ impl TcpStream { self.0.linger() } + /// Sets the value of the `SO_KEEPALIVE` option on this socket. + /// + /// If set to `true`, the operating system will periodically send keepalive + /// probes on an idle connection to verify that the remote peer is still + /// reachable. If the peer fails to respond after a system-determined number + /// of probes, the connection is considered broken and subsequent I/O calls + /// will return an error. + /// + /// This is useful for detecting dead peers on long-lived connections where + /// no application-level traffic is exchanged, such as database or SSH + /// connections. + /// + /// The timing and frequency of keepalive probes are controlled by + /// system-level settings and are not configured by this method alone. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(tcp_keepalive)] + /// + /// use std::net::TcpStream; + /// + /// let stream = TcpStream::connect("127.0.0.1:8080") + /// .expect("Couldn't connect to the server..."); + /// stream.set_keepalive(true).expect("set_keepalive call failed"); + #[unstable(feature = "tcp_keepalive", issue = "69774")] + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + self.0.set_keepalive(keepalive) + } + + /// Gets the value of the `SO_KEEPALIVE` option on this socket. + /// + /// For more information about this option, see [`TcpStream::set_keepalive`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(tcp_keepalive)] + /// + /// use std::net::TcpStream; + /// + /// let stream = TcpStream::connect("127.0.0.1:8080") + /// .expect("Couldn't connect to the server..."); + /// stream.set_keepalive(true).expect("set_keepalive call failed"); + /// assert_eq!(stream.keepalive().unwrap_or(false), true); + /// ``` + #[unstable(feature = "tcp_keepalive", issue = "69774")] + pub fn keepalive(&self) -> io::Result { + self.0.keepalive() + } + /// Sets the value of the `TCP_NODELAY` option on this socket. /// /// If set, this option disables the Nagle algorithm. This means that diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index af15009e665e1..aeb4c1d122c84 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -844,6 +844,22 @@ fn linger() { assert_eq!(None, t!(stream.linger())); } +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +#[cfg_attr(target_os = "wasi", ignore)] +fn keepalive() { + let addr = next_test_ip4(); + let _listener = t!(TcpListener::bind(&addr)); + + let stream = t!(TcpStream::connect(&("localhost", addr.port()))); + + assert_eq!(false, t!(stream.keepalive())); + t!(stream.set_keepalive(true)); + assert_eq!(true, t!(stream.keepalive())); + t!(stream.set_keepalive(false)); + assert_eq!(false, t!(stream.keepalive())); +} + #[test] #[cfg_attr(target_env = "sgx", ignore)] fn nodelay() { diff --git a/library/std/src/sys/net/connection/motor.rs b/library/std/src/sys/net/connection/motor.rs index 79a528792106c..2699599984ca6 100644 --- a/library/std/src/sys/net/connection/motor.rs +++ b/library/std/src/sys/net/connection/motor.rs @@ -5,7 +5,7 @@ use crate::net::SocketAddr::{V4, V6}; use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::sys::fd::FileDesc; -use crate::sys::{AsInner, FromInner, IntoInner, map_motor_error}; +use crate::sys::{AsInner, FromInner, IntoInner, map_motor_error, unsupported}; use crate::time::Duration; // We want to re-use as much of Rust's stdlib code as possible, @@ -127,6 +127,14 @@ impl TcpStream { moto_rt::net::linger(self.inner.as_raw_fd()).map_err(map_motor_error) } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + unsupported() + } + + pub fn keepalive(&self) -> io::Result { + unsupported() + } + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { moto_rt::net::set_nodelay(self.inner.as_raw_fd(), nodelay).map_err(map_motor_error) } diff --git a/library/std/src/sys/net/connection/sgx.rs b/library/std/src/sys/net/connection/sgx.rs index 5735a5db488fb..c72581cf69f0b 100644 --- a/library/std/src/sys/net/connection/sgx.rs +++ b/library/std/src/sys/net/connection/sgx.rs @@ -219,6 +219,14 @@ impl TcpStream { sgx_ineffective(None) } + pub fn set_keepalive(&self, _: bool) -> io::Result<()> { + sgx_ineffective(()) + } + + pub fn keepalive(&self) -> io::Result { + sgx_ineffective(false) + } + pub fn set_nodelay(&self, _: bool) -> io::Result<()> { sgx_ineffective(()) } diff --git a/library/std/src/sys/net/connection/socket/hermit.rs b/library/std/src/sys/net/connection/socket/hermit.rs index 7bbb3fba9ad4a..88fe5906f98c4 100644 --- a/library/std/src/sys/net/connection/socket/hermit.rs +++ b/library/std/src/sys/net/connection/socket/hermit.rs @@ -272,6 +272,16 @@ impl Socket { Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64))) } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + let value: i32 = if keepalive { 1 } else { 0 }; + unsafe { setsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE, value) } + } + + pub fn keepalive(&self) -> io::Result { + let raw: i32 = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE)? }; + Ok(raw != 0) + } + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { let value: i32 = if nodelay { 1 } else { 0 }; unsafe { setsockopt(self, netc::IPPROTO_TCP, netc::TCP_NODELAY, value) } diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 256b99dfa9874..8efe8bd785149 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -468,6 +468,14 @@ impl TcpStream { self.inner.linger() } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + self.inner.set_keepalive(keepalive) + } + + pub fn keepalive(&self) -> io::Result { + self.inner.keepalive() + } + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { self.inner.set_nodelay(nodelay) } diff --git a/library/std/src/sys/net/connection/socket/solid.rs b/library/std/src/sys/net/connection/socket/solid.rs index 7e3cd9b6a5e0a..936355fb3a142 100644 --- a/library/std/src/sys/net/connection/socket/solid.rs +++ b/library/std/src/sys/net/connection/socket/solid.rs @@ -332,6 +332,15 @@ impl Socket { Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64))) } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + unsafe { setsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE, keepalive as c_int) } + } + + pub fn keepalive(&self) -> io::Result { + let raw: c_int = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE)? }; + Ok(raw != 0) + } + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { unsafe { setsockopt(self, netc::IPPROTO_TCP, netc::TCP_NODELAY, nodelay as c_int) } } diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index 04714048e3c5b..936dd4c93233c 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -456,6 +456,15 @@ impl Socket { Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64))) } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + unsafe { setsockopt(self, libc::SOL_SOCKET, libc::SO_KEEPALIVE, keepalive as c_int) } + } + + pub fn keepalive(&self) -> io::Result { + let raw: c_int = unsafe { getsockopt(self, libc::SOL_SOCKET, libc::SO_KEEPALIVE)? }; + Ok(raw != 0) + } + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { unsafe { setsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int) } } diff --git a/library/std/src/sys/net/connection/socket/windows.rs b/library/std/src/sys/net/connection/socket/windows.rs index ca4fa343cb9ab..0ea139cad6a04 100644 --- a/library/std/src/sys/net/connection/socket/windows.rs +++ b/library/std/src/sys/net/connection/socket/windows.rs @@ -426,6 +426,15 @@ impl Socket { Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64))) } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + unsafe { setsockopt(self, c::SOL_SOCKET, c::SO_KEEPALIVE, keepalive as c::BOOL) } + } + + pub fn keepalive(&self) -> io::Result { + let raw: c::BOOL = unsafe { getsockopt(self, c::SOL_SOCKET, c::SO_KEEPALIVE)? }; + Ok(raw != 0) + } + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { unsafe { setsockopt(self, c::IPPROTO_TCP, c::TCP_NODELAY, nodelay as c::BOOL) } } diff --git a/library/std/src/sys/net/connection/uefi/mod.rs b/library/std/src/sys/net/connection/uefi/mod.rs index 107a3e23733de..ac187b8b202e3 100644 --- a/library/std/src/sys/net/connection/uefi/mod.rs +++ b/library/std/src/sys/net/connection/uefi/mod.rs @@ -112,6 +112,14 @@ impl TcpStream { unsupported() } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + unsupported() + } + + pub fn keepalive(&self) -> io::Result { + unsupported() + } + pub fn set_nodelay(&self, _: bool) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/net/connection/unsupported.rs b/library/std/src/sys/net/connection/unsupported.rs index fb18e8dec557c..c019ec1c9e521 100644 --- a/library/std/src/sys/net/connection/unsupported.rs +++ b/library/std/src/sys/net/connection/unsupported.rs @@ -87,6 +87,14 @@ impl TcpStream { self.0 } + pub fn set_keepalive(&self, _: bool) -> io::Result<()> { + self.0 + } + + pub fn keepalive(&self) -> io::Result { + self.0 + } + pub fn set_nodelay(&self, _: bool) -> io::Result<()> { self.0 } diff --git a/library/std/src/sys/net/connection/wasip1.rs b/library/std/src/sys/net/connection/wasip1.rs index d6c7e023e865d..7f813f4ae0f83 100644 --- a/library/std/src/sys/net/connection/wasip1.rs +++ b/library/std/src/sys/net/connection/wasip1.rs @@ -145,6 +145,14 @@ impl TcpStream { unsupported() } + pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + unsupported() + } + + pub fn keepalive(&self) -> io::Result { + unsupported() + } + pub fn set_nodelay(&self, _: bool) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/net/connection/xous/tcpstream.rs b/library/std/src/sys/net/connection/xous/tcpstream.rs index 4df75453d1f45..521d24e3c496e 100644 --- a/library/std/src/sys/net/connection/xous/tcpstream.rs +++ b/library/std/src/sys/net/connection/xous/tcpstream.rs @@ -353,6 +353,14 @@ impl TcpStream { unimpl!(); } + pub fn set_keepalive(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn keepalive(&self) -> io::Result { + unimpl!(); + } + pub fn set_nodelay(&self, enabled: bool) -> io::Result<()> { crate::os::xous::ffi::blocking_scalar( services::net_server(), From 371b79580cbdee44e10422a8a8922c094b2da94b Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Tue, 17 Mar 2026 19:16:19 -0700 Subject: [PATCH 2/6] Add windows binding for SO_KEEPALIVE --- library/std/src/sys/pal/windows/c/bindings.txt | 1 + library/std/src/sys/pal/windows/c/windows_sys.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index c21d1de81341f..7b5abbc363786 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2388,6 +2388,7 @@ SleepConditionVariableSRW SleepEx SO_BROADCAST SO_ERROR +SO_KEEPALIVE SO_LINGER SO_RCVTIMEO SO_SNDTIMEO diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index eb54efd1c1fe0..7cef71097a784 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -3189,6 +3189,7 @@ pub const SOCK_STREAM: WINSOCK_SOCKET_TYPE = 1i32; pub const SOL_SOCKET: i32 = 65535i32; pub const SO_BROADCAST: i32 = 32i32; pub const SO_ERROR: i32 = 4103i32; +pub const SO_KEEPALIVE: i32 = 8i32; pub const SO_LINGER: i32 = 128i32; pub const SO_RCVTIMEO: i32 = 4102i32; pub const SO_SNDTIMEO: i32 = 4101i32; From 9cae143c580afb77ad2a5236e94cd7bc19a63a89 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Tue, 17 Mar 2026 20:23:34 -0700 Subject: [PATCH 3/6] Fix warnings --- library/std/src/sys/net/connection/motor.rs | 2 +- library/std/src/sys/net/connection/wasip1.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/net/connection/motor.rs b/library/std/src/sys/net/connection/motor.rs index 2699599984ca6..be879f43b0bb4 100644 --- a/library/std/src/sys/net/connection/motor.rs +++ b/library/std/src/sys/net/connection/motor.rs @@ -127,7 +127,7 @@ impl TcpStream { moto_rt::net::linger(self.inner.as_raw_fd()).map_err(map_motor_error) } - pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + pub fn set_keepalive(&self, _: bool) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/net/connection/wasip1.rs b/library/std/src/sys/net/connection/wasip1.rs index 7f813f4ae0f83..93cb777a70842 100644 --- a/library/std/src/sys/net/connection/wasip1.rs +++ b/library/std/src/sys/net/connection/wasip1.rs @@ -145,7 +145,7 @@ impl TcpStream { unsupported() } - pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { + pub fn set_keepalive(&self, _: bool) -> io::Result<()> { unsupported() } From e1ac6ff082d91d07cd22379f5030d7c328359166 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Thu, 19 Mar 2026 19:24:11 -0700 Subject: [PATCH 4/6] APIs are unsupported on solid platform --- library/std/src/sys/net/connection/socket/solid.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/net/connection/socket/solid.rs b/library/std/src/sys/net/connection/socket/solid.rs index 936355fb3a142..79e6c12d6ab33 100644 --- a/library/std/src/sys/net/connection/socket/solid.rs +++ b/library/std/src/sys/net/connection/socket/solid.rs @@ -6,6 +6,7 @@ use crate::ffi::CStr; use crate::io::{self, BorrowedBuf, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut}; use crate::net::{Shutdown, SocketAddr}; use crate::os::solid::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}; +use crate::sys::pal::unsupported; use crate::sys::{FromInner, IntoInner, abi}; use crate::time::Duration; use crate::{cmp, mem, ptr, str}; @@ -332,13 +333,12 @@ impl Socket { Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64))) } - pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { - unsafe { setsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE, keepalive as c_int) } + pub fn set_keepalive(&self, _: bool) -> io::Result<()> { + unsupported() } pub fn keepalive(&self) -> io::Result { - let raw: c_int = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE)? }; - Ok(raw != 0) + unsupported() } pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { From 8badb8df7b52bd5f49027e267d1790f6ac4ad42a Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Fri, 24 Apr 2026 12:55:09 -0700 Subject: [PATCH 5/6] PR feedback --- library/std/src/net/tcp/tests.rs | 3 +-- library/std/src/sys/net/connection/socket/hermit.rs | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index aeb4c1d122c84..c8ea35fb1094d 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -834,8 +834,7 @@ fn test_timeout_zero_duration() { fn linger() { let addr = next_test_ip4(); let _listener = t!(TcpListener::bind(&addr)); - - let stream = t!(TcpStream::connect(&("localhost", addr.port()))); + let stream = t!(TcpStream::connect(&addr)); assert_eq!(None, t!(stream.linger())); t!(stream.set_linger(Some(Duration::from_secs(1)))); diff --git a/library/std/src/sys/net/connection/socket/hermit.rs b/library/std/src/sys/net/connection/socket/hermit.rs index 88fe5906f98c4..09953cc515af7 100644 --- a/library/std/src/sys/net/connection/socket/hermit.rs +++ b/library/std/src/sys/net/connection/socket/hermit.rs @@ -273,12 +273,11 @@ impl Socket { } pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { - let value: i32 = if keepalive { 1 } else { 0 }; - unsafe { setsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE, value) } + unsafe { setsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE, keepalive as c_int) } } pub fn keepalive(&self) -> io::Result { - let raw: i32 = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE)? }; + let raw: c_int = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_KEEPALIVE)? }; Ok(raw != 0) } From 3ed5d91f2bd5ba506aefafdad72433f9cc333979 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Sun, 26 Apr 2026 08:41:24 -0700 Subject: [PATCH 6/6] Fix wrong test change --- library/std/src/net/tcp/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index c8ea35fb1094d..f6fe8e1b2353b 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -834,7 +834,8 @@ fn test_timeout_zero_duration() { fn linger() { let addr = next_test_ip4(); let _listener = t!(TcpListener::bind(&addr)); - let stream = t!(TcpStream::connect(&addr)); + + let stream = t!(TcpStream::connect(&("localhost", addr.port()))); assert_eq!(None, t!(stream.linger())); t!(stream.set_linger(Some(Duration::from_secs(1)))); @@ -849,8 +850,7 @@ fn linger() { fn keepalive() { let addr = next_test_ip4(); let _listener = t!(TcpListener::bind(&addr)); - - let stream = t!(TcpStream::connect(&("localhost", addr.port()))); + let stream = t!(TcpStream::connect(&addr)); assert_eq!(false, t!(stream.keepalive())); t!(stream.set_keepalive(true));