diff --git a/.cirrus.yml b/.cirrus.yml index ba330733..a8e54ad6 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -16,6 +16,9 @@ task: - . $HOME/.cargo/env - cargo build - cargo build --no-default-features + enable_tcp_fastopen: + - sysctl net.inet.tcp.fastopen.client_enable=1 + - sysctl net.inet.tcp.fastopen.server_enable=1 amd64_test_script: - . $HOME/.cargo/env - cargo test --all-features diff --git a/src/socket.rs b/src/socket.rs index 1bc6c9f0..c8927914 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1457,6 +1457,92 @@ impl Socket { .map(|recv_tos| recv_tos > 0) } } + + /// Set `TCP_FASTOPEN` option for this socket. + /// + /// ## Windows + /// + /// Windows supports TCP Fast Open since Windows 10. + /// + /// + /// + /// `value` is a boolean with only `0` and `1`. Any `value` > `1` will be set to `1`. + /// + /// ## Linux + /// + /// Linux supports TCP Fast Open since 3.7. + /// + /// + /// + /// The option `value`, `qlen`, specifies this server's limit on the size of the queue of TFO requests that have + /// not yet completed the three-way handshake (see the remarks on prevention of resource-exhaustion attacks above). + /// + /// It was recommended to be `5` in this document. + /// + /// ## macOS + /// + /// `value` is a boolean with only `0` and `1`. Any `value` > `1` will be set to `1`. + /// + /// ## FreeBSD + /// + /// FreeBSD supports TCP Fast Open since 12.0. + /// + /// Example program: + /// + /// `value` is a boolean with only `0` and `1`. Any `value` > `1` will be set to `1`. + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos", + target_os = "windows" + ))] + #[allow(unused_mut)] + pub fn set_tcp_fastopen(&self, mut value: u32) -> io::Result<()> { + #[cfg(not(any(target_os = "linux", target_os = "android")))] + if value > 1 { + value = 1; + } + + unsafe { + setsockopt::( + self.as_raw(), + sys::IPPROTO_TCP, + sys::TCP_FASTOPEN, + value as c_int, + ) + } + } + + /// Get the value of `TCP_FASTOPEN` option for this socket. + /// + /// For more information about this option, see [`set_tcp_fastopen`]. + /// + /// [`set_tcp_fastopen`]: Socket::set_tcp_fastopen + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos", + target_os = "windows" + ))] + pub fn tcp_fastopen(&self) -> io::Result { + #[cfg(not(target_os = "windows"))] + unsafe { + getsockopt::(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_FASTOPEN) + .map(|c| c as u32) + } + #[cfg(target_os = "windows")] + unsafe { + getsockopt::(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_FASTOPEN).map(|c| c as u32) + } + } } /// Socket options for IPv6 sockets, get/set using `IPPROTO_IPV6`. diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 7e1b2755..7e33d304 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -157,6 +157,17 @@ use libc::TCP_KEEPALIVE as KEEPALIVE_TIME; #[cfg(not(any(target_vendor = "apple", target_os = "haiku", target_os = "openbsd")))] use libc::TCP_KEEPIDLE as KEEPALIVE_TIME; +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos" +))] +pub(crate) use libc::TCP_FASTOPEN; + /// Helper macro to execute a system call that returns an `io::Result`. macro_rules! syscall { ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{ diff --git a/src/sys/windows.rs b/src/sys/windows.rs index 03e1a60a..213d0225 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -78,6 +78,7 @@ pub(crate) use windows_sys::Win32::Networking::WinSock::{ }; pub(crate) const IPPROTO_IP: c_int = windows_sys::Win32::Networking::WinSock::IPPROTO_IP as c_int; pub(crate) const SOL_SOCKET: c_int = windows_sys::Win32::Networking::WinSock::SOL_SOCKET as c_int; +pub(crate) const TCP_FASTOPEN: u32 = windows_sys::Win32::Networking::WinSock::TCP_FASTOPEN as u32; /// Type used in set/getsockopt to retrieve the `TCP_NODELAY` option. /// diff --git a/tests/socket.rs b/tests/socket.rs index 7540b5d6..e84785fb 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1281,3 +1281,33 @@ fn header_included() { let got = socket.header_included().expect("failed to get value"); assert_eq!(got, true, "set and get values differ"); } + +#[test] +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos", + target_os = "windows" +))] +fn tcp_fastopen() { + let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); + let baddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 0); + let bsaddr = SockAddr::from(baddr); + socket.bind(&bsaddr).unwrap(); + socket.listen(128).unwrap(); + socket.set_tcp_fastopen(5).unwrap(); + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + assert_eq!(socket.tcp_fastopen().unwrap(), 5); + } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + { + assert_ne!(socket.tcp_fastopen().unwrap(), 0); + } +}