Skip to content

Latest commit

 

History

History
3093 lines (2513 loc) · 100 KB

File metadata and controls

3093 lines (2513 loc) · 100 KB

Linux TCP/IP 协议栈深度解析

基于 Linux Kernel 源码深度分析 路径:net/ipv4/ | include/linux/tcp.h | include/net/sock.h


目录

  1. TCP 在内核中的位置:socket 继承链
  2. tcp_sock 核心数据结构
  3. TCP 连接建立:三次握手内核实现
  4. TCP 状态机:11 个状态及转换
  5. 数据发送路径
  6. 数据接收路径
  7. 重传机制:RTO、RACK、SACK
  8. 拥塞控制框架:可插拔设计
  9. CUBIC 算法深度
  10. BBR 算法深度
  11. TCP 高性能优化
  12. TCP 调优参数
  13. net/ipv4/ 目录主要文件一览
  14. TCP 连接状态机完整实现深度剖析
  15. 拥塞控制算法框架深度实现
  16. TCP Fast Open 深度实现
  17. 接收缓冲区自动调整算法
  18. GRO 和 GSO 机制深度剖析
  19. TCP 重传机制深度:RTO 计算、快速重传、SACK
  20. TCP TIME_WAIT 处理:tw_reuse 实现
  21. SO_REUSEPORT 完整实现
  22. TCP Segmentation Offload 深度
  23. QUIC 在内核中的实现方向与现状

1. TCP 在内核中的位置:socket 继承链

Linux 网络栈的 socket 是一个层次分明的继承体系。每一层都在前一层的基础上添加协议所需的字段,通过 C 语言结构体内嵌(而非指针)实现零开销的"继承"。

用户空间
    |
    |  sys_socket() / sys_connect() / sys_send()
    |
  VFS 层
    |
+---v--------------+
|  struct socket   |  <-- include/linux/net.h
|  (VFS 层抽象)    |      socket.type, socket.state
|  .sk --> sock    |      .ops = inet_stream_ops
+---+---------------+
    |
    | (sk 指针,通过 sock->sk 访问)
    |
+---v--------------+          include/net/sock.h
| struct sock_common|  <-- 最小公共部分(timewait sock 也共用)
|  skc_daddr       |      目标 IP
|  skc_rcv_saddr   |      本地 IP
|  skc_dport       |      目标端口
|  skc_num         |      本地端口
|  skc_state       |      连接状态(TCP_ESTABLISHED 等)
|  skc_family      |      AF_INET / AF_INET6
|  skc_refcnt      |      引用计数
+------------------+
        |
        | (内嵌于 struct sock.__sk_common)
        |
+---v--------------+          include/net/sock.h
|  struct sock     |  <-- 所有协议通用 socket
|  sk_receive_queue|      接收队列(sk_buff 链表)
|  sk_write_queue  |      发送队列
|  tcp_rtx_queue   |      重传队列(红黑树)
|  sk_sndbuf       |      发送缓冲区大小
|  sk_rcvbuf       |      接收缓冲区大小
|  sk_pacing_rate  |      pacing 速率(BBR 使用)
|  sk_state        |      = skc_state
|  sk_lock         |      socket 锁
+------------------+
        |
        | (内嵌 struct inet_sock.sk)
        |
+---v--------------+          include/net/inet_sock.h:218
|  struct inet_sock|  <-- IPv4 专用扩展
|  inet_saddr      |      源 IP(alias skc_rcv_saddr)
|  inet_daddr      |      目标 IP(alias skc_daddr)
|  inet_sport      |      源端口
|  inet_dport      |      目标端口(alias skc_dport)
|  tos             |      Type of Service
|  uc_ttl          |      单播 TTL
|  inet_id         |      IP ID 计数器
+------------------+
        |
        | (内嵌 struct inet_connection_sock.icsk_inet)
        |
+---v--------------------+   include/net/inet_connection_sock.h:82
| struct inet_connection |
|       _sock            |  <-- 面向连接协议通用(TCP/SCTP)
|  icsk_accept_queue     |      accept 队列(含 SYN 队列)
|  icsk_delack_timer     |      延迟 ACK 定时器
|  icsk_keepalive_timer  |      keepalive 定时器
|  icsk_rto              |      当前 RTO(Retransmission Timeout)
|  icsk_rto_min          |      最小 RTO
|  icsk_ca_ops           |      拥塞控制算法指针
|  icsk_ca_state         |      CA 状态(Open/Disorder/CWR/Recovery/Loss)
|  icsk_retransmits      |      重传次数
|  icsk_backoff          |      退避指数
|  icsk_ca_priv[104/8]   |      拥塞控制算法私有数据(104 字节)
+------------------------+
        |
        | (内嵌 struct tcp_sock.inet_conn)
        |
+---v--------------------+   include/linux/tcp.h:197
|   struct tcp_sock      |  <-- TCP 专用,终极 socket
|  (见第 2 节详细展开)   |
+------------------------+

关键宏定义(类型安全转换):

  • tcp_sk(ptr)container_of(ptr, struct tcp_sock, inet_conn.icsk_inet.sk)net/ipv4/tcp.c 中频繁使用
  • inet_csk(ptr)container_of(ptr, struct inet_connection_sock, icsk_inet.sk)include/net/inet_connection_sock.h:152
  • inet_sk(ptr)container_of(ptr, struct inet_sock, sk)include/net/inet_sock.h:362

这种设计让同一个 struct sock * 指针可以被强制转型为任何一层的结构体,且开销为零(首字段内嵌,地址相同)。


2. tcp_sock 核心数据结构

struct tcp_sock 定义在 include/linux/tcp.h:197,是 Linux 内核中最复杂的结构体之一,约 200+ 字段。内核对其进行了精心的 Cache Line 分组优化。

2.1 Cache Line 分组布局

struct tcp_sock {
    struct inet_connection_sock inet_conn;   /* 必须是第一个成员 */

    /* ---- TX 只读热路径 Cache Line ---- */
    __cacheline_group_begin(tcp_sock_read_tx);
      u32  max_window;        /* 对端通告过的最大窗口 */
      u32  rcv_ssthresh;      /* 当前接收窗口钳位值 */
      u32  reordering;        /* 乱序度量 */
      u32  notsent_lowat;     /* TCP_NOTSENT_LOWAT setsockopt */
      u16  gso_segs;          /* 每 GSO 包最大段数 */
      struct sk_buff *retransmit_skb_hint; /* 重传队列扫描指针 */
    __cacheline_group_end(tcp_sock_read_tx);

    /* ---- TXRX 读写热路径 Cache Line ---- */
    __cacheline_group_begin(tcp_sock_read_txrx);
      u32  tsoffset;          /* 时间戳偏移 */
      u32  snd_wnd;           /* 当前发送窗口(对端通告) */
      u32  mss_cache;         /* 缓存的有效 MSS */
      u32  snd_cwnd;          /* 发送拥塞窗口(核心!) */
      u32  lost_out;          /* 判定丢失的包数 */
      u32  sacked_out;        /* SACK 确认的包数 */
    __cacheline_group_end(tcp_sock_read_txrx);

    /* ---- RX 只读热路径 Cache Line ---- */
    __cacheline_group_begin(tcp_sock_read_rx);
      u32  copied_seq;        /* 应用层已读到的序列号 */
      u32  tlp_high_seq;      /* TLP 发送时的 snd_nxt */
      u32  rttvar_us;         /* RTT 方差(平滑后) */
      u32  retrans_out;       /* 重传中的包数 */
      u32  snd_ssthresh;      /* 慢启动阈值 */
      struct minmax rtt_min;  /* 最小 RTT 窗口(RACK 使用) */
      struct rb_root out_of_order_queue; /* 乱序队列(红黑树) */
    __cacheline_group_end(tcp_sock_read_rx);

    /* ---- TX 读写热路径 Cache Line(独立 Cache Line 对齐) ---- */
    __cacheline_group_begin(tcp_sock_write_tx) ____cacheline_aligned;
      u32  write_seq;         /* 发送缓冲区尾序号 */
      u32  pushed_seq;        /* 已推入网络的序号 */
      u64  tcp_wstamp_ns;     /* 下一个包的 departure time(pacing) */
      struct list_head tsorted_sent_queue; /* 按时间排序的已发未确认队列 */
    __cacheline_group_end(tcp_sock_write_tx);

    /* ---- TXRX 读写热路径 Cache Line ---- */
    __cacheline_group_begin(tcp_sock_write_txrx);
      u32  rcv_nxt;           /* 期望收到的下一个序号 */
      u32  snd_nxt;           /* 下一个要发送的序号 */
      u32  snd_una;           /* 最早未被确认的序号 */
      u32  srtt_us;           /* 平滑 RTT(左移 3 位,单位 us) */
      u32  packets_out;       /* 在途(已发未确认)包数 */
      u32  rcv_wnd;           /* 当前接收窗口大小 */
      struct tcp_options_received rx_opt; /* PAWS/SACK/TS 协商结果 */
    __cacheline_group_end(tcp_sock_write_txrx);

2.2 序列号体系

发送方视角:
                snd_una      snd_nxt    write_seq
                   |             |          |
 ...................|..............|..........|.............
 (已确认)     (在途/未确认)    (已写入buf   (未写入)
                                 待发送)

接收方视角:
                copied_seq    rcv_nxt
                   |             |
 ...................|..............|.............
 (已复制到用户)  (内核已收到     (期望下一个)
                  待复制)

2.3 拥塞控制关键字段

字段 说明
snd_cwnd 拥塞窗口(单位:MSS 数),控制在途包上限
snd_ssthresh 慢启动阈值,低于此值使用慢启动,高于使用拥塞避免
snd_cwnd_cnt 拥塞避免中的 ACK 计数(Reno AIMD 使用)
prior_cwnd 进入丢失恢复前的 cwnd(PRR 算法使用)
prr_delivered Recovery 中新传包数(PRR)
prr_out Recovery 中已发总包数(PRR)

2.4 RACK 重传辅助结构

/* include/linux/tcp.h:379 */
struct tcp_rack {
    u64 mstamp;        /* 最近一次(重)传的时间戳 */
    u32 rtt_us;        /* 对应 RTT */
    u32 end_seq;       /* 发送包的结束序号 */
    u32 last_delivered;
    u8  reo_wnd_steps; /* 允许的乱序窗口大小 */
    u8  dsack_seen:1;
    u8  advanced:1;
} rack;

2.5 定时器

TCP 使用的定时器分布在两个结构中:

inet_connection_sock:
  icsk_delack_timer   --> 延迟 ACK(200ms 内合并 ACK)
  icsk_keepalive_timer --> Keepalive 探测

sock(通过 sock.tcp_retransmit_timer):
  tcp_retransmit_timer --> RTO 超时重传(由 icsk_pending 标志区分用途)
                          ICSK_TIME_RETRANS = 1  重传定时器
                          ICSK_TIME_PROBE0  = 3  零窗口探测
                          ICSK_TIME_LOSS_PROBE=5  TLP(Tail Loss Probe)
                          ICSK_TIME_REO_TIMEOUT=6 RACK 乱序超时

3. TCP 连接建立:三次握手内核实现

3.1 SYN Queue 与 Accept Queue

Linux 使用两段式队列设计,防止 SYN Flood 攻击:

客户端                    服务端内核
   |                          |
   |------- SYN ------------>|
   |                    +-----+------+
   |                    | SYN Queue  |  icsk_accept_queue.syn_table[]
   |                    | (半连接队列)|  大小 = /proc/sys/net/ipv4/tcp_max_syn_backlog
   |                    |   req_sock |  状态: TCP_NEW_SYN_RECV
   |<------ SYN+ACK ----|            |
   |                    +------------+
   |------- ACK ------------>|
   |                    +-----+------+
   |                    |Accept Queue|  icsk_accept_queue.rskq_accept_head
   |                    | (全连接队列)|  大小 = listen() 的 backlog 参数
   |                    |  sk (ESTAB)|  状态: TCP_ESTABLISHED
   |                    +------------+
   |                          |
   |                    app: accept()

3.2 服务端接收 SYN:tcp_conn_request()

入口函数:net/ipv4/tcp_input.c:7640 tcp_conn_request()

tcp_v4_rcv()                           net/ipv4/tcp_ipv4.c
  --> tcp_rcv_state_process()          net/ipv4/tcp_input.c
    --> tcp_conn_request()             net/ipv4/tcp_input.c:7640
          |
          |--> inet_reqsk_alloc()      分配 tcp_request_sock
          |       记录 rcv_isn(客户端 ISN)
          |       记录 snt_isn(服务端 ISN,随机生成)
          |
          |--> SYN Cookie 检查
          |    若 SYN Queue 满,启用 Cookie(无状态)
          |
          |--> tcp_v4_send_synack()    发送 SYN+ACK
          |       tcp_make_synack() 构造 SYN+ACK skb
          |
          +--> inet_csk_reqsk_queue_hash_add()  加入 SYN Queue

3.3 接收最终 ACK:完成三次握手

tcp_v4_rcv()
  --> tcp_check_req()                  net/ipv4/tcp_minisocks.c
        验证 ACK 序号和确认号
        |
        --> tcp_v4_syn_recv_sock()     创建子 socket(full socket)
              inet_csk_clone_lock()   克隆 listener socket
              tcp_create_openreq_child()
              |
              --> inet_csk_complete_hashdance()  从 SYN Queue 移除
              --> inet_csk_reqsk_queue_add()     加入 Accept Queue
                  子 socket 状态: TCP_ESTABLISHED

3.4 客户端连接建立:tcp_connect()

connect() syscall
  --> tcp_v4_connect()               net/ipv4/tcp_ipv4.c
        tcp_set_state(sk, TCP_SYN_SENT)
        tcp_connect(sk)
          --> tcp_send_syn_data()    或 tcp_send_syn()
                构造 SYN skb,设置 ISN(write_seq)
                tcp_transmit_skb()
                开启 RTO 定时器(等待 SYN+ACK)

3.5 TCP Fast Open(TFO)特殊路径

TFO 允许在 SYN 包中携带数据,减少一个 RTT 的连接建立开销。

普通连接:  SYN --> SYN+ACK --> ACK --> 数据     (1.5 RTT 才能传数据)
TFO 连接:  SYN+数据 --> SYN+ACK+数据              (0.5 RTT 即可收到响应数据)

实现: net/ipv4/tcp_fastopen.c
  服务端: tcp_try_fastopen() 验证 TFO Cookie
  客户端: tcp_sendmsg_fastopen() 在 connect 时携带数据

4. TCP 状态机:11 个状态及转换

4.1 状态定义

状态枚举定义在 include/net/tcp_states.h:12

enum {
    TCP_ESTABLISHED = 1,   /* 连接已建立 */
    TCP_SYN_SENT,          /* 已发送 SYN,等待 SYN+ACK */
    TCP_SYN_RECV,          /* 收到 SYN,已发 SYN+ACK(半连接) */
    TCP_FIN_WAIT1,         /* 发送了 FIN,等待对端 ACK */
    TCP_FIN_WAIT2,         /* 收到 ACK of FIN,等待对端 FIN */
    TCP_TIME_WAIT,         /* 等待确认对端已收到 ACK(2MSL) */
    TCP_CLOSE,             /* 连接已关闭 */
    TCP_CLOSE_WAIT,        /* 收到对端 FIN,等待本端关闭 */
    TCP_LAST_ACK,          /* 已发 FIN,等待对端最终 ACK */
    TCP_LISTEN,            /* 监听状态 */
    TCP_CLOSING,           /* 双方同时发 FIN(罕见) */
    TCP_NEW_SYN_RECV,      /* 内核内部:SYN Queue 中的请求 */
};

4.2 完整状态转换图

                         CLOSED
                           |
               listen()    |    connect()
                           |
            +--------------+------------------+
            |                                 |
            v                                 v
          LISTEN                           SYN_SENT
            |                                 |
   recv SYN |                      recv SYN+ACK|
   send SYN+ACK                    send ACK    |
            |                                 |
            v                                 v
         SYN_RECV <--- (simultaneous      ESTABLISHED <----------+
            |           open: both               |               |
   recv ACK |           send SYN)       close()/shutdown()       |
            |                           send FIN                 |
            v                                 |                  |
        ESTABLISHED                           v              recv FIN
            |                             FIN_WAIT1           send ACK
   close()  |                                 |                  |
   send FIN |                   recv FIN      |    recv ACK      |
            |                   send ACK      +----------------> FIN_WAIT2
            v                       |                            |
        CLOSE_WAIT                  v                 recv FIN   |
            |                   CLOSING               send ACK   |
   close()  |                       |                            v
   send FIN |               recv ACK|                        TIME_WAIT
            |                       |                  2*MSL timeout|
            v                       v                               |
         LAST_ACK               CLOSED <---------------------+------+
            |
   recv ACK |
            v
         CLOSED

4.3 状态转换在内核中的位置

转换 函数 文件
CLOSED -> LISTEN inet_listen() net/ipv4/af_inet.c
LISTEN -> SYN_RECV tcp_conn_request() net/ipv4/tcp_input.c:7640
SYN_RECV -> ESTABLISHED tcp_check_req() net/ipv4/tcp_minisocks.c
CLOSED -> SYN_SENT tcp_v4_connect() net/ipv4/tcp_ipv4.c
SYN_SENT -> ESTABLISHED tcp_rcv_synsent_state_process() net/ipv4/tcp_input.c
ESTABLISHED -> FIN_WAIT1 tcp_close() net/ipv4/tcp.c
FIN_WAIT1 -> TIME_WAIT tcp_time_wait() net/ipv4/tcp_minisocks.c

tcp_set_state() 是统一的状态切换函数,位于 include/net/tcp.h,内部会更新 sk->sk_state 并触发统计计数器。


5. 数据发送路径

5.1 全路径概览

应用层 write()/send()
        |
        v
  tcp_sendmsg()                net/ipv4/tcp.c:1460
        |
        v
  tcp_sendmsg_locked()         net/ipv4/tcp.c:1130
        |
        |--> 等待连接完成(sk_stream_wait_connect)
        |
        |--> 主循环:将用户数据拷贝到 sk_buff
        |      sk_stream_alloc_skb()   分配 skb
        |      skb_add_data_nocache()  零拷贝或 memcpy
        |      tcp_push()             触发发送
        |
        v
  tcp_push()
        |
        v
  tcp_write_xmit()             net/ipv4/tcp_output.c:2966
        |
        |--> tcp_cwnd_test()         检查拥塞窗口
        |--> tcp_snd_wnd_test()      检查接收窗口
        |--> tcp_nagle_test()        Nagle 算法检查
        |--> tcp_tso_segs()          计算 TSO 分段数
        |--> tso_fragment()          如有必要分片
        |
        v
  tcp_transmit_skb()           net/ipv4/tcp_output.c
        |
        |--> 构造 TCP 头(序号、ACK、窗口、选项)
        |--> icsk_af_ops->queue_xmit()  --> ip_queue_xmit()
        |
        v
  IP 层 --> 网络设备驱动

5.2 TSO/GSO 机制

TSO(TCP Segmentation Offload):将大 skb 的分段工作卸载到网卡。

内核构造大 skb(最大 64KB)
        |
        v
  tcp_write_xmit()
  设置 skb->gso_size = mss,gso_segs = N
        |
        v
  网卡驱动
        |
        +--> 若网卡支持 TSO:硬件自动切分 --> 发送 N 个 MSS 大小的包
        |
        +--> 若不支持:GSO 在软件层(net/core/gso.c)切分后发送

tcp_tso_autosize() 函数(net/ipv4/tcp_output.c:2235)根据 RTT 和 pacing rate 动态计算每次发送的 TSO 段数:

/* net/ipv4/tcp_output.c:2243 */
r = tcp_min_rtt(tcp_sk(sk)) >> sysctl_tcp_tso_rtt_log;
/* 用 RTT 来决定 TSO 聚合窗口大小,RTT 越小,TSO 段数越小 */

5.3 Nagle 算法

tcp_nagle_test() 实现 Nagle 算法(net/ipv4/tcp_output.c):

规则:若满足以下任一条件,允许发送小包:
  1. 发送队列为空(no packets in flight)
  2. 数据量 >= MSS(full-size segment)
  3. 设置了 TCP_NODELAY(禁用 Nagle)
  4. 设置了 TCP_CORK 但超时

nonagle 字段控制:
  tp->nonagle & TCP_NAGLE_OFF   -- TCP_NODELAY 设置时
  tp->nonagle & TCP_NAGLE_CORK  -- TCP_CORK 设置时
  tp->nonagle & TCP_NAGLE_PUSH  -- 强制推送

6. 数据接收路径

6.1 全路径概览

网卡中断
        |
        v
  驱动 NAPI poll
        |
        v
  netif_receive_skb()          网络核心层
        |
  GRO(Generic Receive Offload)合并小包
        |
        v
  ip_rcv() --> ip_local_deliver()
        |
        v
  tcp_v4_rcv()                 net/ipv4/tcp_ipv4.c
        |
        |--> 查找 socket(ehash 或 bhash 哈希表)
        |--> tcp_filter()      BPF/socket filter
        |
        v
  tcp_rcv_established()        net/ipv4/tcp_input.c:6519
        |
        |--> 头部预测(Fast Path)
        |    pred_flags 匹配 --> 最快路径
        |
        |--> Slow Path:
        |    tcp_data_queue()  数据入接收队列
        |    tcp_ack()         处理 ACK,更新 snd_una
        |    tcp_check_ofo_queue() 乱序重组
        |
        v
  tcp_rcv_space_adjust()       动态调整接收缓冲区
        |
  recvmsg() / read()
        |
  tcp_recvmsg()                net/ipv4/tcp.c
        |
        +--> skb_copy_datagram_msg()  数据从内核拷贝到用户空间

6.2 头部预测(Header Prediction)

tcp_rcv_established() 的核心优化,源自 Van Jacobson 1988 年的论文:

/* net/ipv4/tcp_input.c:6559 */
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
    TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
    !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
    /* Fast Path: 包按序到达,无特殊标志 */
    ...
}

pred_flags = 0x5?10 << 16 + snd_wnd,编码了期望的 TCP 头部特征。命中率高时无需完整解析 TCP 选项。

6.3 乱序队列(OOO Queue)

乱序包通过红黑树 out_of_order_queue 管理,按 seq 排序:

out_of_order_queue(红黑树):
        [seq=500-599]
       /             \
 [seq=300-399]   [seq=700-799]

当 rcv_nxt 到达缺口时,tcp_ofo_queue() 将有序段移入接收队列

tcp_rbtree_insert()net/ipv4/tcp_output.c:70 同样用于管理重传队列 tcp_rtx_queue

6.4 GRO(Generic Receive Offload)

GRO 是软件层的 LRO(Large Receive Offload),在 softirq 中将多个小 TCP 包合并为一个大包,减少协议栈处理次数:

多个小包(MSS 大小): [pkt1][pkt2][pkt3][pkt4]
       |
   GRO 合并(按连接聚合)
       |
  一个大 skb(4*MSS)
       |
  tcp_rcv_established()  一次调用处理 4 个包的数据

7. 重传机制:RTO、RACK、SACK

7.1 RTT 估算:Jacobson 算法

实现在 net/ipv4/tcp_input.c:1071tcp_rtt_estimator() 函数:

SRTT(平滑 RTT)更新:
  err = mrtt - (srtt >> 3)        /* 误差(新测量值 - 当前估计/8) */
  srtt = srtt + err               /* 等价于 srtt = 7/8*srtt + 1/8*mrtt */
  tp->srtt_us = max(1U, srtt)     /* srtt_us 是左移 3 位的,单位 us */

RTTVAR(RTT 方差)更新:
  mdev = 3/4 * mdev + 1/4 * |err|
  tp->rttvar_us = max(mdev_max, tcp_rto_min_us)

RTO 计算(RFC 6298):
  RTO = srtt/8 + 4 * rttvar
      = (tp->srtt_us >> 3) + tp->rttvar_us
  [TCP_RTO_MIN = 200ms, TCP_RTO_MAX = 120s]
  icsk->icsk_rto = 指数退避(每次翻倍,直到 MAX)

7.2 RACK(Recent ACK)算法

RACK 是 Linux 4.5+ 引入的丢包检测算法,比传统 dupACK 计数更准确:

核心思想:
  若一个包的发送时间比某已确认包早 RTT+reorder_window,
  且该包仍未被确认,则判定为丢失。

  tcp_rack.mstamp:  最近一次成功送达包的发送时间戳
  tcp_rack.rtt_us:  对应 RTT

检测流程(net/ipv4/tcp_recovery.c):
  tcp_rack_mark_lost()          主入口
    --> tcp_rack_detect_loss()  遍历未确认包
          tcp_rack_skb_timeout() 计算每个包的超时时间
          timeout = rack.mstamp + rack.rtt_us + reo_wnd
          若 skb.mstamp + timeout < now --> 标记丢失

优势:
  - 对乱序友好(可配置 reo_wnd)
  - 不依赖 dupACK 计数(对无序 ACK 鲁棒)
  - 结合 DSACK 动态调整乱序容忍度

7.3 SACK 处理

SACK(Selective ACK)允许接收方通知发送方哪些段已收到,避免重传已收到的数据:

/* include/linux/tcp.h:102 */
struct tcp_sack_block {
    u32 start_seq;
    u32 end_seq;
};

/* tcp_sock 中 */
struct tcp_sack_block selective_acks[4];      /* 发送给对端的 SACK 块 */
struct tcp_sack_block recv_sack_cache[4];     /* 收到的 SACK 块缓存 */
struct tcp_sack_block duplicate_sack[1];      /* D-SACK 块 */

SACK 处理流程:

收到 ACK with SACK options
        |
        v
  tcp_sacktag_write_queue()    net/ipv4/tcp_input.c
        |
        |--> 遍历重传队列(tcp_rtx_queue,红黑树)
        |--> 标记 SACK'd 包:TCP_SKB_CB(skb)->sacked |= TCPCB_SACKED_ACKED
        |--> 更新 sacked_out 计数
        |
        v
  tcp_fastretrans_alert()
        |
        |--> 若 lost_out > 0:进入快速重传
        |--> tcp_enter_recovery():切换 CA 状态到 TCP_CA_Recovery
        |--> tcp_retransmit_skb():重传标记丢失的包

7.4 定时器链:超时重传

RTO 超时触发:
  tcp_retransmit_timer()        net/ipv4/tcp_timer.c
        |
        |--> tcp_write_timeout() 检查是否超过最大重试次数
        |    sysctl_tcp_retries1 = 3  (触发路由更新)
        |    sysctl_tcp_retries2 = 15 (放弃连接,约 15 分钟)
        |
        |--> tcp_retransmit_skb()  重传队首包
        |--> icsk->icsk_backoff++  退避指数++
        |--> icsk_rto <<= 1        RTO 翻倍(指数退避)
        |--> 重新启动定时器

TLP(Tail Loss Probe,尾部丢失探测):
  在快速重传之前,通过发送 probe 包提前发现尾部丢失
  避免 RTO 超时的长延迟

8. 拥塞控制框架:可插拔设计

8.1 核心接口结构

struct tcp_congestion_ops 定义在 include/net/tcp.h:1275

struct tcp_congestion_ops {
    /* 快路径字段(放在第一个 cache line) */

    /* (a) 经典模式:只需实现 cong_avoid */
    void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);

    /* (b) 自定义模式:完全控制(BBR 使用此接口) */
    void (*cong_control)(struct sock *sk, u32 ack, int flag,
                         const struct rate_sample *rs);

    /* 必须实现:计算新的慢启动阈值 */
    u32  (*ssthresh)(struct sock *sk);

    /* 可选:撤销减窗(网络条件好转时恢复 cwnd) */
    u32  (*undo_cwnd)(struct sock *sk);

    /* 可选:状态变更通知 */
    void (*set_state)(struct sock *sk, u8 new_state);

    /* 可选:cwnd 事件(TX_START、CWND_RESTART 等) */
    void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);

    /* 可选:每次 ACK 到达回调 */
    void (*in_ack_event)(struct sock *sk, u32 flags);

    /* 可选:包确认统计(BBR 使用) */
    void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample);

    /* 慢路径字段 */
    void (*init)(struct sock *sk);     /* 初始化私有数据 */
    void (*release)(struct sock *sk);  /* 释放私有数据 */
    char name[TCP_CA_NAME_MAX];
    u32  flags;   /* TCP_CONG_NON_RESTRICTED / TCP_CONG_NEEDS_ECN */
} ____cacheline_aligned_in_smp;

8.2 注册与查找机制

/* net/ipv4/tcp_cong.c:22 */
static LIST_HEAD(tcp_cong_list);    /* 全局算法链表 */

/* 注册 */
tcp_register_congestion_control(ops);  /* 加入链表,用 jhash 生成 key */

/* 查找 */
tcp_ca_find("cubic");              /* 按名查找 */
tcp_ca_find_key(key);              /* 按 hash key 查找 */

/* 分配给连接 */
tcp_assign_congestion_control(sk); /* net/ipv4/tcp_cong.c:216 */
  --> ca = rcu_dereference(net->ipv4.tcp_congestion_control);
  --> icsk->icsk_ca_ops = ca;
  --> memset(icsk->icsk_ca_priv, 0, 104);  /* 清零私有数据区 */

8.3 CA 状态机(5 个状态)

TCP_CA_Open      -- 正常,无丢包
TCP_CA_Disorder  -- 检测到 SACK 或 dupACK,但还未确认丢包
TCP_CA_CWR       -- ECN 拥塞通知,正在减速
TCP_CA_Recovery  -- 快速重传/恢复中
TCP_CA_Loss      -- RTO 超时,慢启动恢复

转换触发:
  tcp_fastretrans_alert()  --> 进入 Recovery
  tcp_enter_loss()         --> 进入 Loss
  tcp_try_undo_recovery()  --> 退出 Recovery -> Open

8.4 默认算法选择与切换

# 查看可用算法
cat /proc/sys/net/ipv4/tcp_available_congestion_control
# cubic reno bbr

# 修改默认算法
echo "bbr" > /proc/sys/net/ipv4/tcp_congestion_control

# 单连接指定算法(setsockopt)
setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "bbr", strlen("bbr"));

9. CUBIC 算法深度

CUBIC 是 Linux 默认拥塞控制算法(自 Linux 2.6.19),实现在 net/ipv4/tcp_cubic.c

9.1 CUBIC 私有状态

/* net/ipv4/tcp_cubic.c:86 */
struct bictcp {
    u32 cnt;              /* cwnd 增长计数器:每 cnt 个 ACK 增加 1 */
    u32 last_max_cwnd;    /* 最近一次丢包前的最大 cwnd(W_max) */
    u32 bic_origin_point; /* 当前 epoch 的原点 cwnd */
    u32 bic_K;            /* 到达 W_max 需要的时间(单位 BICTCP_HZ) */
    u32 delay_min;        /* 测量到的最小 RTT(usec) */
    u32 epoch_start;      /* 当前 epoch 开始时间 */
    u32 ack_cnt;          /* epoch 内 ACK 计数 */
    u32 tcp_cwnd;         /* TCP-friendly 估算的 cwnd */
    /* HyStart 字段 */
    u32 round_start;      /* 当前轮次开始时间 */
    u32 end_seq;          /* 轮次结束序号 */
    u32 curr_rtt;         /* 当前轮次最小 RTT */
};

9.2 CUBIC 函数:W(t)

CUBIC 的核心是一个三次函数,在时间维度(而非 ACK 数维度)上增长:

W(t) = C * (t - K)^3 + W_max

其中:
  W_max = 丢包前的 cwnd(last_max_cwnd)
  C     = bic_scale / 1024 = 41/1024 ≈ 0.04(默认值)
  K     = 3_sqrt( W_max * (1-beta) / C )
        = cubic_root(cube_factor * (W_max - cwnd_now))
  t     = 从本 epoch 开始经过的时间(秒)

beta  = 717/1024 ≈ 0.7(乘性减小因子)
cwnd
  |                                          /
  |                                      /
W_max|. . . . . . . . . . . . . . .* . ./. .  <-- 达到 W_max
  |                            * /
  |  丢包                   * /
  |    \               K^3 * /       <-- CUBIC 函数加速区
  |     \           * /
beta*W_max|. . . .*/ . . . . . . . . . . . .  <-- 丢包后减到 beta*W_max
  |     /|
  |    / |
  |   /  |
  +--K---+-----------------------------------------> t(时间)
       epoch start

9.3 bictcp_update():cwnd 增长计算

/* net/ipv4/tcp_cubic.c:214 */
static void bictcp_update(struct bictcp *ca, u32 cwnd, u32 acked)
{
    /* 计算时间 t = jiffies - epoch_start(转换单位为 BICTCP_HZ) */
    t = (s32)(tcp_jiffies32 - ca->epoch_start) + ...;

    /* 计算 |t - K| */
    offs = (t < ca->bic_K) ? (ca->bic_K - t) : (t - ca->bic_K);

    /* cubic 目标: C/rtt * (t-K)^3 */
    delta = (cube_rtt_scale * offs * offs * offs) >> (10 + 3*BICTCP_HZ);

    if (t < ca->bic_K)   /* t < K:仍在 W_max 以下,concave 阶段 */
        bic_target = ca->bic_origin_point - delta;
    else                  /* t > K:超过 W_max,convex 阶段 */
        bic_target = ca->bic_origin_point + delta;

    /* 转换为每 N 个 ACK 增加 1 MSS 的速率 */
    ca->cnt = cwnd / (bic_target - cwnd);
}

9.4 快速收敛(Fast Convergence)

当检测到网络中有新流时,主动降低 W_max,加快向公平点收敛:

/* net/ipv4/tcp_cubic.c:349 */
if (tcp_snd_cwnd(tp) < ca->last_max_cwnd && fast_convergence)
    /* 新流竞争:W_max 不是真实瓶颈,提前降低 */
    ca->last_max_cwnd = (tcp_snd_cwnd(tp) * (BICTCP_BETA_SCALE + beta))
                        / (2 * BICTCP_BETA_SCALE);
else
    ca->last_max_cwnd = tcp_snd_cwnd(tp);  /* 记录当前 cwnd 为 W_max */

9.5 HyStart:混合慢启动

HyStart 用于在慢启动阶段更早退出,避免过冲(overshooting):

触发条件(任一):
  1. ACK Train(包列探测):相邻 ACK 间隔 < hystart_ack_delta_us(2ms)
     表示链路无排队,可能快到瓶颈了
  2. Delay Increase(时延增加探测):当前轮 RTT 比 delay_min 增加超过
     HYSTART_DELAY_THRESH(4ms ~ 16ms)

触发后:退出慢启动,设置 ssthresh = cwnd(进入拥塞避免)

10. BBR 算法深度

BBR(Bottleneck Bandwidth and RTprop)由 Google 于 2016 年提出,实现在 net/ipv4/tcp_bbr.c,是一种基于带宽-延迟模型的拥塞控制,而非基于丢包。

10.1 BBR 核心模型

两个关键变量:
  BtlBw(Bottleneck Bandwidth):瓶颈带宽
    = windowed_max(delivered / elapsed, 10 RTTs)
    用 minmax 滑动窗口维护

  RTprop(Round-trip propagation delay):传播延迟
    = windowed_min(rtt, 10 seconds)
    用 min_rtt_us 字段维护

发送策略:
  pacing_rate = pacing_gain * BtlBw
  cwnd        = cwnd_gain   * BtlBw * RTprop

10.2 BBR 四阶段状态机

/* net/ipv4/tcp_bbr.c:81 */
enum bbr_mode {
    BBR_STARTUP,    /* 指数增长,探测带宽上限 */
    BBR_DRAIN,      /* 排空启动阶段产生的队列 */
    BBR_PROBE_BW,   /* 稳态:周期性探测带宽,控制排队 */
    BBR_PROBE_RTT,  /* 降低 inflight 至最小,重新测量 RTprop */
};
状态转换:

    STARTUP
      |  3 轮带宽不再增长 25%(full_bw_reached)
      v
    DRAIN
      |  inflight <= BDP(Bandwidth-Delay Product)
      v
    PROBE_BW <-+
      |  10 秒未见更小 RTT  |(probe 完成)
      v                     |
    PROBE_RTT  --------------+

PROBE_BW 增益周期bbr_pacing_gain[]net/ipv4/tcp_bbr.c:162):

8 个阶段的 pacing_gain 值:
  [5/4, 3/4, 1, 1, 1, 1, 1, 1]

周期1:pacing_gain=1.25  超速发送,探测是否有更多带宽
周期2:pacing_gain=0.75  降速,排空前一周期可能产生的队列
周期3-8:pacing_gain=1.0 巡航,以估算带宽发送

10.3 带宽估算

/* net/ipv4/tcp_bbr.c:214 */
static u32 bbr_max_bw(const struct sock *sk)
{
    struct bbr *bbr = inet_csk_ca(sk);
    return minmax_get(&bbr->bw);  /* 10 RTT 滑动最大值 */
}

带宽样本来自 rate_sample 结构(由 tcp_rate.c 维护):

每次 ACK 时:
  delivered_rate = (delivered - prev_delivered) / elapsed_time
  minmax_running_max(&bbr->bw, bbr_bw_rtts, ...)  /* 10轮次最大值 */

10.4 pacing rate 设置

/* net/ipv4/tcp_bbr.c:256 */
static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain)
{
    unsigned long rate = bbr_bw_to_pacing_rate(sk, bw, gain);
    /* 写入 sk->sk_pacing_rate,由 fq qdisc 或内部 hrtimer 控制发送速率 */
    WRITE_ONCE(sk->sk_pacing_rate, rate);
}

10.5 BBR vs CUBIC 对比

维度         CUBIC                    BBR
-----------  -----------------------  -----------------------
丢包响应     丢包立即减半 cwnd        不直接响应丢包
带宽利用     可能高(但会造成排队)   接近理论最优
队列延迟     高(buffer bloat)       低(主动控制)
公平性       与 CUBIC 流公平          与 CUBIC 流共存时可能占优
适用场景     高 BDP、低误码率链路     卫星链路、无线(有随机丢包)
内核支持     默认算法                 需编译或 sysctl 开启

11. TCP 高性能优化

11.1 TCP_NODELAY:禁用 Nagle 算法

/* 使用场景:延迟敏感的交互式应用(SSH、游戏、RPC) */
int flag = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

/* 内核实现:net/ipv4/tcp.c */
tp->nonagle |= TCP_NAGLE_OFF;
/* tcp_nagle_test() 直接放行小包 */

11.2 Zero-copy sendfile

sendfile() 系统调用完全在内核中完成文件数据到 TCP 的传输,用户空间无数据拷贝:

传统 send():                    sendfile()(零拷贝):
  磁盘 --> 内核页缓存              磁盘 --> 内核页缓存
  --> 用户空间缓冲区                      |
  --> 内核 socket 缓冲区                  |--> DMA 直接到网卡
  --> DMA 到网卡                         (两次 DMA + 零 CPU 拷贝)
(四次拷贝,两次 CPU 拷贝)

内核实现:net/ipv4/tcp.ctcp_sendpage()do_tcp_sendpages() → 使用 skb_add_data() 添加页面引用(不拷贝)。

MSG_ZEROCOPY(Linux 4.14+)进一步支持用户态缓冲区的零拷贝:

/* 需先设置 SO_ZEROCOPY */
setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &one, sizeof(one));
/* 发送时指定 MSG_ZEROCOPY */
send(fd, buf, len, MSG_ZEROCOPY);
/* 通过错误队列得到完成通知 */
recvmsg(fd, &msg, MSG_ERRQUEUE);

11.3 TCP Fast Open(TFO)

减少连接建立的 RTT 开销:

/* net/ipv4/tcp_fastopen.c */
/* 服务端启用 TFO */
int qlen = 1024;
setsockopt(listenfd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen));
/* 或全局开启 */
/* echo 3 > /proc/sys/net/ipv4/tcp_fastopen */

/* 客户端发送 TFO 请求 */
sendto(fd, data, len, MSG_FASTOPEN, ...);
/* 等价于 connect() + send() 合并为一次 SYN+DATA */

TFO Cookie 机制防止 SYN 放大攻击(tcp_fastopen_init_key_once()net/ipv4/tcp_fastopen.c:94)。

11.4 SO_REUSEPORT:多进程/线程监听

允许多个 socket 绑定同一端口,内核对入连接进行负载均衡:

                     新连接
                        |
                 内核哈希分发(4-tuple hash)
                   /      |      \
            worker0   worker1   worker2   ...
            fd0       fd1       fd2
            (同一端口的不同 socket)

优点:
  - 消除 accept() 锁竞争
  - 每个 worker 有独立 accept queue
  - CPU 亲和性更好
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
/* 配合 BPF 可实现自定义分发策略 */

11.5 TCP 缓冲区自动调整

内核动态调整 TCP 缓冲区大小(tcp_rcv_space_adjust()):

接收侧自动调整:
  rcvq_space.space 初始 = 87380 字节
  每次 RTT 测量 throughput = bytes_received / rtt
  若 throughput > space/2,翻倍 space(上限 tcp_rmem[2])
  调用 tcp_set_rcvlowat()

发送侧自动调整:
  sk_sndbuf 在 tcp_sndbuf_expand() 中动态扩展
  上限 = tcp_wmem[2]

11.6 TCP 多路径(MPTCP)

Linux 5.6+ 原生支持 MPTCP,允许连接使用多个网络路径:

应用层看到一个普通 TCP socket
  --> MPTCP 层(net/mptcp/)透明地管理多个子流
  --> 每个子流是一个标准 TCP 连接

12. TCP 调优参数

12.1 核心缓冲区参数

参数 默认值 说明
tcp_rmem 4096 131072 6291456 接收缓冲区 [最小, 默认, 最大](字节)
tcp_wmem 4096 16384 4194304 发送缓冲区 [最小, 默认, 最大](字节)
tcp_mem 内核计算 TCP 全局内存限制 [最小, 压力, 最大](页)
# 为 10GbE 高吞吐优化(BDP = 10Gbps * 50ms = 62.5MB)
echo "4096 87380 67108864" > /proc/sys/net/ipv4/tcp_rmem
echo "4096 65536 67108864" > /proc/sys/net/ipv4/tcp_wmem

12.2 连接队列参数

参数 默认值 说明
tcp_max_syn_backlog 128~1024 SYN Queue(半连接队列)最大长度
somaxconn 128 Accept Queue 最大长度(全局上限)
tcp_syn_retries 6 SYN 重试次数(约 127 秒超时)
tcp_synack_retries 5 SYN+ACK 重试次数(服务端)

12.3 重传与超时参数

参数 默认值 说明
tcp_retries1 3 触发路由更新的重传次数
tcp_retries2 15 放弃连接的最大重传次数(约 15 分钟)
tcp_orphan_retries 0 孤儿 socket 重传次数(0=8)
tcp_fin_timeout 60 FIN_WAIT2 超时时间(秒)
tcp_tw_reuse 2 TIME_WAIT socket 复用(1=开启,需 timestamps)

12.4 拥塞控制参数

参数 默认值 说明
tcp_congestion_control cubic 默认拥塞控制算法
tcp_slow_start_after_idle 1 空闲后重新慢启动
tcp_early_retrans 3 早期重传(减少 dupACK 等待)
tcp_recovery 1 RACK/TLP 开关
tcp_sack 1 SACK 开关
tcp_timestamps 1 TCP 时间戳开关(PAWS 和 RTT 测量依赖)

12.5 Fast Open 参数

参数 说明
tcp_fastopen 0 禁用
1 客户端启用
2 服务端启用
3 双端启用
tcp_fastopen_blackhole_timeout_sec 3600 TFO 黑洞检测超时

12.6 性能优化参数

参数 默认值 说明
tcp_no_metrics_save 0 不保存路由缓存的 TCP 指标
tcp_moderate_rcvbuf 1 自动调整接收缓冲区
tcp_tso_win_divisor 3 TSO 发送窗口除数(影响 TSO 聚合)
tcp_min_tso_segs 2 TSO 最小段数
# 查看当前所有 TCP 参数
sysctl -a | grep net.ipv4.tcp_

# 高性能服务器推荐配置
sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.ipv4.tcp_congestion_control=bbr

13. net/ipv4/ 目录主要文件一览

net/ipv4/
|
|-- tcp.c              # TCP 主体:sendmsg/recvmsg/setsockopt/getsockopt
|                      # tcp_sendmsg_locked()  发送入口
|                      # tcp_recvmsg()         接收入口
|                      # tcp_close()           关闭连接
|
|-- tcp_input.c        # TCP 接收路径(最大单文件之一,~7700 行)
|                      # tcp_rcv_established() 已建立连接的接收处理
|                      # tcp_rcv_state_process() 状态机驱动
|                      # tcp_ack()             ACK 处理
|                      # tcp_data_queue()      数据入队
|                      # tcp_conn_request()    服务端 SYN 处理
|                      # tcp_rtt_estimator()   RTT 估算
|
|-- tcp_output.c       # TCP 发送路径(~4200 行)
|                      # tcp_write_xmit()      核心发送循环
|                      # tcp_transmit_skb()    底层发送(构造 TCP 头)
|                      # tso_fragment()        TSO 分片
|                      # tcp_make_synack()     SYN+ACK 构造
|
|-- tcp_timer.c        # 定时器处理
|                      # tcp_retransmit_timer() RTO 超时
|                      # tcp_delack_timer()     延迟 ACK
|                      # tcp_keepalive_timer()  Keepalive
|
|-- tcp_cong.c         # 拥塞控制框架
|                      # tcp_register/unregister_congestion_control()
|                      # tcp_assign_congestion_control()
|
|-- tcp_cubic.c        # CUBIC 算法实现(默认算法)
|-- tcp_bbr.c          # BBR 算法实现(Google,v1)
|-- tcp_reno.c         # Reno(基础算法,新 CA 的参考实现)
|-- tcp_bic.c          # BIC(CUBIC 前身)
|-- tcp_dctcp.c        # DCTCP(数据中心 TCP,基于 ECN)
|-- tcp_vegas.c        # Vegas(基于延迟)
|-- tcp_htcp.c         # H-TCP
|-- tcp_hybla.c        # Hybla(卫星链路优化)
|
|-- tcp_recovery.c     # RACK 丢包检测
|                      # tcp_rack_mark_lost()
|                      # tcp_rack_reo_timeout()
|
|-- tcp_minisocks.c    # TIME_WAIT/SYN_RECV 轻量 socket
|                      # tcp_time_wait()
|                      # tcp_check_req()       三次握手第三步
|
|-- tcp_fastopen.c     # TCP Fast Open 实现
|                      # tcp_try_fastopen()
|                      # reqsk_fastopen_remove()
|
|-- tcp_ipv4.c         # IPv4 特定实现
|                      # tcp_v4_rcv()          IPv4 入口
|                      # tcp_v4_connect()      客户端连接
|                      # tcp_v4_send_synack()  发送 SYN+ACK
|                      # tcp_v4_send_reset()   发送 RST
|
|-- tcp_offload.c      # GSO/GRO 卸载支持
|-- tcp_metrics.c      # TCP 路由度量缓存(记录历史 RTT/CWND)
|-- tcp_diag.c         # inet_diag 接口(ss/netstat 使用)
|-- tcp_bpf.c          # BPF sock ops 支持
|-- tcp_ulp.c          # Upper Layer Protocol 支持(TLS/KCM)
|-- tcp_ao.c           # TCP-AO(Authentication Option)
|-- tcp_plb.c          # PLB(Protective Load Balancing)
|
|-- ip_output.c        # IP 层发送(tcp_transmit_skb 的下一站)
|-- ip_input.c         # IP 层接收(tcp_v4_rcv 的上一站)

关键调用链总结

发送路径:
  write() --> tcp_sendmsg() --> tcp_write_xmit() --> tcp_transmit_skb()
           --> ip_queue_xmit() --> ip_output() --> dev_queue_xmit()

接收路径:
  网卡中断 --> napi_poll() --> netif_receive_skb() --> ip_rcv()
            --> tcp_v4_rcv() --> tcp_rcv_established() --> tcp_data_queue()
            --> read() --> tcp_recvmsg() --> 用户空间

握手路径:
  服务端: tcp_v4_rcv() --> tcp_conn_request() --> [SYN Queue]
          --> tcp_check_req() --> [Accept Queue] --> accept()
  客户端: tcp_v4_connect() --> tcp_connect() --> [RTO Timer]
          --> tcp_rcv_synsent_state_process() --> ESTABLISHED

14. TCP 连接状态机完整实现深度剖析

14.1 tcp_rcv_state_process() 状态机驱动

tcp_rcv_state_process() 是非 ESTABLISHED 状态下的核心驱动函数,位于 net/ipv4/tcp_input.c。它处理除 ESTABLISHED 和 TIME_WAIT 之外所有状态下收到的报文。

tcp_v4_rcv()
    |
    +--> sk->sk_state == TCP_ESTABLISHED?
    |       是 --> tcp_rcv_established()    快速路径
    |       否 --> tcp_rcv_state_process()  慢速路径(状态机)
    |
    +--> sk->sk_state == TCP_TIME_WAIT?
            是 --> tcp_timewait_state_process()

状态机函数内部的 switch-case 分发结构:

/* net/ipv4/tcp_input.c (简化) */
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
    switch (sk->sk_state) {
    case TCP_CLOSE:
        /* 关闭状态,忽略或发 RST */
        goto discard;

    case TCP_LISTEN:
        /* 监听状态:仅接受 SYN */
        if (th->syn) {
            acceptable = icsk->icsk_af_ops->conn_request(sk, skb);
            /* conn_request = tcp_v4_conn_request
               --> tcp_conn_request() */
        }
        goto discard;

    case TCP_SYN_SENT:
        /* 客户端等待 SYN+ACK */
        tp->rx_opt.saw_tstamp = 0;
        queued = tcp_rcv_synsent_state_process(sk, skb, th);
        /* 验证 SYN+ACK,发 ACK,进入 ESTABLISHED */
        break;

    case TCP_SYN_RECV:
        /* 服务端收到最终 ACK(已在 tcp_check_req 处理)
           实际上内核使用 TCP_NEW_SYN_RECV 表示 request_sock */
        break;

    case TCP_LAST_ACK:
        /* 收到对端对 FIN 的 ACK */
        if (tp->snd_una == tp->write_seq) {
            tcp_update_metrics(sk);
            tcp_done(sk);   /* 进入 CLOSED */
        }
        break;

    case TCP_FIN_WAIT1:
        /* 处理 FIN 或 FIN+ACK */
        ...
        break;
    }
}

14.2 SYN_SENT 状态处理:tcp_rcv_synsent_state_process()

当客户端处于 SYN_SENT 状态并收到服务端 SYN+ACK 时:

tcp_rcv_synsent_state_process()     net/ipv4/tcp_input.c
    |
    |--> 验证 ACK(确认号 == ISN+1)
    |--> 验证 SYN 标志
    |
    |--> tcp_parse_options()         解析 TCP 选项
    |       MSS 协商
    |       窗口缩放因子
    |       SACK 是否支持
    |       时间戳(PAWS)
    |
    |--> tcp_ack()                   处理 ACK,确认 SYN 已被接收
    |
    |--> tcp_set_state(sk, TCP_ESTABLISHED)
    |
    |--> tcp_send_ack()              发送第三次握手 ACK
    |
    +--> sk->sk_state_change(sk)     唤醒 connect() 调用者

14.3 四次挥手的内核实现路径

四次挥手中最复杂的是主动关闭方的状态处理:

主动关闭方(A):
                              被动关闭方(B):
ESTABLISHED                   ESTABLISHED
    |                              |
    | close()/shutdown()           |
    v                              |
FIN_WAIT1 -------- FIN ---------->|
    |                         CLOSE_WAIT
    |<------- ACK ------------    |
FIN_WAIT2                         | close()
    |                             v
    |<------- FIN ------------LAST_ACK
    | send ACK                    |
    v                             |
TIME_WAIT                     CLOSED
    | 2*MSL(60s)
    v
CLOSED

内核中 FIN 的发送路径:

/* net/ipv4/tcp.c */
void tcp_close(struct sock *sk, long timeout)
{
    /* ... */
    tcp_set_state(sk, TCP_FIN_WAIT1);
    /* 构造 FIN 包 */
    tcp_send_fin(sk);        /* 将 FIN 追加到发送队列 */
    /* ... */
}

14.4 tcp_rcv_established() 快速路径详解

已建立连接的数据接收是 TCP 最热的路径,tcp_rcv_established() 实现了极致优化:

tcp_rcv_established()                   net/ipv4/tcp_input.c:6519
    |
    |-- 【头部预测 Fast Path】
    |   条件:
    |     - pred_flags 匹配(无 URG/SYN/FIN/RST 等特殊标志)
    |     - seq == rcv_nxt(按序到达)
    |     - ack_seq <= snd_nxt(ACK 不超前)
    |
    |   Fast Path 分两支:
    |   [a] 纯 ACK(数据长度为0):
    |         tcp_ack()
    |         直接返回,不进入慢路径
    |   [b] 数据包(按序):
    |         tcp_rcv_rtt_measure_ts()  时间戳测 RTT
    |         __skb_pull()              去掉 TCP 头
    |         tcp_rcv_space_adjust()    调整接收缓冲区
    |         sk_eat_skb()             数据直接加入 sk_receive_queue
    |         tcp_event_data_recv()    触发延迟 ACK 逻辑
    |
    |-- 【慢路径 Slow Path】(特殊情况)
        tcp_validate_incoming()        检查序号合法性
        tcp_ack()                      处理 ACK(更新 snd_una/cwnd)
        tcp_data_queue()               数据入队(可能乱序)
        tcp_data_snd_check()           尝试发送新数据
        tcp_ack_snd_check()            检查是否需要发送 ACK

pred_flags 的计算方式揭示了它的含义:

/* include/net/tcp.h */
#define TCP_HP_BITS (~(TCP_RESERVED_BITS | TCP_FLAG_PSH))
/* pred_flags = tcp_flag_word(th) & ~TCP_HP_BITS + snd_wnd_scale */

命中率通常超过 90%,这是 TCP 吞吐量的关键保障。


15. 拥塞控制算法框架深度实现

15.1 tcp_cong.c 框架实现分析

net/ipv4/tcp_cong.c 是算法框架的核心,通过模块化设计将算法与框架解耦:

/* net/ipv4/tcp_cong.c:26 */
struct tcp_congestion_ops *tcp_ca_find(const char *name)
{
    struct tcp_congestion_ops *e;

    list_for_each_entry_rcu(e, &tcp_cong_list, list) {
        if (strcmp(e->name, name) == 0)
            return e;
    }
    return NULL;
}

/* net/ipv4/tcp_cong.c:93 */
int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
{
    int ret = 0;

    /* 使用 jhash 生成唯一 key,用于快速查找 */
    ca->key = jhash(ca->name, strlen(ca->name), strlen(ca->name));

    spin_lock(&tcp_cong_list_lock);
    if (ca->key == TCP_CA_UNSPEC || tcp_ca_find_key(ca->key)) {
        /* key 冲突 */
        ret = -EEXIST;
    } else {
        list_add_tail_rcu(&ca->list, &tcp_cong_list);
    }
    spin_unlock(&tcp_cong_list_lock);
    return ret;
}

15.2 算法初始化流程

当新 TCP 连接建立时,拥塞控制算法的初始化链路:

tcp_create_openreq_child()          三次握手完成,创建子 socket
    |
    v
tcp_init_sock()
    |
    v
tcp_assign_congestion_control()     net/ipv4/tcp_cong.c:216
    |
    |--> 读取 net namespace 默认算法
    |    ca = rcu_dereference(net->ipv4.tcp_congestion_control)
    |
    +--> icsk->icsk_ca_ops = ca
         icsk->icsk_ca_priv 清零(104 字节私有数据区)

    v(稍后)
tcp_init_congestion_control()       net/ipv4/tcp_cong.c:236
    |
    +--> if (ca->init) ca->init(sk)  调用算法自定义初始化
         /* CUBIC 在此初始化 bictcp 结构 */
         /* BBR 在此分配 bbr 私有结构 */

15.3 拥塞避免的核心循环

每次收到 ACK 时,框架调用拥塞控制算法的核心入口:

/* net/ipv4/tcp_input.c 中 tcp_ack() 的拥塞控制部分 */
static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked,
                              int flag, const struct rate_sample *rs)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);

    if (icsk->icsk_ca_ops->cong_control) {
        /* BBR 走此分支:完全自定义控制 */
        icsk->icsk_ca_ops->cong_control(sk, ack, flag, rs);
        return;
    }

    if (tcp_in_cwnd_reduction(sk)) {
        /* Recovery/Loss 状态:PRR 算法 */
        tcp_cwnd_reduction(sk, acked_sacked, rs->losses, flag);
    } else if (tcp_may_raise_cwnd(sk, flag)) {
        /* Open/Disorder 状态:CUBIC/Reno 增长 */
        tcp_cong_avoid(sk, ack, acked_sacked);
    }
    tcp_update_pacing_rate(sk);
}

15.4 PRR 算法(Proportional Rate Reduction)

Linux 默认使用 PRR 算法(RFC 6937)代替传统的 cwnd 减半,在 Recovery 状态下更平滑地控制发送速率:

PRR 目标:Recovery 期间,以 ssthresh/cwnd 的比例减少在途包数
          避免在 Recovery 期间发送过多或过少

关键公式:
  prr_out  - 当前 Recovery 中已发送的总包数
  prr_delivered - 当前 Recovery 中已被 ACK 确认的包数
  sndcnt = FLOOR(delivered * ssthresh / pipe0 - prr_out)

  其中 pipe0 = Recovery 开始时的 in-flight 包数

实现位置:net/ipv4/tcp_input.c:3109  tcp_cwnd_reduction()

15.5 ECN(显式拥塞通知)与 CWR 状态

ECN 允许路由器在队列满之前通知端点减速,避免丢包:

路由器:检测到拥塞
    --> 设置 IP 头 ECN 字段 (CE: Congestion Experienced)
    |
    v
接收方:
    --> 在 ACK 中设置 ECE (ECN-Echo) 标志
    |
    v
发送方:
    --> 收到 ECE 标志的 ACK
    --> tcp_enter_cwr()            进入 CWR 状态
        tcp_ca_event(CA_EVENT_ECN_IS_CE)
        ssthresh = ca_ops->ssthresh(sk)  减小 ssthresh
        snd_cwnd = ssthresh              减小 cwnd
    --> 在下一个数据包中设置 CWR 标志
        通知对端已响应拥塞

16. TCP Fast Open 深度实现

16.1 TFO 设计架构

TCP Fast Open 的核心挑战是安全性:如果服务端在 SYN 中接收数据而不验证客户端身份,攻击者可以伪造 SYN+DATA 进行攻击。TFO 用 Cookie 机制解决这个问题。

TFO Cookie 生命周期:

【第一次连接(获取 Cookie)】
客户端                          服务端
    |                               |
    | SYN + TFO Option(len=0)       |  请求 Cookie
    |------------------------------>|
    |                               | tcp_fastopen_cookie_gen()
    |                               | 用 AES-128 对客户端 IP 加密
    | SYN+ACK + TFO Cookie          |
    |<------------------------------|
    | ACK                           |
    |------------------------------>|
    客户端缓存 Cookie

【后续连接(使用 Cookie)】
客户端                          服务端
    |                               |
    | SYN + TFO Cookie + DATA       |
    |------------------------------>|
    |                               | tcp_try_fastopen()
    |                               | 验证 Cookie 合法性
    |                               | 创建子 socket 接收数据
    | SYN+ACK + DATA(响应)        |
    |<------------------------------|
    数据在 ACK 之前已发送

16.2 tcp_fastopen_init_key_once() 实现

net/ipv4/tcp_fastopen.c:94 中的密钥初始化:

void tcp_fastopen_init_key_once(struct net *net)
{
    u8 key[TCP_FASTOPEN_KEY_LENGTH];
    struct tcp_fastopen_context *ctxt;

    rcu_read_lock();
    ctxt = rcu_dereference(net->ipv4.tcp_fastopen_ctx);
    if (ctxt) {
        rcu_read_unlock();
        return;
    }
    rcu_read_unlock();

    /* 生成随机密钥(每个 net namespace 独立) */
    get_random_bytes(key, sizeof(key));
    tcp_fastopen_reset_cipher(net, NULL, key, NULL);
}

密钥存储在 net->ipv4.tcp_fastopen_ctx 中,支持密钥轮转(最多保留两个密钥,用于平滑过渡)。

16.3 tcp_try_fastopen() 完整流程

net/ipv4/tcp_fastopen.c:437

struct sock *tcp_try_fastopen(struct sock *sk, struct sk_buff *skb,
                              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;  /* SYN 中有数据 */

    /* 检查服务端是否开启 TFO */
    if (!((tcp_fastopen & TFO_SERVER_ENABLE) &&
          (syn_data || foc->len >= 0) &&
          tcp_fastopen_queue_check(sk))) {
        foc->len = -1;
        return NULL;
    }

    if (foc->len == 0) {
        /* 客户端只是请求 Cookie,不携带数据 */
        tcp_fastopen_cookie_gen(sk, req, skb, &valid_foc);
    } else if (foc->len > 0) {
        /* 验证 Cookie */
        ret = tcp_fastopen_cookie_gen_check(sk, req, skb, foc, &valid_foc);
        if (ret) {
            /* Cookie 有效,创建子 socket 立即接受数据 */
            child = tcp_fastopen_create_child(sk, skb, req);
            /* child 已处于 SYN_RECV 状态,数据已排队 */
            NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPFASTOPENPASSIVE);
            return child;
        }
    }
    return NULL;
}

16.4 TFO 黑洞检测机制

某些中间设备(防火墙、NAT)会丢弃带数据的 SYN,导致 TFO "黑洞"。Linux 内核会自动检测并临时禁用 TFO:

/* net/ipv4/tcp_fastopen.c:580 */
void tcp_fastopen_active_disable(struct sock *sk)
{
    /* 更新禁用时间戳 */
    WRITE_ONCE(net->ipv4.tfo_active_disable_stamp, jiffies);
    /* 递增禁用计数器 */
    atomic_inc(&net->ipv4.tfo_active_disable_times);
}

/* 检查是否应该禁用 */
bool tcp_fastopen_active_should_disable(struct sock *sk)
{
    /* 默认禁用窗口:1小时 */
    unsigned int tfo_bh_timeout =
        READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_fastopen_blackhole_timeout_sec);

    timeout = READ_ONCE(sock_net(sk)->ipv4.tfo_active_disable_stamp)
              + tfo_bh_timeout * HZ;
    if (time_before(jiffies, timeout))
        return true;
    /* 超时后重置计数器,重新尝试 */
    return false;
}

16.5 fastopenq 结构与 TFO 连接的接受

TFO 使用了专用的 fastopen_queue 结构(嵌入在 inet_connection_sock 中)来管理处于 SYN_RECV 状态的 TFO 子 socket:

/* include/net/request_sock.h */
struct fastopen_queue {
    struct request_sock *rskq_rst_head;  /* RST'd request list(60秒窗口) */
    struct request_sock *rskq_rst_tail;
    spinlock_t    lock;
    int           qlen;       /* 当前 TFO 半连接数 */
    int           max_qlen;   /* 最大 TFO 半连接数 */
};
TFO 连接状态图:

  SYN+DATA 到达
      |
  tcp_try_fastopen()
      |
  tcp_fastopen_create_child()
      |
  子 socket 处于 SYN_RECV,数据已在接收队列
  子 socket 加入 fastopenq(非普通 accept queue)
      |
  应用调用 accept()
      |
  inet_csk_accept() 从 fastopenq 取出子 socket
      |
  三次握手完成(收到最终 ACK)
      |
  reqsk_fastopen_remove() 清理 request_sock

17. 接收缓冲区自动调整算法

17.1 DRS(Dynamic Right-Sizing)核心逻辑

Linux 内核通过 DRS 算法动态调整 sk_rcvbuf,使其恰好足以容纳一个 BDP(带宽延迟积)的数据,同时避免浪费内存。

核心函数 tcp_rcvbuf_grow() 位于 net/ipv4/tcp_input.c:912

void tcp_rcvbuf_grow(struct sock *sk, u32 newval)
{
    const struct net *net = sock_net(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    u32 rcvwin, rcvbuf, cap, oldval;
    u32 rtt_threshold, rtt_us;
    u64 grow;

    oldval = tp->rcvq_space.space;
    tp->rcvq_space.space = newval;

    /* 若用户锁定了 rcvbuf 或 moderate_rcvbuf 关闭,不调整 */
    if (!READ_ONCE(net->ipv4.sysctl_tcp_moderate_rcvbuf) ||
        (sk->sk_userlocks & SOCK_RCVBUF_LOCK))
        return;

    /* DRS 落后一个 RTT,目标窗口 = 2 * 实测吞吐量 */
    rcvwin = newval << 1;

    rtt_us = tp->rcv_rtt_est.rtt_us >> 3;
    rtt_threshold = READ_ONCE(net->ipv4.sysctl_tcp_rcvbuf_low_rtt);

    if (rtt_us < rtt_threshold) {
        /* 低延迟场景:线性增长,避免内存浪费 */
        grow = div_u64((u64)rcvwin * rtt_us, rtt_threshold);
    } else {
        /* 慢启动式:允许发送方翻倍速率 */
        grow = div_u64(((u64)rcvwin << 1) * (newval - oldval), oldval);
    }
    rcvwin += grow;

    /* 考虑乱序队列中的数据 */
    if (!RB_EMPTY_ROOT(&tp->out_of_order_queue))
        rcvwin += TCP_SKB_CB(tp->ooo_last_skb)->end_seq - tp->rcv_nxt;

    /* 限制在 tcp_rmem[2] 以内 */
    cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]);
    rcvbuf = min_t(u32, tcp_space_from_win(sk, rcvwin), cap);

    if (rcvbuf > sk->sk_rcvbuf) {
        WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
        /* 同步更新 window_clamp */
        WRITE_ONCE(tp->window_clamp, tcp_win_from_space(sk, rcvbuf));
    }
}

17.2 tcp_rcv_space_adjust() 触发时机

tcp_rcv_space_adjust()net/ipv4/tcp_input.c:961,在每次数据被应用层读走时调用:

void tcp_rcv_space_adjust(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int time, inq, copied;

    /* 无 RTT 估算时跳过(连接刚建立) */
    if (unlikely(!tp->rcv_rtt_est.rtt_us))
        return;

    /* 仅每 RTT 调整一次(避免频繁调整) */
    time = tcp_stamp_us_delta(tp->tcp_mstamp, tp->rcvq_space.time);
    if (time < (tp->rcv_rtt_est.rtt_us >> 3))   /* < RTT/8 时跳过 */
        return;

    /* 计算本 RTT 内应用层实际消费的字节数 */
    copied = tp->copied_seq - tp->rcvq_space.seq;
    /* 减去仍在内核接收队列中的数据(未被应用读走) */
    inq = tp->rcv_nxt - tp->copied_seq;
    copied -= inq;

    /* 若消费量超过当前 space,触发扩容 */
    if (copied <= tp->rcvq_space.space)
        goto new_measure;

    tcp_rcvbuf_grow(sk, copied);

new_measure:
    /* 更新测量基准 */
    tp->rcvq_space.seq = tp->copied_seq;
    tp->rcvq_space.time = tp->tcp_mstamp;
}

17.3 rcv_rtt 接收端 RTT 估算

为了自动调整缓冲区,内核需要在接收侧估算 RTT。这通过 tcp_rcv_rtt_measure() 实现:

接收端 RTT 估算方式:

方式1(TCP Timestamps 开启时):
  delta = tcp_rtt_tsopt_us(tp)
  用 tsecr(对端回显的时间戳)与本地时间之差估算 RTT

方式2(无时间戳):
  每接收到一个窗口大小的数据为一个 RTT 轮次
  rcv_rtt_est.seq 标记轮次起始序号
  当 rcv_nxt 越过 seq 时,记录一次 RTT

存储位置:
  tp->rcv_rtt_est.rtt_us  接收侧 RTT 估算值(左移 3 位)

17.4 接收缓冲区与发送窗口的联动

接收缓冲区大小直接影响向对端通告的接收窗口大小:

接收窗口通告值的计算链:

sk_rcvbuf                           接收缓冲区上限
    |
    v
tcp_full_space(sk)                  = sk_rcvbuf - sk_rmem_alloc (已用)
    |
    v
tcp_win_from_space(sk, space)       = space >> 1 (减半做安全余量)
    |
    v
window_clamp                        通告窗口上限
    |
    v
rcv_wnd                             实际通告值(受 window_clamp 限制)
    |
    v
TCP 头部 Window 字段(16位,可缩放)

窗口缩放(Window Scale)选项在三次握手时协商,最大可将窗口扩展到 2^30 = 1GB

17.5 tcp_grow_window() 逐包扩展逻辑

除了应用读取触发的扩展,tcp_grow_window() 在数据包到达时也会触发接收窗口扩展:

/* net/ipv4/tcp_input.c:700 */
static void tcp_grow_window(struct sock *sk, const struct sk_buff *skb,
                            bool allow_shrink)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int room;

    room = min_t(int, tp->window_clamp, tcp_space(sk)) - tp->rcv_ssthresh;

    /* 若接收速率高(快速扩展阶段)且还有空间 */
    if (room > 0 && !tcp_under_memory_pressure(sk)) {
        int incr;
        /* 每次至多增加 2*skb->len,避免过快增长 */
        if (tp->rcv_ssthresh < tp->window_clamp &&
            (int)tp->rcv_ssthresh + incr < tp->window_clamp)
            tp->rcv_ssthresh += incr;
    }
}

18. GRO 和 GSO 机制深度剖析

18.1 GRO(Generic Receive Offload)工作原理

GRO 在 net/ipv4/tcp_offload.c 中实现,是内核软件层的接收合并优化。

网卡收到多个小包(每个 MSS = 1460 字节):

  [TCP pkt1: seq=0-1459]   [TCP pkt2: seq=1460-2919]   [TCP pkt3: seq=2920-4379]
           |                          |                           |
           +----------[GRO 引擎]-----------+--------------------+
           |
           v
  合并后的大 skb(seq=0-4379, gso_segs=3, gso_size=1460)
           |
           v
  tcp_rcv_established()   一次调用处理 3 个报文的数据量

GRO 的触发点在 NAPI 轮询的 napi_gro_receive() 中,每个连接维护一个 GRO flow 列表。

18.2 tcp4_gro_receive() 实现分析

net/ipv4/tcp_offload.c:419

struct sk_buff *tcp4_gro_receive(struct list_head *head, struct sk_buff *skb)
{
    struct iphdr *iph = skb_gro_network_header(skb);
    struct tcphdr *th;

    /* 验证 IP checksum(GRO 层可以硬件卸载) */
    if (!NAPI_GRO_CB(skb)->flush &&
        skb_gro_checksum_validate(skb, IPPROTO_TCP,
                                  inet_gro_compute_pseudo)) {
        NAPI_GRO_CB(skb)->flush = 1;
        return NULL;
    }

    /* 调用通用 TCP GRO 处理 */
    th = tcp_gro_header_pull(skb);
    return tcp_gro_receive(head, skb, th);
}

tcp_gro_receive() 核心合并判断(net/ipv4/tcp_offload.c:286):

struct sk_buff *tcp_gro_receive(struct list_head *head, struct sk_buff *skb,
                                struct tcphdr *th)
{
    /* 在 head 列表中查找同一 TCP 连接的已有聚合 skb */
    p = tcp_gro_lookup(head, th);
    if (!p)
        goto out_check_final;

    th2 = tcp_hdr(p);

    /* 判断是否可以合并(flush = 1 表示不能合并):
     * - TCP 标志不同(除 FIN/PSH 外)
     * - ACK 序号不同
     * - TCP 选项不同
     * - seq 不连续
     * - skb 加密状态不同
     */
    flush = (__force int)((flags ^ tcp_flag_word(th2)) &
              ~(TCP_FLAG_FIN | TCP_FLAG_PSH));
    flush |= (__force int)(th->ack_seq ^ th2->ack_seq);
    flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq);

    if (flush || skb_gro_receive(p, skb)) {
        mss = 1;
        goto out_check_final;
    }

    /* 合并成功:合并 FIN/PSH 标志 */
    tcp_flag_word(th2) |= flags & (TCP_FLAG_FIN | TCP_FLAG_PSH);
    ...
}

18.3 tcp_gro_complete() 完成阶段

当 GRO 聚合的 skb 被"冲刷"到协议栈时,需要设置 GSO 元数据,以便后续处理知道这是合并过的包:

/* net/ipv4/tcp_offload.c:369 */
void tcp_gro_complete(struct sk_buff *skb)
{
    struct tcphdr *th = tcp_hdr(skb);
    struct skb_shared_info *shinfo;

    /* 设置 checksum 信息,允许后续校验 */
    skb->csum_start = (unsigned char *)th - skb->head;
    skb->csum_offset = offsetof(struct tcphdr, check);
    skb->ip_summed = CHECKSUM_PARTIAL;

    shinfo = skb_shinfo(skb);
    /* 记录合并的段数,用于 GSO 分拆 */
    shinfo->gso_segs = NAPI_GRO_CB(skb)->count;

    /* AccECN:若合并包中有 CWR 标志 */
    if (th->cwr)
        shinfo->gso_type |= SKB_GSO_TCP_ACCECN;
}

18.4 GSO(Generic Segmentation Offload)

GSO 是发送方向的对应机制。当内核在发送大 skb 时,若网卡不支持硬件分段,tcp_gso_segment() 在软件层完成分段:

/* net/ipv4/tcp_offload.c:133 */
struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
                                netdev_features_t features)
{
    struct tcphdr *th;
    unsigned int mss;
    struct sk_buff *segs;
    unsigned int seq;

    th = tcp_hdr(skb);
    mss = skb_shinfo(skb)->gso_size;  /* 从 skb 元数据取 MSS */

    /* 若网卡支持 GSO,不需要软件分段(skb_gso_ok 检查) */
    if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
        skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
        segs = NULL;
        goto out;
    }

    /* 软件分段:调用通用 skb_segment() */
    segs = skb_segment(skb, features);

    /* 逐段更新 TCP 序号和 checksum */
    seq = ntohl(th->seq);
    while (skb->next) {
        th->fin = th->psh = 0;       /* 中间段清除 FIN/PSH */
        seq += mss;
        skb = skb->next;
        th = tcp_hdr(skb);
        th->seq = htonl(seq);        /* 更新序号 */
    }
    /* 最后一段保留 FIN/PSH */
    ...
}

18.5 TSO 与 GSO 的关系

TSO(硬件分段)      GSO(软件分段)     区别
------------------  ------------------  ----------------
网卡硬件执行        内核软件执行         执行位置不同
NETIF_F_TSO 标志    NETIF_F_GSO 标志     Feature 标志不同
SKB_GSO_TCPV4      SKB_GSO_TCPV4        GSO type 相同
发送延迟极低        有少量 CPU 开销      性能差异

内核判断逻辑:
  tcp_transmit_skb()
      |
      v
  netif_needs_gso(skb, features)?
      |
      +--> 不需要分段(网卡支持 TSO):直接发送大 skb
      |
      +--> 需要软件 GSO:调用 __skb_gso_segment()
               --> tcp_gso_segment()
               --> 分成多个小 skb 发送

18.6 GRO/GSO 的数据结构关联

/* include/linux/skbuff.h */
struct skb_shared_info {
    /* GRO/GSO 元数据 */
    __u16   gso_segs;      /* 合并/待分段的段数 */
    __u16   gso_size;      /* 每段大小(MSS) */
    __u32   gso_type;      /* SKB_GSO_TCPV4 / SKB_GSO_TCPV6 等 */
    /* gso_type 定义 include/linux/skbuff.h:669 */
    /* SKB_GSO_TCPV4  = 1 << 0  IPv4 TCP */
    /* SKB_GSO_TCPV6  = 1 << 4  IPv6 TCP */
};

/* NAPI GRO 控制块(嵌入 skb->cb) */
struct napi_gro_cb {
    u16  count;           /* 已合并的段数 */
    u8   same_flow;       /* 属于同一流 */
    u8   flush;           /* 强制冲刷 */
    u8   is_flist;        /* 使用 fragment list 合并 */
    u32  hash;            /* 流哈希值 */
    ...
};

19. TCP 重传机制深度:RTO 计算、快速重传、SACK

19.1 RTO 计算的精确实现

tcp_rtt_estimator() 位于 net/ipv4/tcp_input.c,严格按照 RFC 6298 实现:

static void tcp_rtt_estimator(struct sock *sk, long mrtt_us)
{
    struct tcp_sock *tp = tcp_sk(sk);
    long m = mrtt_us;   /* 本次测量的 RTT(微秒) */
    u32 srtt = tp->srtt_us;

    if (srtt != 0) {
        /* RFC 6298: SRTT = (1-alpha)*SRTT + alpha*RTT  (alpha=1/8) */
        m -= (srtt >> 3);   /* m = RTT - SRTT/8 = 误差 */
        srtt += m;          /* srtt = srtt + err = 7/8*srtt + 1/8*RTT */

        /* RTTVAR = (1-beta)*RTTVAR + beta*|SRTT-RTT| (beta=1/4) */
        if (m < 0) m = -m;   /* |误差| */
        m -= (tp->mdev_us >> 2);
        tp->mdev_us += m;    /* mdev_us = 3/4*mdev + 1/4*|err| */
        /* 维护 mdev_max:本 RTT 轮次中 mdev 的最大值 */
        if (tp->mdev_us > tp->mdev_max_us) {
            tp->mdev_max_us = tp->mdev_us;
            if (tp->mdev_max_us > tp->rttvar_us)
                tp->rttvar_us = tp->mdev_max_us;
        }
        /* 每个 RTT 轮次重置 mdev_max */
        if (after(tp->snd_una, tp->rtt_seq)) {
            if (tp->mdev_max_us < tp->rttvar_us)
                tp->rttvar_us -= (tp->rttvar_us - tp->mdev_max_us) >> 2;
            tp->rtt_seq = tp->snd_nxt;
            tp->mdev_max_us = tcp_rto_min_us(sk);
        }
    } else {
        /* 第一次测量:直接初始化 */
        srtt = m << 3;       /* srtt = 8*RTT(因为存储时左移3位) */
        tp->mdev_us = m << 1;
        tp->rttvar_us = max(tp->mdev_us, tcp_rto_min_us(sk));
        tp->mdev_max_us = tp->rttvar_us;
        tp->rtt_seq = tp->snd_nxt;
    }
    tp->srtt_us = max(1U, srtt);
}

RTO 的最终计算(RFC 6298 公式):

RTO = SRTT + max(G, 4*RTTVAR)

在内核中:
  icsk_rto = (tp->srtt_us >> 3) + tp->rttvar_us
           = SRTT + RTTVAR(RTTVAR 已包含 4* 因子)

  限制范围:
    TCP_RTO_MIN = HZ/5    = 200ms  (include/net/tcp.h:142)
    TCP_RTO_MAX = 120*HZ  = 120s

  退避指数(每次 RTO 超时翻倍):
    icsk_rto = min(icsk_rto << icsk_backoff, TCP_RTO_MAX)

19.2 tcp_enter_loss() 进入 Loss 状态

当 RTO 超时触发时,tcp_enter_loss() 将连接置于最糟糕的恢复状态:

/* net/ipv4/tcp_input.c:2553 */
void tcp_enter_loss(struct sock *sk)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    bool new_recovery = icsk->icsk_ca_state < TCP_CA_Recovery;

    /* 标记所有在途包为丢失 */
    tcp_timeout_mark_lost(sk);

    /* 更新 ssthresh(若本窗口内首次进入 Loss) */
    if (icsk->icsk_ca_state <= TCP_CA_Disorder ||
        !after(tp->high_seq, tp->snd_una) ||
        (icsk->icsk_ca_state == TCP_CA_Loss && !icsk->icsk_retransmits)) {
        tp->prior_ssthresh = tcp_current_ssthresh(sk);
        tp->prior_cwnd = tcp_snd_cwnd(tp);
        tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);
        tcp_ca_event(sk, CA_EVENT_LOSS);
        tcp_init_undo(tp);
    }

    /* cwnd 设为在途包数 + 1(保守策略) */
    tcp_snd_cwnd_set(tp, tcp_packets_in_flight(tp) + 1);
    tp->snd_cwnd_cnt   = 0;
    tp->snd_cwnd_stamp = tcp_jiffies32;

    /* F-RTO:尝试避免错误的超时重传 */
    tp->frto = READ_ONCE(net->ipv4.sysctl_tcp_frto) &&
               (new_recovery || icsk->icsk_retransmits) &&
               !inet_csk(sk)->icsk_mtup.probe_size;
}

19.3 快速重传与 tcp_fastretrans_alert()

快速重传由 tcp_fastretrans_alert() 驱动,位于 net/ipv4/tcp_input.c:3328

触发条件(满足任一):
  1. 收到 3 个重复 ACK(dupACK >= 3)
  2. SACK 信息显示有丢包
  3. RACK 算法判定包超时

tcp_fastretrans_alert() 内部逻辑:
    |
    |--> 若在 Open/Disorder 状态且 dupACK >= 3:
    |       tcp_enter_recovery()         进入 Recovery
    |
    |--> 若在 Recovery 状态:
    |       tcp_sacktag_write_queue()    更新 SACK 评分板
    |       tcp_verify_retransmit_hint() 定位重传起始点
    |       tcp_retransmit_skb()         重传第一个标记丢失的包
    |
    +--> 若撤销条件成立(收到 DSACK 或对端确认序号回退):
            tcp_try_undo_recovery()      尝试撤销减窗

19.4 SACK 评分板工作机制

TCP 维护一个"评分板"(scoreboard)记录每个在途包的状态:

TCP_SKB_CB(skb)->sacked 标志位含义:
  TCPCB_SACKED_ACKED   (0x01)  已被 SACK 确认
  TCPCB_SACKED_RETRANS (0x02)  已重传
  TCPCB_LOST           (0x04)  判定为丢失
  TCPCB_TAGBITS        (0x06)  SACK + RETRANS 位掩码
  TCPCB_REPAIRED       (0x10)  已修复(重传后被 SACK)

评分板示例:
  snd_una              snd_nxt
    |                     |
    v                     v
  [pkt1:ACK'd][pkt2:LOST][pkt3:SACK'd][pkt4:SACK'd][pkt5:in-flight]
               ^------- 需要重传 --------^

  packets_out = 3  (in-flight: pkt2+pkt5 + pkt4还未退出)
  sacked_out  = 2  (pkt3+pkt4)
  lost_out    = 1  (pkt2)
  retrans_out = 0  (尚未重传)

19.5 TLP(Tail Loss Probe)

TLP 是一种在 RTO 超时之前提前检测尾部丢包的机制,避免了 200ms+ 的 RTO 等待:

TLP 触发场景:
  发送了数据后,没有收到 ACK
  计时器设置为 max(2*RTT, 10ms)(远小于 RTO)

TLP 操作:
  1. 发送一个探测包(最新数据或重传最后一包)
  2. 若探测包触发 ACK 带 SACK 信息,RACK 可据此判断丢包
  3. 避免了等待 RTO 超时(从 ~200ms 降到 ~2*RTT)

内核实现:
  tcp_send_loss_probe()          net/ipv4/tcp_output.c
  ICSK_TIME_LOSS_PROBE = 5       定时器类型
  tp->tlp_high_seq               TLP 发送时的 snd_nxt(用于追踪)

20. TCP TIME_WAIT 处理:tw_reuse 实现

20.1 TIME_WAIT 的设计意图

TIME_WAIT 状态存在 2MSL(Maximum Segment Lifetime,默认 60 秒)的原因:

原因1:防止旧连接的迟到报文干扰新连接
  连接 A(相同 4-tuple)的旧报文可能在网络中延迟
  2*MSL 确保所有旧报文已经"消亡"

原因2:确保最后一个 ACK 到达对端
  主动关闭方发送最终 ACK,若丢失对端会重传 FIN
  TIME_WAIT 状态接收 FIN 并重传 ACK

代价:
  TIME_WAIT socket 占用内存(约 300 字节/个,使用 tcp_timewait_sock)
  高并发短连接场景可能耗尽本地端口

20.2 tcp_time_wait() 的 twsk 轻量化设计

net/ipv4/tcp_minisocks.c:327 中,进入 TIME_WAIT 时会将完整的 tcp_sock 替换为轻量级的 inet_timewait_sock

void tcp_time_wait(struct sock *sk, int state, int timeo)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    struct net *net = sock_net(sk);
    struct inet_timewait_sock *tw;

    /* 分配轻量级 TIME_WAIT socket(约 300 字节,比 tcp_sock 小 10 倍) */
    tw = inet_twsk_alloc(sk, &net->ipv4.tcp_death_row, state);

    if (tw) {
        struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
        const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);

        /* 只复制必要信息 */
        tw->tw_rcv_wscale   = tp->rx_opt.rcv_wscale;
        tcptw->tw_rcv_nxt   = tp->rcv_nxt;
        tcptw->tw_snd_nxt   = tp->snd_nxt;
        tcptw->tw_rcv_wnd   = tcp_receive_window(tp);
        tcptw->tw_ts_recent = tp->rx_opt.ts_recent;
        tcptw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp;
        tcptw->tw_ts_offset = tp->tsoffset;

        /* 计算超时时间 */
        if (timeo < rto) timeo = rto;
        if (state == TCP_TIME_WAIT) timeo = TCP_TIMEWAIT_LEN; /* 60s */

        /* 加入 TIME_WAIT hash 表,启动定时器 */
        inet_twsk_hashdance_schedule(tw, sk, net->ipv4.tcp_death_row.hashinfo, timeo);
    }

    /* 销毁原始 tcp_sock */
    tcp_update_metrics(sk);
    tcp_done(sk);
}

20.3 tcp_timewait_state_process() 详细分析

TIME_WAIT 状态下收到报文时的处理逻辑(net/ipv4/tcp_minisocks.c:101):

收到报文(处于 TIME_WAIT)
    |
    v
tcp_timewait_state_process()
    |
    |--> 检查是否在 FIN_WAIT2 子状态
    |       若是,处理对端 FIN,转入真正的 TIME_WAIT
    |
    |--> TIME_WAIT 中的合法 SYN 处理(RFC 1122):
    |    条件:
    |      - 新 SYN 的序号 > 期望的 rcv_nxt(或时间戳更新)
    |      - 非 PAWS 拒绝(时间戳不回退)
    |    动作:
    |      *tw_isn = tcptw->tw_snd_nxt + 65535 + 2
    |      return TCP_TW_SYN   (允许新连接复用)
    |
    |--> RST 报文:
    |    若 sysctl_tcp_rfc1337 = 0(默认):立即销毁 TIME_WAIT socket
    |    若 sysctl_tcp_rfc1337 = 1:忽略 RST,防止 TIME_WAIT 刺杀
    |
    +--> 窗口外报文或重复 ACK:
            发送 ACK 响应(限速保护)
            return TCP_TW_ACK

20.4 tcp_tw_reuse 的内核实现

tcp_tw_reuse 允许在满足条件时复用 TIME_WAIT socket,由 tcp_twsk_unique() 实现(net/ipv4/tcp_ipv4.c:118):

int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
{
    int reuse = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tw_reuse);
    const struct inet_timewait_sock *tw = inet_twsk(sktw);
    const struct tcp_timewait_sock *tcptw = tcp_twsk(sktw);
    struct tcp_sock *tp = tcp_sk(sk);

    /* FIN_WAIT2 子状态不能复用 */
    if (READ_ONCE(tw->tw_substate) == TCP_FIN_WAIT2)
        reuse = 0;

    /* reuse=2(默认值):仅环回地址可复用(更安全) */
    if (reuse == 2) {
        bool loopback = false;
        if (tw->tw_bound_dev_if == LOOPBACK_IFINDEX)
            loopback = true;
        if (ipv4_is_loopback(tw->tw_daddr) ||
            ipv4_is_loopback(tw->tw_rcv_saddr))
            loopback = true;
        if (!loopback)
            reuse = 0;
    }

    /* 复用条件:
     * 1. TIME_WAIT socket 有时间戳记录(ts_recent_stamp != 0)
     *    (依赖 TCP timestamps 防止序号空间混淆)
     * 2. 距离进入 TIME_WAIT 已超过 reuse_delay(默认 1 秒)
     */
    reuse_thresh = READ_ONCE(tw->tw_entry_stamp) +
                   READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tw_reuse_delay);

    if (ts_recent_stamp &&
        (!twp || (reuse && time_after32(tcp_clock_ms(), reuse_thresh)))) {
        /* 复用:继承时间戳信息,确保 PAWS 正确工作 */
        if (likely(!tp->repair)) {
            /* 设置初始序号,避免与旧连接序号空间重叠 */
            u32 seq = tcptw->tw_snd_nxt + 65535 + 2;
            WRITE_ONCE(tp->write_seq, seq);
            tp->rx_opt.ts_recent       = tcptw->tw_ts_recent;
            tp->rx_opt.ts_recent_stamp = ts_recent_stamp;
        }
        return 1;   /* 可以复用 */
    }

    return 0;
}

20.5 tcp_tw_reuse 配置值含义

sysctl_tcp_tw_reuse 取值:
  0 = 禁用复用
  1 = 允许所有地址复用(包括公网,有 PAWS 保护)
  2 = 默认值,仅允许环回地址复用(最保守,最安全)

sysctl_tcp_tw_reuse_delay(Linux 6.x 新增):
  默认 1000ms(1秒)
  在进入 TIME_WAIT 后至少等待此时间才允许复用
  确保对端有足够时间完成 TIME_WAIT 处理

sysctl_tcp_rfc1337:
  0 = 关闭(默认):TIME_WAIT 收到 RST 立即销毁
  1 = 开启(RFC 1337 合规):忽略 TIME_WAIT 中的 RST
      防止 TIME_WAIT 刺杀攻击(第三方伪造 RST)

已废弃的 tw_recycle(Linux 4.12 已删除):
  tw_recycle 利用每主机时间戳缓存加速 TIME_WAIT 回收
  但在 NAT 环境下会导致连接被 PAWS 错误拒绝
  已从内核彻底删除,不应再使用

21. SO_REUSEPORT 完整实现

21.1 SO_REUSEPORT 的工作原理

传统的 TCP 服务器使用单个监听 socket,多个工作线程通过 accept() 竞争锁。SO_REUSEPORT 允许多个 socket 绑定同一端口,内核负责将新连接分发给不同的 socket,彻底消除 accept 锁竞争:

传统架构(单 listen socket):              SO_REUSEPORT 架构:

    listen_fd                             listen_fd0  listen_fd1  listen_fd2
        |                                    |            |            |
   accept_lock ← 所有线程竞争              worker0     worker1     worker2
   /    |    \                              独立 accept queue,无锁竞争
 w0    w1    w2

21.2 sock_reuseport 数据结构

net/core/sock_reuseport.c 中维护 SO_REUSEPORT 组的核心结构:

/* include/net/sock_reuseport.h */
struct sock_reuseport {
    struct rcu_head     rcu;
    u16                 max_socks;       /* 数组容量 */
    u16                 num_socks;       /* 当前 socket 数 */
    u16                 num_closed_socks;/* 已关闭的 socket 数 */
    u16                 incoming_cpu;    /* 上次服务的 CPU(SO_INCOMING_CPU 优化) */
    unsigned int        synq_overflow_ts;
    unsigned int        reuseport_id;
    unsigned int        bind_inany:1;
    unsigned int        has_conns:1;    /* 是否有 ESTABLISHED 连接 */
    struct bpf_prog __rcu *prog;        /* 可选的 BPF 分发程序 */
    struct sock         *socks[];       /* socket 数组 */
};

21.3 reuseport_select_sock() 分发逻辑

net/core/sock_reuseport.c:568

struct sock *reuseport_select_sock(struct sock *sk,
                                   u32 hash,
                                   struct sk_buff *skb,
                                   int hdr_len)
{
    struct sock_reuseport *reuse;
    struct bpf_prog *prog;
    struct sock *sk2 = NULL;
    u16 socks;

    rcu_read_lock();
    reuse = rcu_dereference(sk->sk_reuseport_cb);
    if (!reuse)
        goto out;

    prog = rcu_dereference(reuse->prog);
    socks = READ_ONCE(reuse->num_socks);

    if (likely(socks)) {
        smp_rmb();    /* 与 __reuseport_add_sock() 的 smp_wmb() 配对 */

        if (prog && skb) {
            if (prog->type == BPF_PROG_TYPE_SK_REUSEPORT)
                /* BPF SK_REUSEPORT 程序:完全自定义分发逻辑 */
                sk2 = bpf_run_sk_reuseport(reuse, sk, prog, skb, NULL, hash);
            else
                /* 传统 SO_ATTACH_REUSEPORT_CBPF/EBPF */
                sk2 = run_bpf_filter(reuse, socks, prog, skb, hdr_len);
        }

        /* 无 BPF 或 BPF 返回 NULL:回退到哈希分发 */
        if (!sk2)
            sk2 = reuseport_select_sock_by_hash(reuse, hash, socks);
    }
out:
    rcu_read_unlock();
    return sk2;
}

哈希分发函数 reuseport_select_sock_by_hash()net/core/sock_reuseport.c:527,使用 hash % num_socks 选择目标 socket(对 2 的幂取模优化为位运算)。

21.4 SO_INCOMING_CPU 与 CPU 亲和性优化

结合 SO_INCOMING_CPU 可实现 CPU 亲和性分发,减少缓存 miss:

/* 内核在分发时优先选择与当前 CPU 关联的 socket */
static void reuseport_get_incoming_cpu(struct sock *sk,
                                       struct sock_reuseport *reuse)
{
    /* 若 SO_INCOMING_CPU 设置了 preferred_cpu,
     * 优先将连接分发给绑定该 CPU 的 socket */
}

21.5 SO_REUSEPORT + BPF 高级用法

Linux 4.19+ 支持 BPF_PROG_TYPE_SK_REUSEPORT,允许用 eBPF 程序自定义分发策略:

/* 用户态 BPF 程序骨架(伪代码) */
SEC("sk_reuseport")
int reuseport_selector(struct sk_reuseport_md *ctx)
{
    /* ctx->data    : 原始报文数据 */
    /* ctx->hash    : 连接 4-tuple 哈希 */
    /* ctx->ip_protocol : IPPROTO_TCP */

    /* 示例:按目标端口选择 socket */
    if (ctx->remote_port == 8080)
        return bpf_sk_select_reuseport(ctx, &sock_map, &key, 0);

    /* 默认哈希分发 */
    return SK_PASS;
}

应用场景:

  • 按 HTTP/2 stream ID 将同一客户端的请求发给同一 worker(保持有序)
  • 动态调整分发权重(如金丝雀发布)
  • 将特定协议的连接发给专用 worker

21.6 inet_connection_sock.c 中的端口绑定冲突检测

net/ipv4/inet_connection_sock.c 中的 SO_REUSEPORT 端口冲突检测:

/* net/ipv4/inet_connection_sock.c:173 */
static bool inet_bind_conflict(const struct sock *sk,
                               struct sock *sk2,
                               kuid_t sk_uid,
                               bool relax,
                               bool reuseport_cb_ok,
                               bool reuseport_ok)
{
    /* 若两个 socket 都设置了 SO_REUSEPORT,且
     * 没有 BPF 程序(或 BPF 程序一致),则不冲突 */
    if (!relax || (!reuseport_ok && sk->sk_reuseport &&
                   sk2->sk_reuseport && reuseport_cb_ok &&
                   (sk->sk_state == TCP_TIME_WAIT ||
                    uid_eq(sk_uid, sock_i_uid(sk2)))))
        return false;  /* 允许共享 */

    return true;  /* 冲突,不允许绑定 */
}

22. TCP Segmentation Offload 深度

22.1 TSO 与 GSO 的硬件协作模型

TSO/GSO 是现代高性能网络的基石,彻底改变了数据发送路径:

【无 TSO/GSO 时】:内核每 MSS 构造一个 skb,CPU 开销 O(N)
  send()
    |
    v
  tcp_write_xmit()
    循环 N 次:
      [skb1:1460B] --> ip_queue_xmit() --> dev_queue_xmit() --> 网卡
      [skb2:1460B] --> ip_queue_xmit() --> dev_queue_xmit() --> 网卡
      ...

【有 TSO 时】:内核构造一个大 skb,CPU 开销 O(1)
  send()
    |
    v
  tcp_write_xmit()
    一次:
      [skb:64KB, gso_size=1460, gso_segs=44] --> 网卡
                                                   ↓
                                           网卡硬件分成 44 个包发送

22.2 tcp_set_skb_tso_segs() 初始化 GSO 元数据

net/ipv4/tcp_output.c:1734

static int tcp_set_skb_tso_segs(struct sk_buff *skb, unsigned int mss_now)
{
    int tso_segs;

    if (skb->len <= mss_now) {
        /* 小于 MSS 的包不需要 TSO */
        tcp_skb_pcount_set(skb, 1);
        TCP_SKB_CB(skb)->tcp_gso_size = 0;
        return 1;
    }

    /* 计算需要分成多少段 */
    tso_segs = DIV_ROUND_UP(skb->len, mss_now);
    tcp_skb_pcount_set(skb, tso_segs);
    TCP_SKB_CB(skb)->tcp_gso_size = mss_now;
    return tso_segs;
}

22.3 tcp_tso_autosize() 动态聚合大小计算

net/ipv4/tcp_output.c:2235,这是 TSO 性能的关键函数:

static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now,
                            int min_tso_segs)
{
    unsigned long bytes;
    u32 r;

    /* 基于 pacing rate 计算每次发送量(1ms 的发送量)
     * sk_pacing_rate 单位:字节/秒
     * >> sk_pacing_shift(默认 10,即除以 1024)= ~1ms 间隔
     */
    bytes = READ_ONCE(sk->sk_pacing_rate) >> READ_ONCE(sk->sk_pacing_shift);

    /* 基于 RTT 的额外增量:RTT 越大,聚合越多(高 BDP 链路) */
    r = tcp_min_rtt(tcp_sk(sk)) >>
        READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tso_rtt_log);
    if (r < BITS_PER_TYPE(sk->sk_gso_max_size))
        bytes += sk->sk_gso_max_size >> r;

    bytes = min_t(unsigned long, bytes, sk->sk_gso_max_size);
    return max_t(u32, bytes / mss_now, min_tso_segs);
}

设计意图:

  • 低 RTT 链路(数据中心):TSO 段数少,延迟优先
  • 高 RTT 链路(跨洋):TSO 段数多,吞吐量优先

22.4 tcp_tso_should_defer() 推迟发送逻辑

net/ipv4/tcp_output.c:2431,在某些情况下推迟发送可以聚合更多数据:

static bool tcp_tso_should_defer(struct sock *sk, struct sk_buff *skb,
                                 bool *is_cwnd_limited,
                                 bool *is_rwnd_limited, u32 max_segs)
{
    /* Recovery 状态不推迟(需要快速重传) */
    if (icsk->icsk_ca_state >= TCP_CA_Recovery)
        goto send_now;

    /* 最近没有发送(超过 1ms)不推迟 */
    delta = tp->tcp_clock_cache - tp->tcp_wstamp_ns - NSEC_PER_MSEC;
    if (delta > 0)
        goto send_now;

    /* 若窗口足够发送一个完整 TSO 包(max_segs * MSS),立即发 */
    if (limit >= max_segs * tp->mss_cache)
        goto send_now;

    /* 推迟:等待更多数据聚合后一起发,减少包数量 */
    return true;
send_now:
    return false;
}

22.5 网卡特性检测与 TSO 路径选择

/* net/ipv4/tcp_output.c 中 __tcp_transmit_skb() */
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
                              int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
    /* ... 构造 TCP 头 ... */

    /* 设置 GSO 类型(通知网卡此包需要分段) */
    skb_shinfo(skb)->gso_type = sk->sk_gso_type;
    /* sk_gso_type 在 socket 建立时根据 IP 版本设置:
     * IPv4: SKB_GSO_TCPV4 = 1 << 0  (include/linux/skbuff.h:669)
     * IPv6: SKB_GSO_TCPV6 = 1 << 4  (include/linux/skbuff.h:679)
     */

    /* Pacing:若启用了 pacing,更新发送时间戳 */
    if (sk->sk_pacing_status != SK_PACING_NONE) {
        unsigned long rate = READ_ONCE(sk->sk_pacing_rate);
        /* tcp_wstamp_ns = 上一包发送时间 + 包大小/发送速率 */
    }
}

22.6 小队列机制(Small Queue)

Linux 3.6+ 引入 TCP Small Queue,限制每个 socket 在 qdisc 层堆积的数据量,避免单连接独占网卡队列:

/* net/ipv4/tcp_output.c:2835 */
static bool tcp_small_queue_check(struct sock *sk, const struct sk_buff *skb,
                                  unsigned int factor)
{
    unsigned long limit;

    /* 允许最多 2 个 TSO 包在 qdisc 中等待 */
    limit = max_t(unsigned long,
                  2 * skb->truesize,
                  READ_ONCE(sk->sk_pacing_rate) >> READ_ONCE(sk->sk_pacing_shift));

    limit = min_t(unsigned long, limit,
                  READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_limit_output_bytes));
    limit <<= factor;

    if (refcount_read(&sk->sk_wmem_alloc) > limit) {
        /* 队列已满,不再发送,等待 TSQ 回调 */
        set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags);
        /* TSQ 回调在 tcp_wfree() 中触发 */
        return true;
    }
    return false;
}

23. QUIC 在内核中的实现方向与现状

23.1 QUIC 协议概述

QUIC(Quick UDP Internet Connections)最初由 Google 开发,后标准化为 RFC 9000。它在 UDP 之上实现了类似 TCP 的可靠传输,同时解决了 TCP 的几个固有问题:

TCP 的固有问题:           QUIC 的解决方案:
+---------------------+   +---------------------------+
| 队头阻塞(HOL)     |   | 独立流,流间不阻塞        |
| 0-RTT 连接建立慢    |   | TLS 1.3 集成,0-RTT 支持  |
| 中间设备 ossification|  | 基于 UDP,易于部署更新    |
| 连接迁移困难        |   | 连接 ID 机制,IP 变化无感  |
+---------------------+   +---------------------------+

23.2 当前内核对 QUIC 的支持现状

截至本文基于的内核版本,Linux 内核尚未原生实现完整的 QUIC 协议,但提供了若干基础设施支持:

内核提供的 QUIC 基础:
  +-- net/tls/           : KTLS(内核 TLS),QUIC 可复用 TLS 记录层加密
  |   tls_main.c         : TLS ULP 框架
  |   tls_device.c       : TLS offload(网卡硬件加密)
  |
  +-- net/udp/           : UDP 基础
  |   udp.c              : UDP socket,QUIC 运行在 UDP 上
  |
  +-- net/handshake/     : TLS 握手框架(Linux 6.2+)
  |   tlshd.c            : 通过 netlink 与用户态 tlshd 通信
  |
  +-- 用户态 QUIC 实现:
      quiche (Cloudflare/Google), msquic (Microsoft),
      NGINX QUIC, lsquic, mvfst (Facebook)

23.3 KTLS(内核 TLS)与 QUIC 的关系

KTLS 将 TLS 加密/解密移入内核(或硬件),QUIC 可以利用类似机制:

/* net/tls/tls_main.c */
/* 启用 KTLS:应用层 TLS 握手后,将密钥传给内核 */
setsockopt(fd, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info));
setsockopt(fd, SOL_TLS, TLS_RX, &crypto_info, sizeof(crypto_info));

/* 此后 send()/recv() 自动在内核中完成加解密 */
/* 优点:
 *   1. 零拷贝 sendfile 支持(sendfile + TLS 加密合并)
 *   2. 网卡 TLS offload(TLS TOE: TCP Offload Engine)
 *   3. 减少用户/内核切换
 */

23.4 内核 QUIC 实现方向讨论

内核社区曾多次讨论是否将 QUIC 实现到内核中:

争议点:
  支持方:
    - 性能:内核 QUIC 可避免用户/内核边界拷贝
    - 与内核集成:可利用 TSO/GRO 等硬件卸载
    - 统一管理:与 TCP 一样可用 sysctl 调优
    - XDP/eBPF 集成更容易

  反对方:
    - 复杂性:QUIC 包含 TLS、流控、多流等复杂逻辑
    - 维护成本:QUIC 协议仍在快速演化(v1 -> v2 -> ...)
    - 用户态更灵活:Google、Cloudflare 等已有成熟实现
    - 内核旁路(DPDK/XDP)可同样实现高性能

当前主流方案:用 io_uring + 用户态 QUIC 库,通过 MSG_ZEROCOPY 减少拷贝:

应用程序
    |
    v
用户态 QUIC 库 (quiche/msquic)
    |  TLS 1.3 加密
    v
io_uring sendmsg (zero-copy UDP)
    |
    v
内核 UDP + XDP 加速

23.5 XDP 加速 QUIC 的可行路径

XDP(eXpress Data Path)提供了一种绕过大部分内核协议栈的快速路径,可用于 QUIC 优化:

网卡收包
    |
    v
XDP 程序(eBPF,运行在驱动层)
    |
    |--> XDP_PASS   : 正常走内核协议栈(普通包)
    |
    |--> XDP_REDIRECT : 直接重定向到 AF_XDP socket
                        用户态 QUIC 程序直接读取
                        绕过 IP/UDP 协议栈,极低延迟

性能对比:
  普通 UDP recv():  ~3 μs/包
  XDP + AF_XDP:    ~0.3 μs/包(10倍提升)

23.6 内核 QUIC 的 RFC 探索

Linux 6.5+ 内核中已有一些为 QUIC 预留的基础设施:

net/handshake/          TLS 握手框架(Linux 6.2)
  功能:允许内核子系统(NFS、SMB)发起 TLS 握手
  QUIC 可复用此框架进行 QUIC 握手

include/net/udp.h       UDP early demux(fast path)
  udp_v4_early_demux()  连接 socket 的快速查找
  QUIC 的 Connection ID 路由可类比此机制实现

net/ipv4/udp_offload.c  UDP GSO/GRO
  udp_gso_segment()     UDP 分段卸载
  udp4_gro_receive()    UDP 接收合并
  QUIC 可利用 UDP GSO 实现批量发送

23.7 未来展望

近期(1-2 年):
  - 内核 UDP GRO/GSO 持续优化,使 QUIC 用户态实现受益
  - io_uring 零拷贝 UDP 发送路径完善
  - eBPF sk_reuseport 支持 QUIC Connection ID 路由

中期(2-5 年):
  - 可能出现内核态 QUIC 基础框架(类似 KTLS 模式)
  - 仅实现数据平面(加密/解密/确认),控制平面留在用户态
  - 网卡 QUIC offload 标准化

参考实现:
  Cloudflare quiche:  https://github.com/cloudflare/quiche
  Microsoft msquic:   https://github.com/microsoft/msquic
  内核 udp_gso:       net/ipv4/udp_offload.c
  内核 KTLS:         net/tls/tls_main.c

附录:关键数据结构内存布局

A.1 inet_timewait_sock vs tcp_sock 内存占用对比

struct tcp_sock(完整连接):
  ~2KB(含所有拥塞控制、重传、缓冲区字段)

struct inet_timewait_sock(TIME_WAIT 轻量版):
  ~200B(仅含 IP/端口/时间戳/序号等必要字段)

节省:90% 的内存
设计意义:高并发短连接服务器可能有数万个 TIME_WAIT,
          轻量化设计避免 OOM

A.2 sk_buff 控制块(TCP SKB CB)

/* include/net/tcp.h */
struct tcp_skb_cb {
    /* 序号信息(快路径访问) */
    __u32   seq;         /* 起始序号 */
    __u32   end_seq;     /* 结束序号 */
    __u32   ack_seq;     /* ACK 序号 */
    __u32   tcp_tw_isn;  /* TIME_WAIT -> SYN 复用时的 ISN */

    /* 时间戳(RACK 使用) */
    u64     tx_delivered_mstamp;
    u64     first_tx_mstamp;

    /* 标志 */
    __u8    tcp_flags;   /* TCP 头部标志的快速副本 */
    __u8    sacked;      /* SACK 评分板标志 */
    __u8    ip_dsfield;  /* TOS/DSCP */

    /* TSO 信息 */
    __u16   tcp_gso_size;
};
/* 存储在 skb->cb[48] 中,无额外内存分配 */

A.3 TCP 连接建立的时序图(含 TFO)

                    普通 TCP                           TFO(第二次连接)
客户端                          服务端    客户端                          服务端
  |                               |         |                               |
  |-------- SYN ----------------->|         |-------- SYN+DATA+Cookie ----->|
  |         t=0                   |         |         t=0                   |
  |                               |         |                               | 验证 Cookie
  |                               |         |                               | 创建 socket
  |                               |         |                               | 数据入队
  |<-------- SYN+ACK ------------|         |<-------- SYN+ACK+DATA --------|
  |         t=RTT                 |         |         t=RTT                 |
  |                               |         |                               |
  |-------- ACK ----------------->|         |-------- ACK ----------------->|
  |         t=1.5*RTT             |         |         t=1.5*RTT             |
  |                               |         |                               |
  |-------- DATA ---------------->|         服务端应用已在 t=RTT 收到数据!
  |         t=1.5*RTT             |
  |                               |
  服务端应用在 t=2*RTT 才收到数据

节省:0.5~1 RTT 的首包延迟

附录:常用内核调试工具

B.1 使用 ss 命令查看 TCP 内部状态

# 查看 TCP 拥塞控制信息(需要 ss 7.x 以上)
ss -tni 'dst 192.168.1.1'
# 输出示例:
#   cubic wscale:7,7 rto:204 rtt:0.5/0.25 ato:40 mss:1460
#   cwnd:10 ssthresh:10 bytes_sent:123456 bytes_retrans:0
#   bytes_acked:123456 bytes_received:98765 segs_out:100
#   segs_in:95 data_segs_out:90 data_segs_in:80 send 233.6Mbps
#   pacing_rate 280.3Mbps delivery_rate 233.6Mbps

# 查看 TIME_WAIT 数量
ss -tan state time-wait | wc -l

# 查看监听 socket 的 accept queue 状态
ss -tlnp
# Recv-Q 列 = 当前 accept queue 长度
# Send-Q 列 = 最大 accept queue 长度(listen backlog)

B.2 使用 /proc/net/tcp 分析连接

# 查看 TCP 连接统计
cat /proc/net/netstat | grep -E "TcpExt:"

# 重要计数器:
# TCPLostRetransmit      : 丢失重传次数(RACK 检测)
# TCPFastRetrans         : 快速重传次数
# TCPSlowStartRetrans    : 慢启动重传次数
# TCPTimeouts            : RTO 超时次数
# TCPFastOpenActive      : TFO 主动连接次数
# TCPFastOpenPassive     : TFO 被动连接次数
# TCPFastOpenBlackhole   : TFO 黑洞检测次数

B.3 使用 eBPF/tracing 调试 TCP

# 使用 bpftrace 追踪 RTO 超时事件
bpftrace -e 'kprobe:tcp_retransmit_timer {
    printf("RTO: pid=%d comm=%s\n", pid, comm);
}'

# 使用 perf 分析 TCP 热点函数
perf top -e cycles:k --call-graph dwarf -- sleep 10 | grep tcp

# 使用 ftrace 追踪 TCP 状态转换
echo 'tcp_set_state' > /sys/kernel/debug/tracing/set_ftrace_filter
echo 'function_graph' > /sys/kernel/debug/tracing/current_tracer

由 Claude Code 分析生成