TCP Fast Open --TFO
TCP Fast Open定义
TCP Fast Open(TFO)是用来加速连续TCP连接的数据交互的TCP协议扩展,原理如下:在TCP三次握手的过程中,当用户首次访问Server时,发送SYN包,Server根据用户IP生成Cookie(已加密),并与SYN-ACK一同发回Client;当Client随后重连时,在SYN包携带TCP Cookie;如果Server校验合法,则在用户回复ACK前就可以直接发送数据;否则按照正常三次握手进行。
参考文章:https://www.qacafe.com/resources/what-is-tcp-fast-open/
参考:https://www.ietf.org/proceedings/80/slides/tcpm-3.pdf
参考:https://lwn.net/Articles/508865/
TFO的的流程如下:
用户向Server发送SYN包并请求TFO Cookie;
Server根据用户的IP加密生成Cookie,随SYN-ACK发给用户
用户储存TFO Cookie
当连接断掉,重连后的流程如下:
用户向Server发送SYN包(携带TCP Cookie),同时附带请求;
Server校验Cookie(解密Cookie以及比对IP地址或者重新加密IP地址以和接收到的Cookie进行对比)。
如果验证成功,向用户发送SYN+ACK,在用户回复ACK之前,便可以向用户传输数据;
如果验证失败,则丢弃此TFO请求携带的数据,回复SYN-ACK确认SYN Seq,完成正常的三次握手。
如果Cookie在网络传输的过程中被丢弃,Client在RTO后,发起普通的TCP连接
开启了 TFO 功能的 Server 在收到该 SYN 报文后,会生成 Cookie,通过 SYNACK 报文的选项字段传回。
Clinet 收到 SYNACK 报文后,便会缓存下该 Cookie。
Requesting Fast Open Cookie in connection 1:
TCP A (Client) TCP B (Server)
______________ ______________
CLOSED LISTEN
#1 SYN-SENT ----- <SYN,CookieOpt=NIL> ----------> SYN-RCVD
#2 ESTABLISHED <---- <SYN,ACK,CookieOpt=C> ---------- SYN-RCVD
(caches cookie C)
后续连接建立过程
当 Clinet 后续再发起连接时,由于已经它已经有了 Cookie,因此它可以在第一个 SYN 报文时就携带数据(DATA_A),
Server 在收到该 SYN 报文后,如果 Cookie 验证通过,便会将数据上送给应用程序,即使现在三次握手还没有完成,TCP 套接字还处于(SYN_RCVD)状态
后续连接建立过程
当 Clinet 后续再发起连接时,由于已经它已经有了 Cookie,因此它可以在第一个 SYN 报文时就携带数据(DATA_A),
Server 在收到该 SYN 报文后,如果 Cookie 验证通过,便会将数据上送给应用程序,即使现在三次握手还没有完成,TCP 套接字还处于(SYN_RCVD)状态
Performing TCP Fast Open in connection 2:
TCP A (Client) TCP B (Server)
______________ ______________
CLOSED LISTEN
#1 SYN-SENT ----- <SYN=x,CookieOpt=C,DATA_A> ----> SYN-RCVD
#2 ESTABLISHED <---- <SYN=y,ACK=x+len(DATA_A)+1> ---- SYN-RCVD
#3 ESTABLISHED <---- <ACK=x+len(DATA_A)+1,DATA_B>---- SYN-RCVD
#4 ESTABLISHED ----- <ACK=y+1>--------------------> ESTABLISHED
#5 ESTABLISHED --- <ACK=y+len(DATA_B)+1>----------> ESTABLISHED
Cookie
Cookie是用来验证Client的IP所有权的加密字符串。Server负责产生及验证Cookie。Client缓存Cookie以及在随后的连接初始化阶段将Cookie返回给Server。
Server通过加密Client的源IP产生16字节长度的Cookie(通过AES-128算法)。加密和解密Cookie很快,可以和正常的SYN及SYN-ACK包的处理时间差不多。
为了确保安全及用户IP会变(如DHCP),Cookie值会隔一段时间变化一次。
Cookie 的格式
Cookie 通过 TCP 的选项(Kind = 34)在 TCP 双方之间交互,其格式如下。它的值由 Server 根据 <ClinetIP、ServerIP> 生成。注意,Cookie 与 TCP 端口号无关,即使应用程序不同,只要 Client 和 Server 使用的 IP 不变,两台主机上的 TCP 程序就可以复用一个 Cookie,换句话说,这个 Cookie 是主机粒度的。
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Kind | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Cookie ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Kind 1 byte: value = 34
Length 1 byte: range 6 to 18 (bytes); limited by
remaining space in the options field.
The number MUST be even.
Cookie 0, or 4 to 16 bytes (Length - 2)
Clinet 发起 TFO 请求
和普通 SYN 报文的构建过程不同,使用 TFO 时,Clinet 使用tcp_send_syn_data()
组装构建 SYN 报文。
它会从内核 metrics 框架中查询是有目标 Server 对应的 Cookie,若有,则直接填充到 SYN 报文中,若没有,则填充一个长度为 0 的Cookie 选项。
tcp_sendmsg
|
|-- tcp_sendmsg_fastopen
|
|-- tcp_connect
|
|-- tcp_send_syn_data
/* Build and send a SYN with data and (cached) Fast Open cookie. However,
* queue a data-only packet after the regular SYN, such that regular SYNs
* are retransmitted on timeouts. Also if the remote SYN-ACK acknowledges
* only the SYN sequence, the data are retransmitted in the first ACK.
* If cookie is not cached or other error occurs, falls back to send a
* regular SYN with Fast Open cookie request option.
它会从内核 metrics 框架中查询是有目标 Server 对应的 Cookie,
若有,则直接填充到 SYN 报文中,若没有,则填充一个长度为 0 的Cookie 选项。
*/
static int tcp_send_syn_data(struct sock *sk, struct sk_buff *syn)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_fastopen_request *fo = tp->fastopen_req;
int space, err = 0;
struct sk_buff *syn_data;
tp->rx_opt.mss_clamp = tp->advmss; /* If MSS is not cached */
// 从 cache 中获取是否已有 Cookie,放入 fo->cookie
//
if (!tcp_fastopen_cookie_check(sk, &tp->rx_opt.mss_clamp, &fo->cookie))
goto fallback;
//-----
}
Server 收到带 TFO Cookie 请求的 SYN 报文
Server 收到带 TFO Cookie 请求的 SYN 报文, 调用tcp_try_fastopen()
,这里并不会直接创建 child 连接,原因是收到的 SYN 只带了 Cookie 请求,Server 随后会通过tcp_fastopen_cookie_gen()
创建有效的 Cookie,存入valid_foc
,最后用foc
带出去后组装 SYNACK 发送出去.
tcp_conn_request
|
|-- tcp_try_fastopen
|
|-- af_ops->send_synack(fastopen_sk, dst, &fl, req, &foc, false);
/* Returns true if we should perform Fast Open on the SYN. The cookie (foc)
* may be updated and return the client in the SYN-ACK later. E.g., Fast Open
* cookie request (foc->len == 0).
*/
struct sock *tcp_try_fastopen(struct sock *sk, struct sk_buff *skb,// sk 是 lisnter
struct request_sock *req,
struct tcp_fastopen_cookie *foc,
const struct dst_entry *dst)
{
bool syn_data = TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq + 1;
int tcp_fastopen = sock_net(sk)->ipv4.sysctl_tcp_fastopen;
struct tcp_fastopen_cookie valid_foc = { .len = -1 };
struct sock *child;
int ret = 0;
if (foc->len == 0) /* Client requests a cookie */
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPFASTOPENCOOKIEREQD);
if (!((tcp_fastopen & TFO_SERVER_ENABLE) &&
(syn_data || foc->len >= 0) &&
tcp_fastopen_queue_check(sk))) {
foc->len = -1;
return NULL;
}
if (syn_data &&
tcp_fastopen_no_cookie(sk, dst, TFO_SERVER_COOKIE_NOT_REQD))
goto fastopen;
if (foc->len == 0) {
/* Client requests a cookie. */
tcp_fastopen_cookie_gen(sk, req, skb, &valid_foc);
} else if (foc->len > 0) { /* Client presents or requests a cookie */
ret = tcp_fastopen_cookie_gen_check(sk, req, skb, foc,
&valid_foc);
if (!ret) {
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENPASSIVEFAIL);
} else {
/* Cookie is valid. Create a (full) child socket to
* accept the data in SYN before returning a SYN-ACK to
* ack the data. If we fail to create the socket, fall
* back and ack the ISN only but includes the same
* cookie.
*
* Note: Data-less SYN with valid cookie is allowed to
* send data in SYN_RECV state.
*/
fastopen:
child = tcp_fastopen_create_child(sk, skb, req);// Cookie 有效, 创建子连接
if (child) {
if (ret == 2) {
valid_foc.exp = foc->exp;
*foc = valid_foc;
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENPASSIVEALTKEY);
} else {
foc->len = -1;
}
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENPASSIVE);
return child;
}
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENPASSIVEFAIL);
}
}
valid_foc.exp = foc->exp;
*foc = valid_foc;
return NULL;
}
Clinet 收到带 TFO Cookie 的 SYNACK 报文
tcp_v4_do_rcv
|
|-- tcp_rcv_state_process
|
|-- tcp_rcv_synsent_state_process
|
|-- tcp_rcv_fastopen_synack
static bool tcp_rcv_fastopen_synack(struct sock *sk, struct sk_buff *synack,
struct tcp_fastopen_cookie *cookie)
{
// code omitted
/* 将 SYNACK 报文中的 Cookie 缓存起来(保存到 metrics 框架) */
tcp_fastopen_cache_set(sk, mss, cookie, syn_drop, try_exp);
// code omitted
}
Server 收到 Clinet 后续发起的新连接
Clinet 后续向 Server 发送的 SYN 请求会携带 Cookie,Server 收到后回立即创建子连接,同时设置为 TCP_NEW_SYN_RECV 状态,同时换新业务进程,告知有新的
之后收到 Clinet 的 ACK 后再在nsk = tcp_check_req(sk, skb, req, false, &req_stolen); ----> tcp_v4_syn_recv_sock 处理逻辑中更改为TCP_SYN_RECV ---->最后在 tcp_child_process(sk, nsk, skb) 将nsk设置为 ESTABLISHED 状态。
listen sk 收到syn 报文调用
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
tcp_v4_conn_request
tcp_conn_request
|
|-- child = tcp_try_fastopen
tcp_conn_request
{
if (!want_cookie) {
tcp_reqsk_record_syn(sk, req, skb);
fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst);
}
if (fastopen_sk) {
af_ops->send_synack(fastopen_sk, dst, &fl, req,
&foc, false);
/* Add the child socket directly into the accept queue */
inet_csk_reqsk_queue_add(sk, req, fastopen_sk);
sk->sk_data_ready(sk);// sock_def_readable
// ---------------------
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2023-04-03 freeradius 的bug处理