土法炼钢兴趣小组的算法知识备份

【Linux 网络子系统深度拆解】TCP 内核实现(上):连接管理与状态机

文章导航

分类入口
linuxnetworking
标签入口
#tcp#linux-kernel#syn-queue#accept-queue#request-sock#tcp-fast-open#syn-cookie#time-wait#tcp-state-machine#bpftrace

目录

上一篇我们拆解了 IP 层的路由查找与转发。当 ip_local_deliver_finish() 根据协议号分发到 tcp_v4_rcv() 时,包就进入了 TCP 的领地。

TCP 是内核网络栈中最复杂的协议实现——一个 struct tcp_sock 有上百个字段,SYN Queue 和 Accept Queue 的交互涉及锁、定时器和无状态 Cookie,TIME_WAIT 用轻量级 socket 节省内存。如果你遇到过这些问题:

本文从内核源码逐一回答。

TCP 三次握手:SYN Queue → Accept Queue 内核流程

一、TCP 连接的内核数据结构层次

TCP 连接在内核中不是一个简单的结构体,而是一个四层继承链:

struct sock_common          // 最基础:五元组、状态
  └─ struct sock            // 通用 socket:缓冲区、定时器、backlog
      └─ struct inet_sock   // IPv4 特有:IP 地址、端口
          └─ struct inet_connection_sock  // 面向连接:Accept Queue、拥塞控制
              └─ struct tcp_sock         // TCP 特有:序列号、窗口、SACK

1.1 struct sock 中的关键字段

struct sockinclude/net/sock.h:341)是所有 socket 的基类:

struct sock {
    struct sock_common __sk_common;
    // sk_state = __sk_common.skc_state  (行 365)
    // TCP 状态:TCP_LISTEN, TCP_ESTABLISHED, ...

    struct sk_buff_head sk_receive_queue;   // 行 390: 接收队列
    struct rb_root      tcp_rtx_queue;      // 行 441: 重传队列(红黑树)

    u32 sk_ack_backlog;        // 行 477: Accept Queue 当前长度
    u32 sk_max_ack_backlog;    // 行 478: Accept Queue 最大长度
    // = min(somaxconn, listen() 的 backlog 参数)

    struct timer_list sk_timer;  // 行 447: 通用定时器
};

sk_ack_backlog / sk_max_ack_backlog 控制 Accept Queue 的大小——每次 accept() 取走一个连接,sk_ack_backlog 减一。

1.2 struct inet_connection_sock

struct inet_connection_sockinclude/net/inet_connection_sock.h:82)是面向连接协议(TCP、SCTP、DCCP)的通用结构:

struct inet_connection_sock {
    struct inet_sock          icsk_inet;
    struct request_sock_queue icsk_accept_queue;  // 行 85: SYN + Accept 队列

    unsigned long  icsk_timeout;                  // 行 88
    struct timer_list icsk_retransmit_timer;       // 行 89: RTO 重传定时器
    struct timer_list icsk_delack_timer;           // 行 90: 延迟 ACK 定时器
    __u32          icsk_rto;                       // 行 91: 当前 RTO 值

    const struct tcp_congestion_ops *icsk_ca_ops;  // 行 95: 拥塞控制算法
};

注意 icsk_accept_queue 同时管理 SYN Queue 和 Accept Queue——这是一个统一的容器。

1.3 定时器常量

TCP 连接需要管理多个定时器(inet_connection_sock.h:144):

常量 用途
ICSK_TIME_RETRANS 1 RTO 重传定时器
ICSK_TIME_DACK 2 延迟 ACK 定时器
ICSK_TIME_PROBE0 3 零窗口探针定时器
ICSK_TIME_LOSS_PROBE 5 尾部丢包探针(TLP)
ICSK_TIME_REO_TIMEOUT 6 乱序超时

二、TCP 状态机在内核中的实现

2.1 状态枚举

TCP 状态定义在 include/net/tcp_states.h:13

enum {
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,          // 客户端发出 SYN
    TCP_SYN_RECV,          // 服务端收到 SYN(完整 socket)
    TCP_FIN_WAIT1,         // 已发 FIN,等对端 ACK
    TCP_FIN_WAIT2,         // 对端已 ACK 我们的 FIN
    TCP_TIME_WAIT,         // 等待 2MSL
    TCP_CLOSE,             // 连接关闭
    TCP_CLOSE_WAIT,        // 对端已关,等本地关
    TCP_LAST_ACK,          // 已发 FIN,等最后 ACK
    TCP_LISTEN,            // 监听
    TCP_CLOSING,           // 双方同时关闭
    TCP_NEW_SYN_RECV,      // Linux 4.10+:SYN Queue 中的轻量级状态
};

2.2 TCP_NEW_SYN_RECV 的意义

传统实现中,收到 SYN 后内核创建一个完整的 struct sock(TCP_SYN_RECV 状态),占用大量内存。Linux 4.10 引入了 TCP_NEW_SYN_RECV——在 SYN Queue 中只创建轻量级的 request_sock,直到收到第三步 ACK 才创建完整 socket。这大幅减少了 SYN Flood 攻击时的内存消耗。

2.3 tcp_set_state():状态转换的统一入口

所有 TCP 状态转换都通过 tcp_set_state()include/net/tcp.h:1457)完成:

void tcp_set_state(struct sock *sk, int state);

这个函数不只是设置 sk->sk_state——它还负责:

  1. 更新统计计数器(/proc/net/snmp 中的 CurrEstab
  2. 触发 tracepoint(tracepoint:tcp:tcp_set_state
  3. 通知连接跟踪模块
  4. 在进入 TCP_CLOSE 时清理资源

用 bpftrace 追踪所有状态转换:

bpftrace -e '
tracepoint:tcp:tcp_set_state {
    printf("%-16s %-6d %s → %s\n",
        comm, pid,
        @states[args->oldstate],
        @states[args->newstate]);
}
BEGIN {
    @states[1] = "ESTABLISHED";
    @states[2] = "SYN_SENT";
    @states[3] = "SYN_RECV";
    @states[4] = "FIN_WAIT1";
    @states[5] = "FIN_WAIT2";
    @states[6] = "TIME_WAIT";
    @states[7] = "CLOSE";
    @states[8] = "CLOSE_WAIT";
    @states[9] = "LAST_ACK";
    @states[10] = "LISTEN";
    @states[11] = "CLOSING";
    @states[12] = "NEW_SYN_RECV";
}
'

三、连接建立:SYN Queue 与 Accept Queue

3.1 两级队列模型

TCP 三次握手在内核中使用两级队列:

客户端 SYN  →  [SYN Queue]  →  客户端 ACK  →  [Accept Queue]  →  accept()
               request_sock       tcp_check_req()    完整 tcp_sock      inet_csk_accept()
               轻量级对象         创建完整 socket     等待用户态取走

两个队列由 request_sock_queue 统一管理(include/net/request_sock.h:175):

struct request_sock_queue {
    spinlock_t    rskq_lock;               // 保护队列的自旋锁
    u8            rskq_defer_accept;        // TCP_DEFER_ACCEPT 延迟标志

    u32           synflood_warned;          // SYN Flood 告警标记
    atomic_t      qlen;                    // SYN Queue 长度
    atomic_t      young;                   // 未重传过的 SYN 请求数

    struct request_sock *rskq_accept_head;  // Accept Queue 头
    struct request_sock *rskq_accept_tail;  // Accept Queue 尾

    struct fastopen_queue fastopenq;        // TFO 队列
};

3.2 SYN Queue:request_sock

收到 SYN 后,内核创建 request_sockinclude/net/request_sock.h:53):

struct request_sock {
    struct sock_common __req_common;    // 五元组

    struct request_sock *dl_next;       // SYN Queue 链表
    u16           mss;                  // 客户端 MSS
    u8            num_retrans;          // SYN+ACK 已重传次数
    u8            syncookie:1;          // 是否由 SYN Cookie 生成
    u8            num_timeout:7;        // 超时次数
    u32           ts_recent;            // 客户端时间戳
    struct timer_list rsk_timer;        // SYN+ACK 重传定时器
    struct sock   *sk;                  // 握手完成后指向完整 socket
    u32           timeout;              // 当前超时值
};

request_sock 比完整的 tcp_sock 小得多——几百字节 vs 数千字节。这是 SYN Flood 防御的关键:即使半连接队列有上万条目,内存消耗仍然可控。

3.3 连接建立的函数调用链

服务端收到 SYN

tcp_v4_rcv(skb)                          // include/net/tcp.h:335
  → tcp_rcv_state_process(sk, skb)       // 状态机主处理
    → tcp_conn_request(...)              // include/net/tcp.h:2201
      → 分配 request_sock
      → 计算 ISN(初始序列号)
      → 发送 SYN+ACK: tcp_make_synack()  // include/net/tcp.h:481
      → 加入 SYN Queue: inet_csk_reqsk_queue_hash_add()

服务端收到第三步 ACK

tcp_v4_rcv(skb)
  → tcp_check_req(sk, skb, req, ...)     // include/net/tcp.h:397
    → 验证 ACK 序列号
    → tcp_v4_syn_recv_sock(...)           // 创建完整 tcp_sock
    → inet_csk_reqsk_queue_add(sk, req, child)  // 移入 Accept Queue

用户态 accept()

sys_accept() → inet_accept()
  → inet_csk_accept(sk, flags, err, kern)  // include/net/inet_connection_sock.h:256
    → 从 Accept Queue 头部取出 request_sock
    → 返回关联的完整 socket
    → sk_ack_backlog--

3.4 队列大小控制

两个队列的大小由以下参数共同决定:

// Accept Queue 最大值
sk->sk_max_ack_backlog = min(somaxconn, listen(backlog));
// somaxconn: sysctl net.core.somaxconn,默认 4096
// backlog: 应用调用 listen(fd, backlog) 时指定

// SYN Queue 最大值
// 由 sk_max_ack_backlog 间接控制
// 检查: inet_csk_reqsk_queue_is_full()
// = qlen > sk_max_ack_backlog

重要变化:Linux 4.4 之后,SYN Queue 和 Accept Queue 共享 sk_max_ack_backlog 作为上限。之前 SYN Queue 有独立的 tcp_max_syn_backlog 上限,现在这个参数的影响较小。

# 查看当前队列状态
ss -lnt
# Recv-Q = Accept Queue 当前长度
# Send-Q = Accept Queue 最大长度 (sk_max_ack_backlog)

3.5 队列满时的行为

Accept Queue 满

// include/net/sock.h:1003
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
    return READ_ONCE(sk->sk_ack_backlog) > READ_ONCE(sk->sk_max_ack_backlog);
}

当 Accept Queue 满时,内核对新完成握手的连接的处理取决于 tcp_abort_on_overflow: - = 0(默认):丢弃第三步 ACK,让客户端重传——给应用一个消费队列的机会 - = 1:直接发送 RST 拒绝连接

SYN Queue 满(且 tcp_syncookies 未启用): - 丢弃新的 SYN 包 - 内核日志:possible SYN flooding on port XXX. Sending cookies.


四、SYN Cookie:无状态的 SYN Flood 防御

当 SYN Queue 满时,如果 net.ipv4.tcp_syncookies = 1,内核不再创建 request_sock,而是将连接信息编码到 SYN+ACK 的初始序列号(ISN)中:

ISN 编码:
  ┌─────────────┬──────────┬──────────────────────┐
  │  时间戳(5b)  │  MSS(3b) │  Hash(源IP,目的IP,   │
  │              │          │  源端口,目的端口,密钥) │
  └─────────────┴──────────┴──────────────────────┘

流程:

1. SYN 到达,SYN Queue 满
2. 不创建 request_sock
3. 用 Hash(四元组, 密钥) + 时间戳 + MSS 索引 构造 ISN
4. 发送 SYN+ACK(ISN = cookie)
5. 收到 ACK(ack_seq = cookie + 1)
6. 从 cookie 反解出 MSS、验证 Hash
7. 直接创建完整 tcp_sock,跳过 SYN Queue

SYN Cookie 是无状态的,所以有几个限制:

限制 原因
MSS 只有 8 种选择 只有 3 bit 编码空间
不支持 Window Scale SYN+ACK 中不能带(因为没有存储)
不支持 SACK 同上
不支持 TCP Timestamps(可选) 编码空间有限

这意味着在 SYN Cookie 激活期间,连接的性能可能下降——没有 Window Scale 限制了窗口大小,没有 SACK 限制了丢包恢复效率。

# 启用 SYN Cookie(默认 1)
sysctl net.ipv4.tcp_syncookies
# 0: 禁用
# 1: SYN Queue 满时自动启用
# 2: 始终启用

# SYN+ACK 重传次数
sysctl net.ipv4.tcp_synack_retries  # 默认 5
# 每次重传间隔翻倍:1s → 2s → 4s → 8s → 16s
# 5 次重传约 31 秒后放弃

五、TCP Fast Open:零 RTT 连接建立

5.1 TFO 原理

标准 TCP 三次握手需要 1 个 RTT 才能开始传数据。TCP Fast Open(RFC 7413)允许在 SYN 包中就携带数据,省去一个 RTT。

首次连接(获取 Cookie):

客户端 → SYN + TFO Cookie 请求
服务端 → SYN+ACK + TFO Cookie
(正常三次握手,Cookie 缓存在客户端)

后续连接(使用 Cookie):

客户端 → SYN + TFO Cookie + 数据
服务端 → 验证 Cookie → 直接 ESTABLISHED + 处理数据
       → SYN+ACK + 数据响应
客户端 → ACK(连接已建立,数据已送达)

5.2 TFO 内核实现

TFO Cookie 使用 SipHash 算法生成(include/net/tcp.h:1909):

struct tcp_fastopen_context {
    siphash_key_t key[TCP_FASTOPEN_KEY_MAX]; // 最多 2 把密钥(支持轮换)
    int           num;                        // 当前有效密钥数
    struct rcu_head rcu;
};

// TFO Cookie 结构 (include/linux/tcp.h:90)
struct tcp_fastopen_cookie {
    __le64 val[...];     // Cookie 值(默认 8 字节)
    s8     len;          // 4-16 字节
    bool   exp;          // RFC6994 实验格式
};

Cookie 生成过程:SipHash(客户端IP, 密钥) → 8 字节 Cookie。

TFO 队列在 request_sock_queue 中(include/net/request_sock.h:155):

struct fastopen_queue {
    struct request_sock *rskq_rst_head; // 发送过 RST 的 TFO 请求
    struct request_sock *rskq_rst_tail;
    spinlock_t lock;
    int        qlen;      // 待处理 TFO 请求数
    int        max_qlen;  // TFO 最大队列长度(!= 0 表示 TFO 已启用)
    struct tcp_fastopen_context __rcu *ctx; // Cookie 加密上下文
};

5.3 TFO 的安全考虑

TFO Cookie 防止了放大攻击(攻击者伪造源 IP 发 SYN+Data,服务器回复数据到受害者),但有个边缘情况:

5.4 TFO 配置

# 启用 TFO(位掩码)
sysctl net.ipv4.tcp_fastopen
# bit 0 (1): 客户端启用
# bit 1 (2): 服务端启用
# bit 2 (4): 客户端无 Cookie 时也允许数据 in SYN
# 常用值: 3 (客户端+服务端)

# TFO 黑洞检测超时
sysctl net.ipv4.tcp_fastopen_blackhole_timeout_sec  # 默认 0(禁用)
# 中间设备可能丢弃带数据的 SYN,这个参数让内核自动回退

# TFO 最大队列长度(per-listener)
# 在应用代码中设置: setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen))

六、连接关闭与 TIME_WAIT

6.1 四次挥手的内核路径

主动关闭方(调用 close()):

tcp_close(sk, timeout)          // include/net/tcp.h:410
  → tcp_set_state(sk, TCP_FIN_WAIT1)
  → tcp_send_fin(sk)            // 发送 FIN
  → 等待对端 ACK → TCP_FIN_WAIT2
  → 等待对端 FIN → TCP_TIME_WAIT
  → tcp_time_wait(sk, TCP_TIME_WAIT, timeo)

被动关闭方(收到 FIN):

tcp_v4_rcv() → tcp_v4_do_rcv() → tcp_rcv_state_process()
  → tcp_fin(sk)                 // include/net/tcp.h:642
  → tcp_set_state(sk, TCP_CLOSE_WAIT)
  → 应用调用 close()
  → tcp_set_state(sk, TCP_LAST_ACK)
  → 发送 FIN → 等待 ACK → TCP_CLOSE

6.2 inet_timewait_sock:轻量级 TIME_WAIT

当连接进入 TIME_WAIT 时,内核不会保留完整的 tcp_sock(数千字节),而是替换为轻量级的 inet_timewait_sockinclude/net/inet_timewait_sock.h:33):

struct inet_timewait_sock {
    struct sock_common __tw_common;       // 五元组
    __u32          tw_mark;               // fwmark
    volatile unsigned char tw_substate;   // TIME_WAIT 子状态
    unsigned char  tw_rcv_wscale;         // 窗口缩放
    __be16         tw_sport;              // 源端口
    unsigned int   tw_transparent:1;
    unsigned int   tw_flowlabel:20;
    unsigned int   tw_usec_ts:1;
    unsigned int   tw_tos:8;
    u32            tw_txhash;
    u32            tw_priority;
    struct timer_list tw_timer;           // TIME_WAIT 定时器
    struct inet_bind_bucket  *tw_tb;
    struct inet_bind2_bucket *tw_tb2;
};

对比:

结构体 大小 说明
tcp_sock ~2KB 完整连接:序列号、窗口、拥塞状态、重传队列
inet_timewait_sock ~200B 仅五元组 + 定时器

这个设计让服务器可以同时维护数十万个 TIME_WAIT 连接而不会耗尽内存。

6.3 TIME_WAIT 的持续时间

// include/net/tcp.h:125
#define TCP_TIMEWAIT_LEN  (60 * HZ)   // 60 秒
#define TCP_FIN_TIMEOUT   TCP_TIMEWAIT_LEN

TIME_WAIT 函数链:

tcp_time_wait(sk, state, timeo)       // include/net/tcp.h:56
  → inet_twsk_alloc(sk, dr, state)    // 分配轻量级 socket
  → inet_twsk_hashdance_schedule(tw, sk, hashinfo, timeo)
    // 1. 从连接哈希表摘除完整 socket
    // 2. 插入 TIME_WAIT socket
    // 3. 启动 tw_timer 定时器
  → sock_put(sk)                       // 释放完整 socket

6.4 TIME_WAIT 期间收到包

tcp_timewait_state_process()include/net/tcp.h:394)处理 TIME_WAIT 状态中收到的包:

enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw,
                           struct sk_buff *skb,
                           const struct tcphdr *th);

返回值:

状态 含义
TCP_TW_SUCCESS 正常处理(如重复 FIN,重新发 ACK)
TCP_TW_RST 发送 RST(异常包)
TCP_TW_ACK 需要发送 ACK
TCP_TW_SYN 收到新 SYN,可以复用端口

6.5 tcp_tw_reuse:TIME_WAIT 端口复用

# 允许复用 TIME_WAIT 端口(作为客户端连接时)
sysctl net.ipv4.tcp_tw_reuse  # 默认 2
# 0: 禁用
# 1: 启用(仅限 loopback)
# 2: 启用(所有接口,Linux 4.6+ 默认)

tcp_tw_reuse 允许客户端在 connect() 时复用处于 TIME_WAIT 的本地端口,条件是新连接的时间戳大于旧连接——利用 TCP Timestamps 保证不会混淆新旧连接的包。

注意tcp_tw_recycle 参数在 Linux 4.12 中已被移除(因为在 NAT 环境下导致连接失败)。


七、SYN 重试与超时

7.1 客户端 SYN 重试

// include/net/tcp.h:109
#define TCP_SYN_RETRIES   6    // 默认 6 次

重试间隔指数退避:1s → 2s → 4s → 8s → 16s → 32s → 放弃(总计约 127 秒)。

可调整:

sysctl net.ipv4.tcp_syn_retries  # 默认 6
# 设为 2: 约 7 秒超时(1+2+4)
# 设为 1: 约 3 秒超时(1+2)

7.2 服务端 SYN+ACK 重试

// include/net/tcp.h:118
#define TCP_SYNACK_RETRIES  5   // 默认 5 次

重试间隔同样指数退避:1s → 2s → 4s → 8s → 16s → 放弃(总计约 31 秒)。

sysctl net.ipv4.tcp_synack_retries  # 默认 5
# 在 SYN Flood 时可减小到 1-2,更快释放 SYN Queue 中的条目

八、TCP_DEFER_ACCEPT:延迟 Accept

TCP_DEFER_ACCEPT 是一个 socket 选项,让内核在收到第三步 ACK 后不立即将连接放入 Accept Queue,而是等到收到第一个数据包才通知应用。

// request_sock_queue 中的标志
struct request_sock_queue {
    // ...
    u8 rskq_defer_accept;  // 行 177: TCP_DEFER_ACCEPT 启用标志
    // ...
};

适用场景:HTTP 服务器——客户端三次握手后立即发送请求,TCP_DEFER_ACCEPT 让内核等到请求到达才唤醒 accept(),避免空连接占用 Accept Queue。

// 应用代码
int val = 5;  // 最多等 5 秒
setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val));

九、可观测性实战

9.1 追踪三次握手延迟

# 从 SYN 收到到 ESTABLISHED 的延迟
bpftrace -e '
tracepoint:tcp:tcp_set_state
/args->newstate == 1/  // TCP_ESTABLISHED
{
    @handshake_us[comm] = hist(
        (nsecs - @syn_time[args->skaddr]) / 1000);
    delete(@syn_time[args->skaddr]);
}
tracepoint:tcp:tcp_set_state
/args->newstate == 12/  // TCP_NEW_SYN_RECV
{
    @syn_time[args->skaddr] = nsecs;
}
'

9.2 监控 Accept Queue 溢出

# 追踪 Accept Queue 满导致的丢包
bpftrace -e '
kprobe:tcp_v4_syn_recv_sock {
    @attempts = count();
}
kretprobe:tcp_v4_syn_recv_sock /retval == 0/ {
    @overflow = count();
    printf("Accept queue overflow at %s\n", strftime("%H:%M:%S", nsecs));
}
interval:s:5 {
    printf("--- syn_recv attempts: %d, overflows: %d ---\n",
        @attempts, @overflow);
    clear(@attempts);
    clear(@overflow);
}
'
# 追踪 SYN Cookie 的发送和验证
bpftrace -e '
kprobe:cookie_v4_init_sequence {
    @syncookie_sent = count();
    printf("[%s] SYN Cookie sent from CPU %d\n",
        strftime("%H:%M:%S", nsecs), cpu);
}
kprobe:cookie_v4_check {
    @syncookie_check = count();
}
interval:s:5 {
    printf("--- SYN Cookies: sent=%d checked=%d ---\n",
        @syncookie_sent, @syncookie_check);
    clear(@syncookie_sent);
    clear(@syncookie_check);
}
'

9.4 TIME_WAIT 统计

# TIME_WAIT socket 数量和端口复用频率
ss -s | grep timewait

# 追踪 TIME_WAIT 创建和销毁
bpftrace -e '
kprobe:tcp_time_wait {
    @tw_created = count();
}
kprobe:inet_twsk_free {
    @tw_freed = count();
}
interval:s:10 {
    printf("--- TIME_WAIT: created=%d freed=%d ---\n",
        @tw_created, @tw_freed);
    clear(@tw_created);
    clear(@tw_freed);
}
'

9.5 ss 命令深度用法

# 查看所有 LISTEN socket 的队列状态
ss -lnt
# State   Recv-Q  Send-Q  Local Address:Port
# LISTEN  0       4096    0.0.0.0:80
# Recv-Q = Accept Queue 当前长度
# Send-Q = sk_max_ack_backlog

# 查看半连接队列
ss -n state syn-recv

# 查看 TIME_WAIT 统计
ss -n state time-wait | wc -l

# 查看连接的详细 TCP 信息
ss -tiep
# 显示 RTO, RTT, cwnd, retrans 等

十、关键参数调优汇总

参数 默认值 作用 对应内核实现
net.core.somaxconn 4096 Accept Queue 最大长度上限 sk_max_ack_backlog
net.ipv4.tcp_max_syn_backlog 1024 SYN Queue 长度(影响较小) reqsk_queue 辅助上限
net.ipv4.tcp_syncookies 1 SYN Cookie 开关 cookie_v4_init_sequence
net.ipv4.tcp_syn_retries 6 客户端 SYN 重试次数 icsk_retransmit_timer
net.ipv4.tcp_synack_retries 5 服务端 SYN+ACK 重试次数 rsk_timer
net.ipv4.tcp_abort_on_overflow 0 Accept Queue 满时发 RST sk_acceptq_is_full
net.ipv4.tcp_fin_timeout 60 FIN_WAIT2 超时(秒) TCP_FIN_TIMEOUT
net.ipv4.tcp_tw_reuse 2 TIME_WAIT 端口复用 tcp_twsk_unique
net.ipv4.tcp_fastopen 1 TFO 位掩码 TFO_* 常量
net.ipv4.tcp_max_tw_buckets 262144 TIME_WAIT 最大数量 超限直接销毁

高并发短连接场景调优

# 增大 Accept Queue(配合应用 listen() backlog)
sysctl -w net.core.somaxconn=65535

# 加速 SYN+ACK 重试超时
sysctl -w net.ipv4.tcp_synack_retries=2

# 启用 TFO
sysctl -w net.ipv4.tcp_fastopen=3

# TIME_WAIT 复用
sysctl -w net.ipv4.tcp_tw_reuse=1

# 减少 FIN_WAIT2 超时
sysctl -w net.ipv4.tcp_fin_timeout=15

十一、总结

TCP 连接管理是内核网络栈中最精密的子系统:

  1. 两级队列模型:SYN Queue 用轻量级 request_sock(几百字节),Accept Queue 用完整 tcp_sock——分离握手和应用的内存开销
  2. TCP_NEW_SYN_RECV:Linux 4.10+ 的优化,SYN Queue 中不再创建完整 socket,大幅降低 SYN Flood 内存消耗
  3. SYN Cookie:SYN Queue 满时的无状态退化方案,ISN 编码四元组哈希——代价是丢失 Window Scale 和 SACK 协商
  4. TCP Fast Open:SipHash Cookie 验证,零 RTT 数据传输——密钥支持双密钥轮换
  5. TIME_WAIT 轻量化inet_timewait_sock 仅 ~200B,服务器可维护数十万 TIME_WAIT 而不耗尽内存
  6. tcp_set_state() 是统一入口:所有状态转换都经过它,用 tracepoint:tcp:tcp_set_state 即可追踪全部状态机行为

下一篇我们进入 TCP 数据传输——拆解 tcp_sendmsg/tcp_recvmsg 的收发路径、TSQ 限流和拥塞控制框架。


参考文献

  1. Linux 内核源码,include/net/tcp.h,6.8(tcp_v4_rcv 声明于第 335 行,tcp_conn_request 声明于第 2201 行)
  2. Linux 内核源码,include/net/inet_connection_sock.h,6.8(inet_connection_sock 定义于第 82 行,icsk_accept_queue 位于第 85 行)
  3. Linux 内核源码,include/net/request_sock.h,6.8(request_sock_queue 定义于第 175 行,request_sock 定义于第 53 行)
  4. Linux 内核源码,include/net/tcp_states.h,6.8(TCP 状态枚举定义于第 13 行)
  5. Linux 内核源码,include/net/inet_timewait_sock.h,6.8(inet_timewait_sock 定义于第 33 行)
  6. RFC 7413, “TCP Fast Open”, Y. Cheng et al., 2014
  7. Linux 内核文档,Documentation/networking/ip-sysctl.rst

上一篇IP 层内核实现:路由查找、分片与转发

下一篇TCP 内核实现(下):数据传输与拥塞控制

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】TCP 内核实现(下):数据传输与拥塞控制

tcp_sendmsg 把用户数据拷到 sk_buff 就完事了?远没有。后面还有 Nagle 合并、TSQ 限流、cwnd/rwnd 双窗口门控、RACK-TLP 丢包检测、拥塞状态机五态跳转、sk_pacing_rate 软件限速。本文从 Linux 6.6 内核源码拆解 TCP 数据传输的完整路径——从 send() 到 ACK 处理——以及拥塞控制框架 tcp_congestion_ops 的可插拔架构。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】邻居子系统与 ARP:L2 地址解析的内核实现

IP 层知道下一跳是 10.0.0.1,但网卡发帧需要 MAC 地址。ARP 解析只是表面——底层是邻居子系统(neighbour subsystem)的完整状态机:NUD_INCOMPLETE → NUD_REACHABLE → NUD_STALE → NUD_DELAY → NUD_PROBE → NUD_FAILED。本文从 Linux 6.6 内核源码拆解 struct neighbour、neigh_table 双哈希表、ARP 请求/响应处理、NDP(IPv6)、Proxy ARP、GC 回收机制,以及 neigh_connected_output 快路径的 L2 头缓存优化。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】Socket 层内核实现:从 VFS 到协议栈的桥梁

你调用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 TCP 连接,底层发生了什么?内核分配了两个核心对象——VFS 层的 struct socket 和协议层的 struct sock,通过 proto_ops 和 proto 两张分发表,把文件系统语义的 read/write 翻译成协议语义的 tcp_sendmsg/tcp_recvmsg。本文从 Linux 6.6 内核源码拆解 socket 创建、双层分发、SO_REUSEPORT 多核分发、epoll 集成的完整实现。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】UDP 内核实现与 socket lookup 优化

UDP 简单?在内核中它一点都不简单。双哈希表 socket 查找、SO_REUSEPORT 多核分发、Early Demux 路由缓存、UDP GRO 聚合、reader_queue 无锁读、forward allocation 内存管理、UDP 封装(ESP/L2TP/VXLAN)——本文从 Linux 6.6 内核源码拆解 UDP 的每一个优化细节。


By .