From db5353cae02f0b8dca63274516e131a3d7b5e591 Mon Sep 17 00:00:00 2001 From: Daniil Bondarev Date: Sun, 3 Dec 2023 15:01:28 -0800 Subject: [PATCH] Added TcpFastOpenConnect sockopt (#2247) From [tcp](https://man7.org/linux/man-pages/man7/tcp.7.html) man page: ``` TCP_FASTOPEN_CONNECT (since Linux 4.11) This option enables an alternative way to perform Fast Open on the active side (client). When this option is enabled, connect(2) would behave differently depending on if a Fast Open cookie is available for the destination. If a cookie is not available (i.e. first contact to the destination), connect(2) behaves as usual by sending a SYN immediately, except the SYN would include an empty Fast Open cookie option to solicit a cookie. If a cookie is available, connect(2) would return 0 immediately but the SYN transmission is deferred. A subsequent write(2) or sendmsg(2) would trigger a SYN with data plus cookie in the Fast Open option. In other words, the actual connect operation is deferred until data is supplied. Note: While this option is designed for convenience, enabling it does change the behaviors and certain system calls might set different errno values. With cookie present, write(2) or sendmsg(2) must be called right after connect(2) in order to send out SYN+data to complete 3WHS and establish connection. Calling read(2) right after connect(2) without write(2) will cause the blocking socket to be blocked forever. The application should either set TCP_FASTOPEN_CONNECT socket option before write(2) or sendmsg(2), or call write(2) or sendmsg(2) with MSG_FASTOPEN flag directly, instead of both on the same connection. Here is the typical call flow with this new option: s = socket(); setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, 1, ...); connect(s); write(s); /* write() should always follow connect() * in order to trigger SYN to go out. */ read(s)/write(s); /* ... */ close(s); ``` --- changelog/2247.added.md | 1 + src/sys/socket/sockopt.rs | 20 ++++++++++++++++++++ test/sys/test_sockopt.rs | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 changelog/2247.added.md diff --git a/changelog/2247.added.md b/changelog/2247.added.md new file mode 100644 index 0000000000..f79f35495f --- /dev/null +++ b/changelog/2247.added.md @@ -0,0 +1 @@ +Added `TcpFastOpenConnect` sockopt to support `TCP_FASTOPEN_CONNECT` available on linux. diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 17d3f9b77f..ac5827b572 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -600,6 +600,26 @@ sockopt_impl!( libc::TCP_USER_TIMEOUT, u32 ); +#[cfg(linux_android)] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Enables TCP Fast Open (RFC 7413) on a connecting socket. If a fast open + /// cookie is not available (first attempt to connect), `connect` syscall + /// will behave as usual, except for internally trying to solicit a cookie + /// from remote peer. When cookie is available, the next `connect` syscall + /// will immediately succeed without actually establishing TCP connection. + /// The connection establishment will be defered till the next `write` or + /// `sendmsg` syscalls on the socket, allowing TCP prtocol to establish + /// connection and send data in the same packets. Note: calling `read` right + /// after `connect` without `write` on the socket will cause the blocking + /// socket to be blocked forever. + TcpFastOpenConnect, + Both, + libc::IPPROTO_TCP, + libc::TCP_FASTOPEN_CONNECT, + bool +); sockopt_impl!( /// Sets or gets the maximum socket receive buffer in bytes. RcvBuf, diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index e4e4f493c6..60b57fb5f2 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -576,3 +576,27 @@ fn test_ip_bind_address_no_port() { "getting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed", )); } + +#[test] +#[cfg(linux_android)] +fn test_tcp_fast_open_connect() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::TcpFastOpenConnect, &true).expect( + "setting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + ); + assert!(getsockopt(&fd, sockopt::TcpFastOpenConnect).expect( + "getting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + )); + setsockopt(&fd, sockopt::TcpFastOpenConnect, &false).expect( + "unsetting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + ); + assert!(!getsockopt(&fd, sockopt::TcpFastOpenConnect).expect( + "getting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + )); +}