上一篇我们拆解了 IP
层的路由查找与转发。当
ip_local_deliver_finish() 根据协议号分发到
tcp_v4_rcv() 时,包就进入了 TCP 的领地。
TCP 是内核网络栈中最复杂的协议实现——一个
struct tcp_sock 有上百个字段,SYN Queue 和
Accept Queue 的交互涉及锁、定时器和无状态 Cookie,TIME_WAIT
用轻量级 socket 节省内存。如果你遇到过这些问题:
- 服务启动时
ss -lnt看到Recv-Q堆积,accept()来不及消费——瓶颈在 SYN Queue 还是 Accept Queue? - SYN Flood 时内核日志刷
possible SYN flooding——SYN Cookie 到底怎么工作的? netstat显示大量 TIME_WAIT——内核怎么存这些连接?能复用吗?- 想用 TCP Fast Open 省一个 RTT——内核到底怎么校验 Cookie 的?
本文从内核源码逐一回答。
一、TCP 连接的内核数据结构层次
TCP 连接在内核中不是一个简单的结构体,而是一个四层继承链:
struct sock_common // 最基础:五元组、状态
└─ struct sock // 通用 socket:缓冲区、定时器、backlog
└─ struct inet_sock // IPv4 特有:IP 地址、端口
└─ struct inet_connection_sock // 面向连接:Accept Queue、拥塞控制
└─ struct tcp_sock // TCP 特有:序列号、窗口、SACK1.1 struct sock 中的关键字段
struct sock(include/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_sock(include/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——它还负责:
- 更新统计计数器(
/proc/net/snmp中的CurrEstab) - 触发
tracepoint(
tracepoint:tcp:tcp_set_state) - 通知连接跟踪模块
- 在进入
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_sock(include/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 防御
4.1 SYN Cookie 原理
当 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
4.2 SYN Cookie 的代价
SYN Cookie 是无状态的,所以有几个限制:
| 限制 | 原因 |
|---|---|
| MSS 只有 8 种选择 | 只有 3 bit 编码空间 |
| 不支持 Window Scale | SYN+ACK 中不能带(因为没有存储) |
| 不支持 SACK | 同上 |
| 不支持 TCP Timestamps(可选) | 编码空间有限 |
这意味着在 SYN Cookie 激活期间,连接的性能可能下降——没有 Window Scale 限制了窗口大小,没有 SACK 限制了丢包恢复效率。
4.3 SYN Cookie 相关参数
# 启用 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,服务器回复数据到受害者),但有个边缘情况:
- Cookie 泄露:如果攻击者获取了某客户端 IP 的有效 Cookie,可以伪造该 IP 发送携带数据的 SYN
- 密钥轮换:内核支持两把密钥同时有效(
TCP_FASTOPEN_KEY_MAX = 2),轮换时旧密钥仍可验证
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_sock(include/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_LENTIME_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) // 释放完整 socket6.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);
}
'9.3 监控 SYN Cookie 激活
# 追踪 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 连接管理是内核网络栈中最精密的子系统:
- 两级队列模型:SYN Queue 用轻量级
request_sock(几百字节),Accept Queue 用完整tcp_sock——分离握手和应用的内存开销 - TCP_NEW_SYN_RECV:Linux 4.10+ 的优化,SYN Queue 中不再创建完整 socket,大幅降低 SYN Flood 内存消耗
- SYN Cookie:SYN Queue 满时的无状态退化方案,ISN 编码四元组哈希——代价是丢失 Window Scale 和 SACK 协商
- TCP Fast Open:SipHash Cookie 验证,零 RTT 数据传输——密钥支持双密钥轮换
- TIME_WAIT
轻量化:
inet_timewait_sock仅 ~200B,服务器可维护数十万 TIME_WAIT 而不耗尽内存 tcp_set_state()是统一入口:所有状态转换都经过它,用tracepoint:tcp:tcp_set_state即可追踪全部状态机行为
下一篇我们进入 TCP 数据传输——拆解
tcp_sendmsg/tcp_recvmsg
的收发路径、TSQ 限流和拥塞控制框架。
参考文献
- Linux
内核源码,
include/net/tcp.h,6.8(tcp_v4_rcv声明于第 335 行,tcp_conn_request声明于第 2201 行) - Linux
内核源码,
include/net/inet_connection_sock.h,6.8(inet_connection_sock定义于第 82 行,icsk_accept_queue位于第 85 行) - Linux
内核源码,
include/net/request_sock.h,6.8(request_sock_queue定义于第 175 行,request_sock定义于第 53 行) - Linux
内核源码,
include/net/tcp_states.h,6.8(TCP 状态枚举定义于第 13 行) - Linux
内核源码,
include/net/inet_timewait_sock.h,6.8(inet_timewait_sock定义于第 33 行) - RFC 7413, “TCP Fast Open”, Y. Cheng et al., 2014
- Linux
内核文档,
Documentation/networking/ip-sysctl.rst
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Linux 网络子系统深度拆解】TCP 内核实现(下):数据传输与拥塞控制
tcp_sendmsg 把用户数据拷到 sk_buff 就完事了?远没有。后面还有 Nagle 合并、TSQ 限流、cwnd/rwnd 双窗口门控、RACK-TLP 丢包检测、拥塞状态机五态跳转、sk_pacing_rate 软件限速。本文从 Linux 6.6 内核源码拆解 TCP 数据传输的完整路径——从 send() 到 ACK 处理——以及拥塞控制框架 tcp_congestion_ops 的可插拔架构。
【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 头缓存优化。
【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 集成的完整实现。
【Linux 网络子系统深度拆解】UDP 内核实现与 socket lookup 优化
UDP 简单?在内核中它一点都不简单。双哈希表 socket 查找、SO_REUSEPORT 多核分发、Early Demux 路由缓存、UDP GRO 聚合、reader_queue 无锁读、forward allocation 内存管理、UDP 封装(ESP/L2TP/VXLAN)——本文从 Linux 6.6 内核源码拆解 UDP 的每一个优化细节。