TCP Fast Open
TCP Fast Open, or TFO for short, is an extension to the TCP protocol that allow a small amount of data to be sent alongside the initial SYN packet, when a TCP connection is being established. This can save round-trips and reduce response time in certain cases.
TFO cannot always be used, however, because it changes how TCP behave: The receiving end might see this data duplicated due to retransmission of the SYN packet. For this reason, TFO must only be used when the data in the initial packet will be processed idempotently. Whether this is the case, depends on the protocol and the application.
One important use case where idempotency is guaranteed, is the TLS Client Hello message.
This is the first message that a client sends to a server, to initiate the TLS handshake.
This saves a round-trip when establishing TLS connections.
SslHandler in Netty will automatically take advantage of TFO when available.
First, TFO needs to be supported and enabled in the operating system.
On Linux, this is controlled with the
The file may contain one of the following values:
- TFO is not enabled.
- TFO is enabled for outgoing connections (clients).
- TFO is enabled for incoming connections (servers).
- TFO is enabled for both clients and servers.
The setting can be changed by writing the derised configuration value to the file as the
The second step is to enable TFO in Netty. This is done in different ways for servers and clients.
For servers, you set the
EpollChannelOption.TCP_FASTOPEN as an option on the
ServerBootstrap sb = ...; sb.option(EpollChannelOption.TCP_FASTOPEN, maxPendingFastOpen);
And that's all it takes. The option specifies how many fast-open requests can be pending on the socket at any one time. This limits the system resources that can be tied up in fast-open payloads for connections that haven't been established. See the Passive Open appendix to RFC 7413 for reference.
For clients, the situation is a little more involved.
You first set the
ChannelOption.TCP_FASTOPEN_CONNECT option to
Bootstrap cb = ...; cb.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
Then, you also have to decide what data is allowed to be sent alongside the SYN packet, taking into consideration that it may arrive multiple times due to retransmission.
This data then needs to be placed in the channels outbound buffer before the channel is connected.
To obtain a channel before it's connected, we call
Here is an example of what this can look like:
Bootstrap cb = new Bootstrap(); cb.option(ChannelOption.TCP_FASTOPEN_CONNECT, true); // ...set handler, etc... Channel channel = cb.register().sync().channel(); // Get unconnected channel. ByteBuf fastOpenData = ...; ByteBuf normalData = ...; channel.write(fastOpenData); // Write TFO data. channel.connect(remoteAddress).sync(); // Establish connection (flushes TFO data). channel.write(normalData); // TCP connection works like normal now.
An important aspect about the above: we call
connect() on the channel produced by
We cannot use the
Bootstrap.connect() method, because that will create another new channel with its own outbound buffer.