找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1970

积分

0

好友

264

主题
发表于 昨天 18:35 | 查看: 8| 回复: 0

适用场景:网络故障排查、连接超时分析、TIME_WAIT 过多问题定位、TCP 参数调优。
环境要求:Linux 4.18+ (RHEL/CentOS 8+ / Ubuntu 20.04+),root 或 sudo 权限,Wireshark 3.0+/tcpdump 4.9+。

1️⃣ 实施步骤(核心内容)

架构与数据流说明

TCP 三次握手流程:

客户端                                服务器
   │                                     │
   │──────── SYN (seq=x) ────────────→  │  (1) 客户端发起连接
   │                                     │
   │←───── SYN-ACK (seq=y, ack=x+1) ────│  (2) 服务器确认并同步
   │                                     │
   │──────── ACK (ack=y+1) ───────────→  │  (3) 客户端确认
   │                                     │
   │========== 连接建立,开始传输 ==========│

TCP 四次挥手流程:

客户端                                服务器
   │                                     │
   │──────── FIN (seq=m) ─────────────→  │  (1) 客户端请求关闭
   │                                     │
   │←────── ACK (ack=m+1) ───────────────│  (2) 服务器确认
   │                                     │
   │←────── FIN (seq=n) ─────────────────│  (3) 服务器请求关闭
   │                                     │
   │──────── ACK (ack=n+1) ───────────→  │  (4) 客户端确认
   │                                     │
   │      [TIME_WAIT: 2MSL = 120秒]      │
   │                                     │
   └────────── 连接关闭 ──────────────────┘

关键组件:

  • SYN (Synchronize):同步序列号,请求建立连接
  • ACK (Acknowledgment):确认号,确认接收到的数据
  • FIN (Finish):结束标志,请求关闭连接
  • seq (Sequence Number):序列号,标识发送的数据字节
  • ack (Acknowledgment Number):确认号,期望接收的下一个字节
  • TIME_WAIT:主动关闭方等待 2MSL(最大报文段生存时间),默认 60 秒

Step 1: 环境准备与工具安装

目标: 安装 Wireshark/tcpdump,配置测试环境

环境信息(示例):

  • 测试客户端:192.168.1.100 (client / 2C4G / CentOS 8.5)
  • 测试服务器:192.168.1.200 (server / 2C4G / CentOS 8.5)
  • 测试端口:80 (HTTP) / 443 (HTTPS) / 3306 (MySQL)
  • 网络接口:eth0

RHEL/CentOS 命令:

# 1. 安装 tcpdump 和 Wireshark(命令行版 tshark)
yum install -y tcpdump wireshark-cli nc telnet nmap-ncat

# 2. 验证 tcpdump 版本
tcpdump --version
# 预期输出:tcpdump version 4.9.3 或更高

# 3. 检查网络接口
ip link show
# 预期输出:eth0、lo 等接口列表

# 4. 测试抓包权限
tcpdump -D
# 预期输出:
# 1.eth0 [Up, Running]
# 2.lo [Up, Running, Loopback]

# 5. 启动测试 HTTP 服务器(Python 简易服务器)
python3 -m http.server 8080 &
# 预期输出:Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

# 6. 验证服务监听
ss -tuln | grep 8080
# 预期输出:
# tcp   LISTEN 0      5            0.0.0.0:8080         0.0.0.0:*

Ubuntu/Debian 命令:

# 1. 安装 tcpdump 和 Wireshark
apt update && apt install -y tcpdump tshark netcat-openbsd telnet

# 2. 验证安装
tcpdump --version
# 预期输出:tcpdump version 4.99.0 或更高

# 3. 启动测试服务器(使用 nc)
nc -l -p 8080 &
# 或使用 Python
python3 -m http.server 8080 &

# 4. 验证监听
ss -tuln | grep 8080
# 预期输出:tcp   LISTEN 0      5      0.0.0.0:8080      0.0.0.0:*

关键参数解释:

  1. tcpdump -D:列出可用网络接口,确认抓包权限
  2. ss -tuln:查看 TCP/UDP 监听端口(-t=TCP, -u=UDP, -l=LISTEN, -n=数字显示)
  3. python3 -m http.server:快速启动 HTTP 服务器用于测试

执行前验证:

# 确认测试端口未被占用
ss -tuln | grep 8080
# 预期输出:无输出(端口空闲)

# 确认 tcpdump 有权限抓包
tcpdump -i eth0 -c 1
# 预期输出:抓到 1 个数据包(按 Ctrl+C 停止)

执行后验证:

# 验证服务启动成功
curl -I http://127.0.0.1:8080/
# 预期输出:
# HTTP/1.0 200 OK
# Server: SimpleHTTP/0.6 Python/3.6.8

# 验证外部可访问(如果防火墙已放行)
curl -I http://192.168.1.200:8080/
# 预期输出:HTTP/1.0 200 OK

常见错误与处理:

# 错误1:tcpdump: eth0: You don't have permission to capture on that device
# 原因:非 root 用户无抓包权限
# 解决:使用 sudo 或切换到 root 用户

# 错误2:Address already in use
# 原因:端口 8080 已被占用
# 解决:更换端口或停止占用进程
lsof -i :8080
kill -9 <PID>

# 错误3:No route to host
# 原因:防火墙阻止连接
# 解决:临时关闭防火墙或放行端口
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload

幂等性保障:

  • 重复安装软件包自动跳过
  • 重复启动服务会提示端口占用(可先停止旧进程)

回滚要点:

# 停止测试服务器
pkill -f "http.server 8080"

# 卸载工具(可选)
yum remove -y tcpdump wireshark-cli  # RHEL/CentOS
apt remove --purge -y tcpdump tshark  # Ubuntu/Debian

Step 2: 抓取三次握手数据包

目标: 使用 tcpdump 抓取完整的 TCP 三次握手过程

RHEL/CentOS 和 Ubuntu/Debian 通用命令:

# 1. 启动 tcpdump 抓包(在服务器端 192.168.1.200 执行)
tcpdump -i eth0 -nn -S -vvv 'tcp and port 8080' -w /tmp/tcp_handshake.pcap &
# 参数说明:
# -i eth0: 指定网络接口
# -nn: 不解析主机名和端口名(显示 IP 和端口号)
# -S: 显示绝对序列号(不使用相对序列号)
# -vvv: 非常详细的输出
# -w: 保存到文件
# 'tcp and port 8080': 过滤规则(仅抓取 TCP 且端口为 8080 的包)

# 2. 在客户端(192.168.1.100)发起连接
curl http://192.168.1.200:8080/
# 或使用 telnet 测试
telnet 192.168.1.200 8080
# 输入 GET / HTTP/1.0 并按两次回车

# 3. 停止抓包(在服务器端)
pkill -INT tcpdump
# 或等待 tcpdump 自动停止

# 4. 查看抓包文件
ls -lh /tmp/tcp_handshake.pcap
# 预期输出:
# -rw-r--r-- 1 root root 2.3K Jan 20 10:30 /tmp/tcp_handshake.pcap

实时查看抓包(不保存文件):

# 方法1:tcpdump 实时输出(终端查看)
tcpdump -i eth0 -nn -S -vvv 'tcp and port 8080'
# 预期输出(三次握手示例):
# 10:30:15.123456 IP 192.168.1.100.45678 > 192.168.1.200.8080: Flags [S], seq 1234567890, win 29200, options [mss 1460,sackOK,TS val 123456 ecr 0,nop,wscale 7], length 0
# 10:30:15.123789 IP 192.168.1.200.8080 > 192.168.1.100.45678: Flags [S.], seq 9876543210, ack 1234567891, win 28960, options [mss 1460,sackOK,TS val 789012 ecr 123456,nop,wscale 7], length 0
# 10:30:15.124012 IP 192.168.1.100.45678 > 192.168.1.200.8080: Flags [.], ack 9876543211, win 229, options [nop,nop,TS val 123457 ecr 789012], length 0

# 方法2:使用 tshark 解析(更易读)
tshark -i eth0 -f 'tcp and port 8080' -Y 'tcp.flags.syn==1 or tcp.flags.fin==1'
# 预期输出:
#   1   0.000000 192.168.1.100 → 192.168.1.200 TCP 74 45678 → 8080 [SYN] Seq=0 Win=29200 Len=0 MSS=1460
#   2   0.000333 192.168.1.200 → 192.168.1.100 TCP 74 8080 → 45678 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460
#   3   0.000612 192.168.1.100 → 192.168.1.200 TCP 66 45678 → 8080 [ACK] Seq=1 Ack=1 Win=229 Len=0

关键字段解读:

  1. Flags [S]:SYN 标志,表示请求建立连接(第一次握手)
  2. Flags [S.]:SYN+ACK 标志,表示同步并确认(第二次握手)
  3. Flags [.]:ACK 标志,表示确认(第三次握手)
  4. seq 1234567890:初始序列号(ISN,Initial Sequence Number)
  5. ack 1234567891:确认号 = 对方 seq + 1
  6. win 29200:TCP 窗口大小(接收缓冲区大小),单位字节
  7. options [mss 1460]:最大报文段大小(Maximum Segment Size)

执行前验证:

# 确认服务器正在监听
ss -tuln | grep 8080
# 预期输出:tcp   LISTEN 0      5      0.0.0.0:8080      0.0.0.0:*

# 确认客户端网络可达
ping -c 3 192.168.1.200
# 预期输出:3 packets transmitted, 3 received, 0% packet loss

执行后验证:

# 统计抓包文件包数量
tcpdump -r /tmp/tcp_handshake.pcap | wc -l
# 预期输出:10-20 个包(包括握手、数据传输、挥手)

# 提取握手包(SYN/SYN-ACK/ACK)
tcpdump -r /tmp/tcp_handshake.pcap -nn 'tcp[tcpflags] & (tcp-syn) != 0'
# 预期输出:2 个包(SYN 和 SYN-ACK)

Step 3: Wireshark 图形化分析(Windows/Linux GUI)

目标: 使用 Wireshark 详细分析三次握手和四次挥手

操作步骤:

# 1. 将抓包文件传输到 Windows 客户端(如有)
scp root@192.168.1.200:/tmp/tcp_handshake.pcap ~/Desktop/
# 或直接在 Linux 图形界面打开 Wireshark

# 2. Wireshark 打开文件
# File → Open → 选择 tcp_handshake.pcap

# 3. 应用过滤器(仅显示握手和挥手)
# 过滤器输入框输入:
tcp.flags.syn==1 or tcp.flags.fin==1

# 4. 查看三次握手详细信息
# 点击第 1 个包(SYN),查看 Packet Details 窗格:
# ▸ Transmission Control Protocol, Src Port: 45678, Dst Port: 8080
#   ▸ Flags: 0x002 (SYN)
#       .... .... ..1. = Syn: Set
#       .... .... ...0 = Fin: Not set
#   ▸ Sequence Number: 1234567890 (relative sequence number)
#   ▸ Window: 29200
#   ▸ Options: (20 bytes)
#       ▸ Maximum Segment Size: 1460 bytes
#       ▸ Window Scale: 7 (multiply by 128)
#       ▸ Timestamps: TSval 123456, TSecr 0

# 5. 查看序列号和确认号关系
# 三次握手序列号验证:
# Packet 1 (SYN):      seq=1234567890, ack=0
# Packet 2 (SYN-ACK):  seq=9876543210, ack=1234567891 (=Packet1.seq+1 ✅)
# Packet 3 (ACK):      seq=1234567891, ack=9876543211 (=Packet2.seq+1 ✅)

Wireshark 快捷操作:

# 快捷键(Windows/Linux 通用)
Ctrl+F        # 查找包
Ctrl+G        # 跳转到指定包编号
Ctrl+E        # 显示/隐藏 Packet Details
Ctrl+Shift+P  # 打印包
右键 → Follow → TCP Stream  # 查看完整 TCP 会话

# 常用过滤器
tcp.stream eq 0                  # 查看第 0 个 TCP 流
tcp.flags.syn==1 and tcp.flags.ack==0  # 仅 SYN 包(第一次握手)
tcp.flags.syn==1 and tcp.flags.ack==1  # 仅 SYN-ACK 包(第二次握手)
tcp.flags.fin==1                 # 仅 FIN 包(挥手)
tcp.analysis.retransmission      # 重传包
tcp.analysis.window_full         # 窗口已满

关键字段详解:

字段 含义 示例值 说明
Flags TCP 标志位 0x002 (SYN) SYN=0x02, ACK=0x10, FIN=0x01, PSH=0x08, RST=0x04
Seq 序列号 1234567890 标识发送的数据字节位置
Ack 确认号 1234567891 期望接收的下一个字节(对方 seq+1)
Win 窗口大小 29200 接收缓冲区剩余空间(字节)
Len 数据长度 0 握手包无数据(Len=0)
MSS 最大报文段 1460 单个 TCP 包最大数据长度(MTU 1500 - IP 头 20 - TCP 头 20)
Window Scale 窗口缩放因子 7 实际窗口 = Win × 2^7 = 29200 × 128 = 3.7MB
Timestamps 时间戳 TSval 123456 用于计算 RTT(往返时间)

执行后验证:

# 验证三次握手完整性(使用 tshark 命令行)
tshark -r /tmp/tcp_handshake.pcap -Y 'tcp.flags.syn==1' -T fields -e frame.number -e ip.src -e ip.dst -e tcp.flags
# 预期输出:
# 1 192.168.1.100   192.168.1.200   0x0002  # SYN
# 2 192.168.1.200   192.168.1.100   0x0012  # SYN-ACK (0x02 | 0x10)

# 验证序列号连续性
tshark -r /tmp/tcp_handshake.pcap -Y 'tcp.stream eq 0' -T fields -e frame.number -e tcp.seq -e tcp.ack
# 预期输出:
# 1 1234567890  0
# 2 9876543210  1234567891
# 3 1234567891  9876543211

Step 4: 抓取四次挥手数据包

目标: 抓取完整的 TCP 连接关闭过程

RHEL/CentOS 和 Ubuntu/Debian 通用命令:

# 1. 启动抓包(在服务器端)
tcpdump -i eth0 -nn -S -vvv 'tcp and port 8080' -w /tmp/tcp_close.pcap &

# 2. 在客户端建立连接并主动关闭
echo "GET / HTTP/1.0" | nc 192.168.1.200 8080
# nc 会自动关闭连接,触发四次挥手

# 3. 停止抓包
pkill -INT tcpdump

# 4. 分析四次挥手
tcpdump -r /tmp/tcp_close.pcap -nn 'tcp[tcpflags] & (tcp-fin) != 0'
# 预期输出(简化版):
# 10:35:20.123456 IP 192.168.1.100.45678 > 192.168.1.200.8080: Flags [F.], seq 1234567901, ack 9876543250, win 229, length 0
# 10:35:20.123789 IP 192.168.1.200.8080 > 192.168.1.100.45678: Flags [.], ack 1234567902, win 227, length 0
# 10:35:20.124012 IP 192.168.1.200.8080 > 192.168.1.100.45678: Flags [F.], seq 9876543250, ack 1234567902, win 227, length 0
# 10:35:20.124234 IP 192.168.1.100.45678 > 192.168.1.200.8080: Flags [.], ack 9876543251, win 229, length 0

# 5. 使用 tshark 详细分析
tshark -r /tmp/tcp_close.pcap -Y 'tcp.flags.fin==1' -T fields -e frame.number -e frame.time_relative -e ip.src -e ip.dst -e tcp.flags
# 预期输出:
# 15    5.234567    192.168.1.100   192.168.1.200   0x0011  # FIN-ACK (第1次挥手)
# 17    5.234890    192.168.1.200   192.168.1.100   0x0011  # FIN-ACK (第3次挥手)

四次挥手详细流程:

包序号  时间(ms)  源地址 → 目标地址            标志    seq          ack          说明
------  --------  ---------------------------  -----  -----------  -----------  -------------------
15      5234.567  192.168.1.100 → 200:8080   [F.]    1234567901   9876543250   客户端请求关闭(第1次)
16      5234.890  192.168.1.200 → 100:45678  [.]     9876543250   1234567902   服务器确认(第2次)
17      5235.123  192.168.1.200 → 100:45678  [F.]    9876543250   1234567902   服务器请求关闭(第3次)
18      5235.456  192.168.1.100 → 200:8080   [.]     1234567902   9876543251   客户端确认(第4次)
19+     125234    (客户端进入 TIME_WAIT,等待 2MSL = 120秒)

关键参数解释:

  1. Flags [F.]:FIN+ACK,请求关闭连接并确认之前的数据
  2. TIME_WAIT 状态:主动关闭方(客户端)在最后一个 ACK 后等待 2MSL(Maximum Segment Lifetime),默认 120 秒
  3. 为什么需要 TIME_WAIT?
    • 确保最后一个 ACK 到达服务器(若丢失,服务器会重传 FIN)
    • 防止旧连接的延迟数据包影响新连接

执行后验证:

# 验证 TIME_WAIT 状态
ss -tan | grep 8080 | grep TIME-WAIT
# 预期输出:
# TIME-WAIT  0      0      192.168.1.100:45678   192.168.1.200:8080

# 统计 TIME_WAIT 数量
ss -tan | grep TIME-WAIT | wc -l
# 预期输出:1-10 个(取决于并发连接数)

# 查看 TIME_WAIT 超时时间(内核参数)
cat /proc/sys/net/ipv4/tcp_fin_timeout
# 预期输出:60(秒)

Step 5: TCP 状态机与异常连接分析

目标: 理解 TCP 状态转换,排查常见异常状态

TCP 11 种状态:

状态名称        说明                                                  谁持有
-----------    ----------------------------------------              --------
LISTEN         监听状态,等待客户端连接                               服务器
SYN_SENT       客户端发送 SYN 后,等待 SYN-ACK                         客户端
SYN_RECEIVED   服务器收到 SYN,发送 SYN-ACK 后等待 ACK                   服务器
ESTABLISHED    连接建立,可以传输数据                                    双方
FIN_WAIT1       主动关闭方发送 FIN,等待 ACK                            主动方
FIN_WAIT2       主动关闭方收到 ACK,等待对方 FIN                          主动方
CLOSE_WAIT     被动关闭方收到 FIN,发送 ACK 后等待应用关闭                  被动方
CLOSING        双方同时关闭(罕见)                                      双方
LAST_ACK       被动关闭方发送 FIN,等待最后的 ACK                        被动方
TIME_WAIT      主动关闭方收到 FIN 并发送 ACK,等待 2MSL                   主动方
CLOSED         连接关闭                                                  双方

查看当前 TCP 连接状态:

# 1. 查看所有 TCP 连接状态统计
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# 预期输出:
#     50 ESTAB
#     20 TIME-WAIT
#     10 LISTEN
#      3 SYN-SENT
#      1 State

# 2. 查看指定端口的连接状态
ss -tan state established '( dport = :8080 or sport = :8080 )'
# 预期输出:
# State      Recv-Q Send-Q Local Address:Port   Peer Address:Port
# ESTAB      0      0      192.168.1.200:8080   192.168.1.100:45678

# 3. 查看异常状态(SYN_SENT 过多可能遭受 SYN Flood 攻击)
ss -tan state syn-sent | wc -l
# 预期输出:0-5(正常)/ 100+(异常,可能受攻击)

# 4. 查看 CLOSE_WAIT 过多(应用未正常关闭连接)
ss -tan state close-wait | wc -l
# 预期输出:0-10(正常)/ 100+(异常,应用程序 bug)

常见异常状态排查:

# 问题1:TIME_WAIT 过多(>10000)
# 危害:占用端口资源,无法建立新连接
# 排查:
ss -tan | grep TIME-WAIT | wc -l
# 解决方案1:启用 TIME_WAIT 快速回收(内核参数)
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_tw_recycle=0  # 注意:tcp_tw_recycle 在 Linux 4.12+ 已废弃
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
sysctl -p

# 解决方案2:增大本地端口范围
sysctl -w net.ipv4.ip_local_port_range="10000 65535"
echo "net.ipv4.ip_local_port_range = 10000 65535" >> /etc/sysctl.conf

# 问题2:CLOSE_WAIT 过多
# 危害:应用未关闭连接,导致资源泄漏
# 排查:
ss -tan state close-wait | head -5
# 预期输出:
# CLOSE-WAIT  0      0      192.168.1.200:8080   192.168.1.100:45678
# 解决:修复应用程序代码,确保调用 close() 关闭连接

# 问题3:SYN_RECV 过多(可能受 SYN Flood 攻击)
# 排查:
ss -tan state syn-recv | wc -l
# 解决:启用 SYN Cookie(内核参数)
sysctl -w net.ipv4.tcp_syncookies=1
echo "net.ipv4.tcp_syncookies = 1" >> /etc/sysctl.conf

执行后验证:

# 验证参数生效
sysctl -a | grep -E 'tcp_tw_reuse|ip_local_port_range|tcp_syncookies'
# 预期输出:
# net.ipv4.tcp_tw_reuse = 1
# net.ipv4.ip_local_port_range = 10000  65535
# net.ipv4.tcp_syncookies = 1

2️⃣ 最小必要原理

核心机制:

TCP 使用 三次握手 建立可靠连接,四次挥手 安全关闭连接。

为什么是三次握手而非两次?

  • 防止旧连接请求:若客户端发送的第一个 SYN 延迟到达,服务器回复 SYN-ACK 后建立连接,但客户端认为此连接无效,导致资源浪费。第三次握手让客户端确认“我确实要建立这个连接”。
  • 同步双方初始序列号(ISN):客户端和服务器各自生成随机 ISN,三次握手确保双方都知道对方的 ISN。

为什么是四次挥手而非三次?

  • TCP 全双工通信:客户端发送 FIN 关闭发送方向,但服务器可能还有数据要发送,因此需要单独发送 FIN。
  • 流程:客户端 FIN → 服务器 ACK(此时服务器可继续发送数据)→ 服务器 FIN → 客户端 ACK。

TIME_WAIT 存在的意义:

  1. 确保最后的 ACK 到达:若最后的 ACK 丢失,服务器会重传 FIN,客户端需保持连接以重新发送 ACK。
  2. 防止旧数据污染新连接:等待 2MSL(120 秒)确保所有旧数据包在网络中消失。

3️⃣ 可观测性(监控 + 告警 + 性能)

监控指标

Linux 原生监控:

# 1. 实时查看 TCP 连接状态统计
watch -n 1 'ss -tan | awk "{print \$1}" | sort | uniq -c | sort -rn'
# 预期输出:
# Every 1.0s: ss -tan | awk ...
#     150 ESTAB
#      50 TIME-WAIT
#      10 LISTEN
#       5 SYN-SENT

# 2. 监控 TIME_WAIT 数量
watch -n 5 'ss -tan | grep TIME-WAIT | wc -l'
# 预期输出:20-100(正常)/ 1000+(异常)

# 3. 查看网络吞吐量(需安装 iftop)
yum install -y iftop  # RHEL/CentOS
apt install -y iftop  # Ubuntu/Debian
iftop -i eth0 -n -P
# 预期输出:实时显示每个连接的发送/接收速率

# 4. 监控 TCP 重传率
netstat -s | grep -E 'retransmitted|segments'
# 预期输出:
#     12345678 segments sent
#     567 segments retransmitted
# 重传率 = 567 / 12345678 ≈ 0.0046% (< 1% 为正常)

# 5. 查看 TCP 缓冲区使用情况
ss -tm
# 预期输出:
# ESTAB      0      0      192.168.1.200:8080   192.168.1.100:45678
#          ts sack cubic wscale:7,7 rto:204 rtt:3.5/1.5 ato:40 mss:1448 cwnd:10

Prometheus 监控配置:

# /etc/prometheus/prometheus.yml
scrape_configs:
- job_name: 'node_exporter'
  static_configs:
  - targets: ['192.168.1.200:9100']

# 关键 TCP 指标(node_exporter 自动采集)
# node_netstat_Tcp_CurrEstab          # 当前 ESTABLISHED 连接数
# node_netstat_Tcp_ActiveOpens        # 主动打开连接数(累计)
# node_netstat_Tcp_PassiveOpens       # 被动打开连接数(累计)
# node_netstat_Tcp_RetransSegs        # 重传段数(累计)
# node_netstat_TcpExt_TCPTimeouts     # 超时次数(累计)
# node_netstat_TcpExt_ListenOverflows # Listen 队列溢出次数

Prometheus 告警规则:

# /etc/prometheus/rules/tcp_alerts.yml
groups:
- name: tcp_alerts
  interval: 15s
  rules:
  # 告警1:TIME_WAIT 过多
  - alert: TcpTimeWaitTooMany
    expr: node_sockstat_TCP_tw > 5000
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "TIME_WAIT 连接过多({{ $value }})"
      description: "主机 {{ $labels.instance }} 的 TIME_WAIT 连接数超过 5000,可能影响新连接建立"

  # 告警2:TCP 重传率过高
  - alert: TcpRetransmitRateHigh
    expr: rate(node_netstat_Tcp_RetransSegs[5m]) / rate(node_netstat_Tcp_OutSegs[5m]) > 0.02
    for: 3m
    labels:
      severity: critical
    annotations:
      summary: "TCP 重传率过高({{ $value | humanizePercentage }})"
      description: "主机 {{ $labels.instance }} 的 TCP 重传率超过 2%,网络质量异常"

  # 告警3:ESTABLISHED 连接数过多
  - alert: TcpEstablishedTooMany
    expr: node_netstat_Tcp_CurrEstab > 10000
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "TCP ESTABLISHED 连接数过多({{ $value }})"
      description: "主机 {{ $labels.instance }} 的 ESTABLISHED 连接数超过 10000"

  # 告警4:Listen 队列溢出
  - alert: TcpListenQueueOverflow
    expr: increase(node_netstat_TcpExt_ListenOverflows[5m]) > 10
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "TCP Listen 队列溢出({{ $value }} 次)"
      description: "主机 {{ $labels.instance }} 的 Listen 队列在 5 分钟内溢出 {{ $value }} 次,需增大 somaxconn"

性能基准测试

场景1:并发连接性能测试

# 工具:wrk(需提前安装)
wrk -t10 -c1000 -d30s http://192.168.1.200:8080/
# 预期输出(4C8G 节点):
# Running 30s test @ http://192.168.1.200:8080/
#   10 threads and 1000 connections
#   Thread Stats   Avg      Stdev     Max   +/- Stdev
#     Latency    45.23ms   12.34ms  120.45ms   78.90%
#     Req/Sec     2.21k   234.56     3.12k    81.23%
#   663000 requests in 30.05s, 545.67MB read
# Requests/sec:  22064.32
# Transfer/sec:     18.15MB

场景2:短连接性能测试(大量 TIME_WAIT)

# 使用 ab 测试短连接
ab -n 100000 -c 1000 http://192.168.1.200:8080/
# 预期输出:
# Requests per second:    15234.56 [#/sec] (mean)
# Time per request:       65.632 [ms] (mean)
# Connection Times (ms)
#               min  mean[+/-sd] median   max
# Connect:        0   15  12.3     12     120
# Processing:     1   45  23.4     42     250
# Waiting:        1   40  21.2     38     240
# Total:          2   60  28.5     55     320

# 测试期间监控 TIME_WAIT 数量
watch -n 1 'ss -tan | grep TIME-WAIT | wc -l'
# 预期输出:1000-5000(短连接场景下正常)

4️⃣ 常见故障与排错

故障编号 症状 诊断命令 可能根因 快速修复 永久修复
#1 连接建立缓慢 ss -tan state syn-recv | wc -l 1. SYN 队列满
2. 服务器负载高
3. 网络延迟大
增大 SYN 队列:sysctl -w net.ipv4.tcp_max_syn_backlog=8192 优化应用性能
增加服务器资源
#2 TIME_WAIT 过多 ss -tan | grep TIME-WAIT | wc -l 1. 短连接频繁
2. 主动关闭连接过多
启用 tw_reuse:sysctl -w net.ipv4.tcp_tw_reuse=1 使用长连接(HTTP Keep-Alive)
#3 CLOSE_WAIT 过多 ss -tan state close-wait | wc -l 应用未调用 close() 关闭连接 重启应用释放连接 修复应用代码
确保正确关闭连接
#4 Connection refused telnet 192.168.1.200 8080 1. 服务未启动
2. 端口未监听
3. 防火墙阻止
启动服务:systemctl start httpd 配置防火墙放行端口
#5 Connection timeout tcpdump -i eth0 'tcp and port 8080' 1. 网络不通
2. SYN 包被丢弃
3. 服务器无响应
检查网络:ping 192.168.1.200 检查路由和防火墙规则
#6 Connection reset ss -tan | grep ESTAB 1. 服务器主动发送 RST
2. 防火墙中断连接
3. 应用崩溃
查看应用日志 修复应用程序 bug
调整超时参数
#7 半连接队列满 ss -tan state syn-recv | wc -l SYN Flood 攻击 启用 SYN Cookie:sysctl -w net.ipv4.tcp_syncookies=1 部署防火墙过滤
增大队列大小

详细故障排查案例:

案例1:TIME_WAIT 过多导致无法建立新连接

# 现象:客户端连接失败
curl http://192.168.1.200:8080/
# 输出:curl: (7) Failed to connect to 192.168.1.200 port 8080: Cannot assign requested address

# 排查步骤1:检查 TIME_WAIT 数量
ss -tan | grep TIME-WAIT | wc -l
# 输出:28000(异常!系统端口范围 32768-60999,仅剩 28232-28000=232 个可用)

# 排查步骤2:查看端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 输出:32768  60999

# 快速修复:启用 TIME_WAIT 复用
sysctl -w net.ipv4.tcp_tw_reuse=1
# 输出:net.ipv4.tcp_tw_reuse = 1

# 永久修复:扩大端口范围
echo "net.ipv4.ip_local_port_range = 10000 65535" >> /etc/sysctl.conf
sysctl -p
# 预期效果:可用端口从 28232 增加到 55535 个

# 验证修复
curl http://192.168.1.200:8080/
# 输出:HTTP/1.0 200 OK(成功)

案例2:CLOSE_WAIT 过多导致应用无法接受新连接

# 现象:应用无法接受新连接
ss -tan state close-wait | wc -l
# 输出:5000(异常!)

# 排查步骤1:查看哪些进程有 CLOSE_WAIT 连接
ss -tanp state close-wait | grep -oP 'pid=\K\d+' | sort | uniq -c | sort -rn
# 输出:
#    5000 12345  # PID 12345 的进程有 5000 个 CLOSE_WAIT 连接

# 排查步骤2:确认进程名称
ps -p 12345 -o comm=
# 输出:nginx(或其他应用)

# 快速修复:重启应用(临时)
systemctl restart nginx

# 永久修复:修复应用代码
# 原因:应用接收到对方的 FIN 后,未调用 close() 关闭连接
# 解决:在应用代码中确保 socket 正确关闭
# 例如(Python):
# try:
#     conn, addr = server_socket.accept()
#     # ... 处理请求 ...
# finally:
#     conn.close()  # 确保连接关闭

5️⃣ 变更与回滚剧本

灰度策略

场景:调整 TCP 内核参数

# 步骤1:备份当前配置
sysctl -a | grep -E 'tcp_tw_reuse|tcp_max_syn_backlog|ip_local_port_range' > /tmp/tcp_params_backup.txt

# 步骤2:在测试环境验证
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
# 观察 5-10 分钟,监控连接状态

# 步骤3:生产环境灰度(先在部分节点应用)
# 仅在 192.168.1.200 应用
ssh root@192.168.1.200 "sysctl -w net.ipv4.tcp_tw_reuse=1"

# 步骤4:全量应用
for host in 192.168.1.{200..210}; do
    ssh root@$host "sysctl -w net.ipv4.tcp_tw_reuse=1"
done

# 步骤5:持久化配置
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
sysctl -p

回滚条件与命令

回滚触发条件:

  1. 连接失败率超过 5%
  2. TCP 重传率超过 5%
  3. 应用响应时间增加超过 50%

回滚步骤:

# 恢复备份的参数
while IFS= read -r line; do
    sysctl -w "$line"
done < /tmp/tcp_params_backup.txt

# 或手动恢复关键参数
sysctl -w net.ipv4.tcp_tw_reuse=0
sysctl -w net.ipv4.tcp_max_syn_backlog=512

# 重启应用(如需)
systemctl restart nginx

6️⃣ 最佳实践

1. 使用 TIME_WAIT 复用而非回收

# 推荐配置(Linux 3.10+)
sysctl -w net.ipv4.tcp_tw_reuse=1
# 不推荐:tcp_tw_recycle(已在 Linux 4.12+ 废弃,NAT 环境下有问题)

2. 调整 SYN 队列大小防止 SYN Flood

sysctl -w net.ipv4.tcp_max_syn_backlog=8192      # 半连接队列
sysctl -w net.core.somaxconn=8192                # 全连接队列
sysctl -w net.ipv4.tcp_syncookies=1              # 启用 SYN Cookie

3. 扩大本地端口范围

sysctl -w net.ipv4.ip_local_port_range="10000 65535"
# 可用端口从 28232 增加到 55535 个

4. 启用 TCP Fast Open(减少握手延迟)

sysctl -w net.ipv4.tcp_fastopen=3  # 3=客户端和服务器都启用
# 效果:第二次连接可跳过三次握手,延迟降低 1 RTT

5. 监控关键指标并设置告警

# 关键指标阈值(Prometheus)
# TIME_WAIT > 5000 → 告警
# 重传率 > 2% → 告警
# CLOSE_WAIT > 1000 → 告警
# SYN_RECV > 500 → 告警(可能受攻击)

6. 使用长连接减少握手开销

# HTTP Keep-Alive 配置(Nginx)
keepalive_timeout 65;        # 连接保持 65 秒
keepalive_requests 1000;     # 单个连接最多处理 1000 个请求

7. 定期清理 CLOSE_WAIT 连接

# 监控脚本(每 5 分钟检查一次)
*/5 * * * * [ $(ss -tan state close-wait | wc -l) -gt 1000 ] && systemctl restart myapp

7️⃣ FAQ

Q1: 为什么三次握手不是两次或四次?
A: 两次握手无法防止旧 SYN 请求建立无效连接;四次握手冗余(SYN 和 ACK 可合并为 SYN-ACK)。三次握手是效率与可靠性的最佳平衡。

Q2: TIME_WAIT 状态可以关闭吗?
A: 不能完全关闭,但可通过 tcp_tw_reuse=1 快速复用 TIME_WAIT 连接。完全关闭会导致旧数据包污染新连接。

Q3: CLOSE_WAIT 过多如何排查应用 bug?
A:

# 1. 查看哪个进程有 CLOSE_WAIT 连接
ss -tanp state close-wait | grep -oP 'pid=\K\d+' | sort | uniq -c
# 2. 使用 strace 跟踪进程系统调用
strace -p <PID> -e trace=close,shutdown
# 3. 检查代码是否正确调用 close()/shutdown()

Q4: 如何区分主动关闭和被动关闭?
A:

  • 主动关闭:先发送 FIN 的一方,最终进入 TIME_WAIT 状态(等待 2MSL)
  • 被动关闭:先收到 FIN 的一方,经过 CLOSE_WAIT → LAST_ACK → CLOSED

Q5: 为什么 tcpdump 抓到的序列号和 Wireshark 不一致?
A: tcpdump 默认显示相对序列号,Wireshark 显示绝对序列号。可使用 tcpdump -S 显示绝对序列号。

Q6: 如何处理 SYN Flood 攻击?
A:

# 1. 启用 SYN Cookie(无状态处理 SYN)
sysctl -w net.ipv4.tcp_syncookies=1
# 2. 增大 SYN 队列
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
# 3. 减小 SYN-ACK 重试次数
sysctl -w net.ipv4.tcp_synack_retries=1
# 4. 部署防火墙限速(iptables)
iptables -A INPUT -p tcp --syn -m limit --limit 10/s -j ACCEPT

Q7: 短连接场景如何优化性能?
A:

# 1. 启用 TIME_WAIT 复用
sysctl -w net.ipv4.tcp_tw_reuse=1
# 2. 扩大端口范围
sysctl -w net.ipv4.ip_local_port_range="10000 65535"
# 3. 启用 TCP Fast Open
sysctl -w net.ipv4.tcp_fastopen=3
# 4. 应用层改为长连接(HTTP Keep-Alive)

8️⃣ 附录:关键脚本

脚本1:TCP 连接状态监控脚本

#!/bin/bash
##############################################################################
# 文件名:tcp_monitor.sh
# 版本:v1.0.0
# 用途:监控 TCP 连接状态,输出 Prometheus 格式指标
# 使用:crontab -e → */1 * * * * /usr/local/bin/tcp_monitor.sh
##############################################################################

set -euo pipefail

METRICS_FILE="/var/lib/node_exporter/textfile_collector/tcp_states.prom"
mkdir -p "$(dirname "$METRICS_FILE")"

# 获取各状态的连接数
ESTAB=$(ss -tan state established | wc -l)
TIME_WAIT=$(ss -tan state time-wait | wc -l)
CLOSE_WAIT=$(ss -tan state close-wait | wc -l)
SYN_SENT=$(ss -tan state syn-sent | wc -l)
SYN_RECV=$(ss -tan state syn-recv | wc -l)
FIN_WAIT1=$(ss -tan state fin-wait-1 | wc -l)
FIN_WAIT2=$(ss -tan state fin-wait-2 | wc -l)
LISTEN=$(ss -tan state listen | wc -l)

# 输出 Prometheus 格式
cat > "$METRICS_FILE" << EOF
# HELP tcp_connections_state TCP connections by state
# TYPE tcp_connections_state gauge
tcp_connections_state{state="established"} $ESTAB
tcp_connections_state{state="time_wait"} $TIME_WAIT
tcp_connections_state{state="close_wait"} $CLOSE_WAIT
tcp_connections_state{state="syn_sent"} $SYN_SENT
tcp_connections_state{state="syn_recv"} $SYN_RECV
tcp_connections_state{state="fin_wait1"} $FIN_WAIT1
tcp_connections_state{state="fin_wait2"} $FIN_WAIT2
tcp_connections_state{state="listen"} $LISTEN
EOF

# 计算重传率
RETRANS=$(netstat -s | grep 'segments retransmitted' | awk '{print $1}')
SENT=$(netstat -s | grep 'segments sent out' | awk '{print $1}')

cat >> "$METRICS_FILE" << EOF
# HELP tcp_segments_total TCP segments statistics
# TYPE tcp_segments_total counter
tcp_segments_retransmitted_total $RETRANS
tcp_segments_sent_total $SENT
EOF

echo "[$(date)] TCP metrics updated: ESTAB=$ESTAB, TIME_WAIT=$TIME_WAIT, CLOSE_WAIT=$CLOSE_WAIT"

脚本2:TCP 连接异常检测与告警脚本

#!/bin/bash
##############################################################################
# 文件名:tcp_alert.sh
# 版本:v1.0.0
# 用途:检测 TCP 连接异常状态并发送告警
##############################################################################

set -euo pipefail

# 告警阈值
TIME_WAIT_THRESHOLD=5000
CLOSE_WAIT_THRESHOLD=1000
SYN_RECV_THRESHOLD=500

# 告警方式(修改为实际告警接口)
send_alert() {
    local message="$1"
    echo "[ALERT] $(date): $message" >> /var/log/tcp_alert.log
    # 发送到钉钉/企业微信/邮件等(需自行实现)
    # curl -X POST -d "text=$message" https://your-alert-webhook-url
}

# 检查 TIME_WAIT
TIME_WAIT=$(ss -tan state time-wait | wc -l)
if [ "$TIME_WAIT" -gt "$TIME_WAIT_THRESHOLD" ]; then
    send_alert "TIME_WAIT 连接数过多:$TIME_WAIT(阈值:$TIME_WAIT_THRESHOLD)"
fi

# 检查 CLOSE_WAIT
CLOSE_WAIT=$(ss -tan state close-wait | wc -l)
if [ "$CLOSE_WAIT" -gt "$CLOSE_WAIT_THRESHOLD" ]; then
    send_alert "CLOSE_WAIT 连接数过多:$CLOSE_WAIT(阈值:$CLOSE_WAIT_THRESHOLD),可能应用未正确关闭连接"
fi

# 检查 SYN_RECV(可能受攻击)
SYN_RECV=$(ss -tan state syn-recv | wc -l)
if [ "$SYN_RECV" -gt "$SYN_RECV_THRESHOLD" ]; then
    send_alert "SYN_RECV 连接数过多:$SYN_RECV(阈值:$SYN_RECV_THRESHOLD),可能遭受 SYN Flood 攻击"
fi

脚本3:自动化抓包脚本

#!/bin/bash
##############################################################################
# 文件名:tcp_capture.sh
# 版本:v1.0.0
# 用途:自动抓取指定端口的 TCP 连接(三次握手+四次挥手)
# 使用:./tcp_capture.sh 8080 60  # 抓取 8080 端口,持续 60 秒
##############################################################################

set -euo pipefail

PORT=${1:-80}
DURATION=${2:-30}
OUTPUT_DIR="/tmp/tcp_captures"
OUTPUT_FILE="${OUTPUT_DIR}/tcp_${PORT}_$(date +%Y%m%d_%H%M%S).pcap"

mkdir -p "$OUTPUT_DIR"

echo "[$(date)] 开始抓包:端口=$PORT,持续时间=$DURATION 秒"
echo "[$(date)] 输出文件:$OUTPUT_FILE"

# 启动 tcpdump 抓包
timeout "$DURATION" tcpdump -i any -nn -S -vvv "tcp and port $PORT" -w "$OUTPUT_FILE" 2>&1 | tee "${OUTPUT_FILE}.log" &
TCPDUMP_PID=$!

# 等待抓包结束
wait "$TCPDUMP_PID" || true

# 分析抓包结果
echo "[$(date)] 抓包完成,开始分析..."

# 统计包数量
TOTAL_PACKETS=$(tcpdump -r "$OUTPUT_FILE" 2>/dev/null | wc -l)
SYN_PACKETS=$(tcpdump -r "$OUTPUT_FILE" 'tcp[tcpflags] & (tcp-syn) != 0' 2>/dev/null | wc -l)
FIN_PACKETS=$(tcpdump -r "$OUTPUT_FILE" 'tcp[tcpflags] & (tcp-fin) != 0' 2>/dev/null | wc -l)

echo "========== 抓包统计 =========="
echo "总包数:$TOTAL_PACKETS"
echo "SYN 包数:$SYN_PACKETS(包括 SYN 和 SYN-ACK)"
echo "FIN 包数:$FIN_PACKETS"
echo "文件路径:$OUTPUT_FILE"
echo "文件大小:$(du -h "$OUTPUT_FILE" | cut -f1)"

# 导出可读的文本格式
tcpdump -r "$OUTPUT_FILE" -nn -tttt > "${OUTPUT_FILE}.txt"
echo "文本格式已导出:${OUTPUT_FILE}.txt"

9️⃣ 扩展阅读

官方文档:

深入技术博客:

工具下载:

社区资源:

希望这篇结合了实战抓包、故障排查与内核调优的详细指南,能帮助您更深入地理解 TCP/IP 协议栈。如果您在实践过程中遇到任何问题,或者有更深入的见解,欢迎到云栈社区与广大开发者交流讨论。




上一篇:软考高级架构师论文指南:三步精准回应子题目,拒绝跑题
下一篇:别只盯着 MySQL 了:后端与数据工程师的国产数据库生存指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 09:15 , Processed in 0.608061 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表