Skip to content

Support TCP Fast Open #49

Open
Open
@zonyitoo

Description

@zonyitoo

TcpListener

All of Linux, OS X and Windows are the same, call setsockopt with TCP_FASTOPEN option

// For Linux, value is the queue length of pending packets
int opt = 5;
// For the others, just a boolean value for enable and disable
int opt = 1;

// Call it before listen()
int ret = setsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));

TcpStream

This is a bit more complicated than TcpListener, because we have to send SYN with the first data packet to make TFO works. APIs are completely different on different platforms:

Linux

Before 4.11, Linux uses MSG_FASTOPEN flag for sendto(), doesn't need to call connect()

// Send SYN with data in buf
ssize_t n = sendto(socket, buf, buf_length, MSG_FASTOPEN, saddr, saddr_len);

After 4.11, Linux provides a TCP_FASTOPEN_CONNECT option

int enable = 1;

// Enable it before connect()
int ret = setsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &enable, sizeof(enable));

// Call connect as usual
int ret = connect(socket, saddr, saddr_len);

BSDs (except Darwin)

Uses sendto() as Linux without MSG_FASTOPEN flag

// Set before sendto()
int ret = setsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));

// Call sendto() without flags
ssize_t n = sendto(socket, buf, buf_length, 0, saddr, saddr_len);

Darwin (OS X)

Darwin supports TFO after OS X 10.11, iOS 9.0, tvOS 9.0 and watchOS 2.0.

It supports with a new syscall connectx.

sa_endpoints_t endpoints;
memset(&endpoints, 0, sizeof(endpoints));
endpoints.sae_dstaddr = &saddr;
endpoints.sae_dstaddrlen = saddr_len;

int ret = connectx(socket,
                   &endpoints,
                   SAE_ASSOCID_ANY,
                   CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE,
                   NULL,
                   0,
                   NULL,
                   NULL);

SYN will be sent with the first write or send.

Windows

Windows supports with ConnectEx, since Windows 10. https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex

// TCP_FASTOPEN is required
int ret = setsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));

// Get LPFN_CONNECTEX
LPFN_CONNECTEX ConnectEx = ...;

// Call it with the first packet of data, SYN will send with this packet's data
LPDWORD bytesSent = 0;
int ret = ConnectEx(socket, saddr, saddr_len, buf, buf_length, &bytesSent, &overlapped);

Challenge

As we can see for those OSes' APIs, (except OS X and Linux > 4.10) connections are made with the first write(), which means that if we want to support TFO, we have to put those code in the first call of std::io::Write::write or tokio::AsyncWrite::poll_write.

There is no other way except customizing a TcpStream from socket() and call different APIs on different OSes while sending the first data packet.

I want to open a discussion here for how in Rust's world to support TFO gracefully.

Related API PRs

  1. connectx for OS X: Add TCP FastOpen support for macOS libc#1635
  2. TCP_FASTOPEN_CONNECT for Linux: Add TCP_FASTOPEN_CONNECT for Linux after 4.11 libc#1634
  3. TCP_FASTOPEN for Windows: Missing TCP_FASTOPEN retep998/winapi-rs#856

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions