Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TCP_FASTOPEN option for Socket #336

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .cirrus.yml
Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions src/socket.rs
Expand Up @@ -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.
///
/// <https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-tcp-socket-options>
///
/// `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.
///
/// <https://lwn.net/Articles/508865/>
///
/// 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: <https://people.freebsd.org/~pkelsey/tfo-tools/tfo-srv.c>
///
/// `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::<c_int>(
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<u32> {
#[cfg(not(target_os = "windows"))]
unsafe {
getsockopt::<c_int>(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_FASTOPEN)
.map(|c| c as u32)
}
#[cfg(target_os = "windows")]
unsafe {
getsockopt::<u8>(self.as_raw(), sys::IPPROTO_TCP, sys::TCP_FASTOPEN).map(|c| c as u32)
}
}
}

/// Socket options for IPv6 sockets, get/set using `IPPROTO_IPV6`.
Expand Down
11 changes: 11 additions & 0 deletions src/sys/unix.rs
Expand Up @@ -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),* $(,)* ) ) => {{
Expand Down
1 change: 1 addition & 0 deletions src/sys/windows.rs
Expand Up @@ -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.
///
Expand Down
30 changes: 30 additions & 0 deletions tests/socket.rs
Expand Up @@ -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);
}
}