线上服务出问题,十有八九跟网络有关。连接超时、丢包、端口不通、防火墙误拦截,这些问题每天都在发生。我带团队这些年,处理过的网络故障少说上千次,最后总结下来,掌握tcpdump、ss、iptables这三个工具,基本能覆盖80%的网络排障场景。
剩下20%涉及交换机、路由策略、BGP这些,通常需要网络组配合。但作为运维工程师,把这三个工具用熟,至少能快速定位问题出在哪一层,不至于跟网络组扯皮时说不清楚。
实际工作中的典型场景:
tcpdump --version查看 | ||
ss --version查看 | ||
# 确认内核版本,4.9以上最佳
uname -r
# 检查系统版本
cat /etc/os-release
# 查看网卡信息,确认要抓包的接口名
ip addr show
# 检查磁盘空间,抓包文件可能很大
df -h /tmp
# 查看当前内存使用情况
free -h
# CentOS / RHEL
sudo yum install -y tcpdump iproute iptables-services net-tools bind-utils mtr nc
# Ubuntu / Debian
sudo apt update
sudo apt install -y tcpdump iproute2 iptables dnsutils mtr-tiny netcat-openbsd
# 验证安装
tcpdump --version
ss --version
iptables --version
一个容易踩的坑:CentOS 8及以上版本默认用firewalld管理防火墙,底层是nftables。如果你同时用iptables命令和firewalld,规则会互相覆盖。生产环境建议二选一,我们团队的做法是关掉firewalld,直接用iptables-services:
# CentOS 7/8 关闭firewalld,启用iptables
sudo systemctl stop firewalld
sudo systemctl disable firewalld
sudo systemctl mask firewalld
sudo yum install -y iptables-services
sudo systemctl enable iptables
sudo systemctl start iptables
tcpdump是网络排障的第一工具。很多人觉得tcpdump难用,其实掌握几个固定套路就够了。
基础语法:
tcpdump [选项] [过滤表达式]
常用选项:
-i eth0 | ||
-n | ||
-nn | ||
-c 100 | ||
-w file.pcap | ||
-r file.pcap | ||
-s 0 | ||
-A | ||
-X | ||
-v/-vv/-vvv | ||
-tttt |
按IP过滤:
# 抓取与特定IP通信的所有包
tcpdump -i eth0 -nn host 192.168.1.100
# 只抓源IP
tcpdump -i eth0 -nn src host 192.168.1.100
# 只抓目标IP
tcpdump -i eth0 -nn dst host 192.168.1.100
# 抓取某个网段的流量
tcpdump -i eth0 -nn net 192.168.1.0/24
按端口过滤:
# 抓取80端口的流量
tcpdump -i eth0 -nn port 80
# 抓取源端口为3306的流量(MySQL响应)
tcpdump -i eth0 -nn src port 3306
# 抓取目标端口为443的流量
tcpdump -i eth0 -nn dst port 443
# 抓取端口范围
tcpdump -i eth0 -nn portrange 8000-9000
按协议过滤:
# 只抓TCP
tcpdump -i eth0 -nn tcp
# 只抓UDP
tcpdump -i eth0 -nn udp
# 只抓ICMP(排查ping不通)
tcpdump -i eth0 -nn icmp
# 只抓ARP(排查IP冲突)
tcpdump -i eth0 -nn arp
组合过滤(实际工作中最常用):
# 抓取某IP的80端口TCP流量
tcpdump -i eth0 -nn tcp and host 192.168.1.100 and port 80
# 抓取除SSH外的所有流量(排障时避免刷屏)
tcpdump -i eth0 -nn not port 22
# 抓取来自某IP且目标端口为3306或6379的流量
tcpdump -i eth0 -nn src host 10.0.1.50 and \(dst port 3306 or dst port 6379\)
# 抓取SYN包(只看新建连接)
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0'
# 抓取SYN+ACK包(看服务端响应)
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) = (tcp-syn|tcp-ack)'
# 抓取RST包(连接被重置,排查连接异常断开)
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'
保存和分析pcap文件:
# 保存抓包结果到文件,限制100MB自动轮转,最多保留5个文件
tcpdump -i eth0 -nn -w /tmp/capture.pcap -C 100 -W 5 port 80
# 按时间轮转,每60秒一个文件
tcpdump -i eth0 -nn -w /tmp/capture_%Y%m%d_%H%M%S.pcap -G 60 port 80
# 读取pcap文件
tcpdump -nn -r /tmp/capture.pcap
# 读取pcap文件并过滤
tcpdump -nn -r /tmp/capture.pcap 'tcp and port 80 and host 10.0.1.50'
# 统计pcap文件中各IP的包数量
tcpdump -nn -r /tmp/capture.pcap | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head -20
抓HTTP请求(排查接口问题):
# 抓取HTTP GET请求
tcpdump -i eth0 -nn -A -s 0 'tcp dst port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
# 更简单的方式:抓80端口并显示ASCII内容
tcpdump -i eth0 -nn -A -s 0 port 80 | grep -E "GET|POST|HTTP|Host:"
# 抓取DNS查询(排查域名解析问题)
tcpdump -i eth0 -nn port 53
# 抓取DNS查询并显示详细内容
tcpdump -i eth0 -nn -vvv port 53
实战案例:排查MySQL连接超时
某天收到告警,应用连接MySQL超时率突增到5%。先在应用服务器上抓包:
# 在应用服务器上抓与MySQL服务器的通信
tcpdump -i eth0 -nn -tttt -w /tmp/mysql_debug.pcap host 10.0.2.100 and port 3306 -c 10000
# 分析TCP握手时间(SYN到SYN-ACK的间隔)
tcpdump -nn -r /tmp/mysql_debug.pcap 'tcp[tcpflags] & (tcp-syn) != 0' | head -50
# 查看重传包数量
tcpdump -nn -r /tmp/mysql_debug.pcap 'tcp[tcpflags] & tcp-syn != 0' | wc -l
# 查看RST包(连接被拒绝)
tcpdump -nn -r /tmp/mysql_debug.pcap 'tcp[tcpflags] & tcp-rst != 0' | wc -l
这次排查发现SYN到SYN-ACK的延迟从正常的0.2ms飙升到800ms,而且有大量重传。最终定位到MySQL服务器所在宿主机的网卡队列溢出,调整了ring buffer后恢复:
# 查看网卡丢包统计
ethtool -S eth0 | grep -i drop
# 查看当前ring buffer大小
ethtool -g eth0
# 调大ring buffer(需要根据网卡支持的最大值来设)
ethtool -G eth0 rx 4096 tx 4096
ss是iproute2套件里的工具,用来查看socket统计信息。netstat在连接数多的机器上慢得不能忍,ss直接读netlink接口,实测5万连接的机器上ss耗时0.3秒,netstat要35秒。生产环境全面用ss替代netstat。
基础用法:
# 查看所有TCP连接
ss -tn
# 查看所有监听端口
ss -tlnp
# 查看所有UDP监听
ss -ulnp
# 查看所有连接(TCP + UDP + Unix Socket)
ss -a
# 查看连接摘要统计
ss -s
ss -s的输出非常有用,一眼就能看到各状态的连接数分布:
Total: 1856
TCP: 1523 (estab 892, closed 312, orphaned 15, timewait 289, synrecv 0)
Transport Total IP IPv6
RAW 2 1 1
UDP 12 8 4
TCP 1211 1089 122
INET 1225 1098 127
FRAG 0 0 0
常用选项说明:
-t | ||
-u | ||
-l | ||
-n | ||
-p | ||
-e | ||
-m | ||
-i | ||
-o |
按状态过滤(排障核心技能):
# 查看所有ESTABLISHED连接
ss -tn state established
# 查看所有TIME-WAIT连接
ss -tn state time-wait
# 查看所有CLOSE-WAIT连接(这个状态多了说明应用没正确关闭连接)
ss -tn state close-wait
# 查看SYN-RECV状态(SYN Flood攻击时会暴涨)
ss -tn state syn-recv
# 查看FIN-WAIT-1状态
ss -tn state fin-wait-1
# 查看除TIME-WAIT外的所有连接
ss -tn state all exclude time-wait
按地址和端口过滤:
# 查看连接到本机80端口的所有连接
ss -tn dst :80
# 注意:上面这个在某些版本不好使,用下面这个更稳
ss -tn 'sport = :80'
# 查看连接到远程3306端口的连接
ss -tn 'dport = :3306'
# 查看来自某个IP的连接
ss -tn 'src 10.0.1.0/24'
# 查看到某个IP的连接
ss -tn 'dst 10.0.2.100'
# 组合过滤:来自某网段且连接到80端口
ss -tn 'src 10.0.1.0/24 and sport = :80'
# 查看端口大于1024的连接
ss -tn 'sport > :1024'
查看连接队列(排查连接建立慢的关键):
# 查看监听socket的连接队列
ss -tlnp
# 输出示例:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6))
# LISTEN 129 128 0.0.0.0:8080 0.0.0.0:* users:(("java",pid=5678,fd=100))
对于LISTEN状态:
Recv-Q:当前等待accept的连接数(半连接+全连接队列中已完成握手的)Send-Q:backlog值,即最大队列长度上面第二行,Recv-Q=129 > Send-Q=128,说明全连接队列已满,新连接会被丢弃。这是典型的应用处理不过来的表现。
# 查看全连接队列溢出次数
netstat -s | grep -i "listen"
# 或者
nstat -az TcpExtListenOverflows TcpExtListenDrops
统计分析(日常巡检常用):
# 统计各状态的连接数
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
# 统计连接数最多的远程IP(Top 20)
ss -tn state established | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# 统计连接到本机各端口的连接数
ss -tn state established | awk 'NR>1 {print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn | head -20
# 统计TIME-WAIT连接的远程IP分布
ss -tn state time-wait | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# 查看TCP内部信息(RTT、拥塞窗口)
ss -ti state established dst 10.0.2.100
实战案例:排查连接数暴涨
某天下午3点,监控告警某台Web服务器TCP连接数从正常的2000飙升到6万。排查过程:
# 第一步:看连接状态分布
ss -s
# 发现TIME-WAIT有5.2万个
# 第二步:看TIME-WAIT连接的目标分布
ss -tn state time-wait | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn | head -5
# 输出:
# 48923 6379
# 2100 3306
# 800 80
# 第三步:确认是Redis连接导致的
ss -tn state time-wait | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -5
# 输出:
# 48923 10.0.3.50
# 结论:应用到Redis(10.0.3.50:6379)的短连接太多
最终发现是某个开发同事写的定时任务,每次请求都新建Redis连接而不是用连接池。修复后连接数回到正常水平。临时缓解措施是调整内核参数加速TIME-WAIT回收:
# 临时生效
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_max_tw_buckets=20000
# 永久生效写入配置
echo"net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
echo"net.ipv4.tcp_max_tw_buckets = 20000" >> /etc/sysctl.conf
sysctl -p
注意:tcp_tw_recycle在4.12以上内核已经被移除,不要再用了。tcp_tw_reuse只对客户端(主动发起连接的一方)有效。
iptables是Linux防火墙的核心工具。虽然CentOS 8+和Ubuntu 20.04+推firewalld和nftables,但底层逻辑是一样的,而且很多老系统还在用iptables。搞清楚四表五链的数据流向,其他防火墙工具都是换皮。
四表五链架构:
四张表(按优先级从高到低):
raw:决定是否跳过连接跟踪(conntrack),高流量场景下用来减少开销mangle:修改数据包头部(TTL、TOS等),一般用不到nat:网络地址转换,做端口转发、SNAT/DNATfilter:过滤数据包,决定放行还是丢弃,最常用的表五条链:
PREROUTING:数据包进入路由判断之前(raw、mangle、nat表)INPUT:数据包目标是本机时经过(mangle、filter表)FORWARD:数据包经过本机转发时经过(mangle、filter表)OUTPUT:本机产生的数据包出去时经过(raw、mangle、nat、filter表)POSTROUTING:数据包离开本机之前(mangle、nat表)数据包流向:
外部 --> PREROUTING --> 路由判断 --> INPUT --> 本机进程
|
v
FORWARD --> POSTROUTING --> 外部
^
本机进程 --> OUTPUT --> 路由判断 --> POSTROUTING --> 外部
查看规则(排障第一步):
# 查看filter表所有规则(带行号,方便删除)
iptables -L -n --line-numbers
# 查看filter表规则(更详细,显示包计数)
iptables -L -n -v
# 查看nat表规则
iptables -t nat -L -n --line-numbers
# 查看raw表规则
iptables -t raw -L -n
# 以iptables-save格式查看(最推荐,输出可以直接用于恢复)
iptables-save
# 只看某条链
iptables -L INPUT -n --line-numbers
添加规则:
# 允许SSH访问(放在INPUT链)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 允许特定IP访问MySQL
iptables -A INPUT -s 10.0.1.0/24 -p tcp --dport 3306 -j ACCEPT
# 允许已建立的连接和相关连接通过(这条必须有,否则回包会被拦)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT
# 允许ICMP(ping)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# 拒绝其他所有入站流量
iptables -A INPUT -j DROP
# 在指定位置插入规则(插入到第3行)
iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT
删除规则:
# 按行号删除(先用--line-numbers查看行号)
iptables -D INPUT 5
# 按规则内容删除
iptables -D INPUT -p tcp --dport 8080 -j ACCEPT
# 清空某条链的所有规则(危险操作,生产环境慎用)
iptables -F INPUT
# 清空所有链的所有规则
iptables -F
NAT配置(端口转发):
# 开启内核转发(NAT必须)
echo 1 > /proc/sys/net/ipv4/ip_forward
# 永久生效
echo"net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
# DNAT:将外部访问本机8080端口的流量转发到内网10.0.2.100:80
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.2.100:80
# 配套的FORWARD规则(允许转发流量通过)
iptables -A FORWARD -p tcp -d 10.0.2.100 --dport 80 -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
# SNAT:内网机器通过本机上网(本机外网IP为203.0.113.10)
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j SNAT --to-source 203.0.113.10
# MASQUERADE:动态IP场景下的SNAT(拨号上网等)
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j MASQUERADE
# 本机端口转发:将本机80端口转发到8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
规则持久化:
# CentOS / RHEL(使用iptables-services)
service iptables save
# 规则保存在 /etc/sysconfig/iptables
# 手动保存和恢复
iptables-save > /etc/iptables.rules
iptables-restore < /etc/iptables.rules
# Ubuntu / Debian(使用iptables-persistent)
sudo apt install -y iptables-persistent
# 安装时会提示保存当前规则
# 后续保存
sudo netfilter-persistent save
# 规则保存在 /etc/iptables/rules.v4 和 rules.v6
# 开机自动加载(如果没装iptables-persistent)
# 在 /etc/rc.local 中添加:
iptables-restore < /etc/iptables.rules
实战案例:排查端口不通
开发反馈新部署的服务(端口9090)从外部访问不了,但在服务器本地curl是通的。排查过程:
# 第一步:确认服务在监听
ss -tlnp | grep 9090
# 输出:LISTEN 0 128 0.0.0.0:9090 0.0.0.0:* users:(("java",pid=12345,fd=50))
# 监听正常,绑定的是0.0.0.0
# 第二步:检查iptables规则
iptables -L INPUT -n --line-numbers
# 输出:
# num target prot opt source destination
# 1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
# 2 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
# 3 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
# 4 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
# 5 DROP all -- 0.0.0.0/0 0.0.0.0/0
# 第5条DROP规则把9090端口的流量拦截了
# 第三步:在DROP规则之前插入允许9090的规则
iptables -I INPUT 5 -p tcp --dport 9090 -j ACCEPT
# 第四步:保存规则
service iptables save
# 第五步:验证
iptables -L INPUT -n --line-numbers
规则顺序很关键,iptables是从上到下匹配的,匹配到就不再往下走。所以ACCEPT规则一定要放在DROP/REJECT之前。
除了三板斧之外,还有几个工具在排障时经常配合使用。
ping / traceroute / mtr:
# 基础连通性测试
ping -c 10 10.0.2.100
# 带时间戳的ping(排查间歇性丢包)
ping -c 100 -D 10.0.2.100
# 设置包大小(排查MTU问题,1472+28=1500)
ping -c 5 -s 1472 -M do 10.0.2.100
# 如果报 "message too long",说明路径上有MTU小于1500的设备
# traceroute查看路由路径
traceroute -n 10.0.2.100
# TCP模式的traceroute(ICMP被封时用)
traceroute -n -T -p 80 10.0.2.100
# mtr是ping和traceroute的结合体,实时显示每一跳的丢包率和延迟
mtr -n -c 100 10.0.2.100
# mtr生成报告模式
mtr -n -r -c 200 10.0.2.100
mtr的输出比traceroute好用很多,能看到每一跳的丢包率。生产环境排查网络抖动时,我们团队的做法是跑200个包的mtr报告,发给网络组分析。
curl(HTTP层排障):
# 查看HTTP响应头
curl -I http://10.0.1.50:8080/health
# 查看详细的连接过程(DNS解析、TCP握手、TLS握手、首字节时间)
curl -w "\nDNS解析: %{time_namelookup}s\nTCP连接: %{time_connect}s\nTLS握手: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总耗时: %{time_total}s\n" -o /dev/null -s http://10.0.1.50:8080/api/test
# 指定超时时间
curl --connect-timeout 3 --max-time 10 http://10.0.1.50:8080/health
# 指定源IP发请求(多网卡机器上有用)
curl --interface 10.0.1.10 http://10.0.2.100:8080/health
# 跳过DNS直接指定IP
curl -H "Host: api.example.com" http://10.0.1.50:8080/api/test
dig / nslookup(DNS排障):
# 查询A记录
dig api.example.com
# 指定DNS服务器查询
dig @8.8.8.8 api.example.com
# 查询详细的解析过程(+trace)
dig +trace api.example.com
# 查询反向解析
dig -x 10.0.1.50
# 简洁输出(只要IP)
dig +short api.example.com
# 查询特定记录类型
dig api.example.com MX
dig api.example.com CNAME
dig api.example.com NS
nc(netcat,端口连通性测试):
# 测试TCP端口是否通
nc -zv 10.0.2.100 3306
# 测试UDP端口
nc -zuv 10.0.2.100 53
# 扫描端口范围
nc -zv 10.0.2.100 8000-9000
# 设置超时时间(秒)
nc -zv -w 3 10.0.2.100 3306
# 用nc做简单的文件传输(接收端)
nc -l 9999 > received_file
# 用nc做简单的文件传输(发送端)
nc 10.0.2.100 9999 < send_file
iptables作为防火墙服务需要确保开机自启:
# CentOS 7 使用iptables-services
sudo systemctl start iptables
sudo systemctl enable iptables
sudo systemctl status iptables
# 确认规则已加载
iptables -L -n | head -20
# Ubuntu 使用 netfilter-persistent
sudo systemctl enable netfilter-persistent
sudo systemctl start netfilter-persistent
部署完防火墙规则后,必须做验证。我们团队有个规矩:改完防火墙规则,必须从外部测试一遍所有业务端口。
# 从另一台机器验证端口连通性
for port in 22 80 443 8080 9090; do
nc -zv -w 3 目标IP $port 2>&1
done
# 验证NAT转发是否生效
curl -s -o /dev/null -w "%{http_code}" http://外网IP:8080/health
# 验证iptables规则的包计数(看规则是否被命中)
iptables -L -n -v | grep -E "dpt:(80|443|9090)"
# 验证连接跟踪表
cat /proc/net/nf_conntrack | wc -l
# 或者
conntrack -L | wc -l
这是我们团队在生产环境用的iptables初始化脚本,经过多次迭代,覆盖了Web服务器的常见需求。直接保存为/usr/local/sbin/iptables-init.sh,赋予执行权限即可。
#!/bin/bash
# 文件路径:/usr/local/sbin/iptables-init.sh
# 功能:生产环境iptables规则初始化
# 使用方法:sudo bash /usr/local/sbin/iptables-init.sh
# 注意:执行前务必确认SSH端口正确,否则会把自己锁在外面
set -e
# ========== 配置区域 ==========
SSH_PORT=22
HTTP_PORT=80
HTTPS_PORT=443
APP_PORTS="8080 9090"
MYSQL_PORT=3306
REDIS_PORT=6379
# 允许访问MySQL和Redis的内网网段
TRUSTED_NET="10.0.0.0/8"
# 管理员IP(紧急情况下放行所有端口)
ADMIN_IPS="10.0.1.5 10.0.1.6"
# 日志前缀
LOG_PREFIX="IPTABLES-DROP: "
# ========== 配置区域结束 ==========
echo"[INFO] 开始配置iptables规则..."
# 清空现有规则
iptables -F
iptables -X
iptables -Z
iptables -t nat -F
iptables -t nat -X
# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
echo"[INFO] 默认策略已设置"
# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT
# 允许已建立的连接(这条必须在最前面,否则回包会被拦)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 管理员IP放行所有
for ip in$ADMIN_IPS; do
iptables -A INPUT -s $ip -j ACCEPT
done
# 允许ICMP(限速防ping flood)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 10/s --limit-burst 20 -j ACCEPT
# SSH端口(限制连接频率,防暴力破解)
iptables -A INPUT -p tcp --dport $SSH_PORT -m state --state NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport $SSH_PORT -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name SSH -j DROP
iptables -A INPUT -p tcp --dport $SSH_PORT -j ACCEPT
# HTTP/HTTPS端口
iptables -A INPUT -p tcp --dport $HTTP_PORT -j ACCEPT
iptables -A INPUT -p tcp --dport $HTTPS_PORT -j ACCEPT
# 应用端口
for port in$APP_PORTS; do
iptables -A INPUT -p tcp --dport $port -j ACCEPT
done
# MySQL和Redis只允许内网访问
iptables -A INPUT -s $TRUSTED_NET -p tcp --dport $MYSQL_PORT -j ACCEPT
iptables -A INPUT -s $TRUSTED_NET -p tcp --dport $REDIS_PORT -j ACCEPT
# 防SYN Flood
iptables -A INPUT -p tcp --syn -m limit --limit 100/s --limit-burst 200 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP
# 记录被丢弃的包(限制日志频率,防止日志撑爆磁盘)
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "$LOG_PREFIX" --log-level 4
# 最终拒绝(用REJECT而不是DROP,让客户端快速收到拒绝而不是等超时)
iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited
echo"[INFO] 规则配置完成"
# 保存规则
ifcommand -v service &> /dev/null && systemctl is-active iptables &> /dev/null; then
service iptables save
echo"[INFO] 规则已保存(iptables-services)"
elifcommand -v netfilter-persistent &> /dev/null; then
netfilter-persistent save
echo"[INFO] 规则已保存(netfilter-persistent)"
else
iptables-save > /etc/iptables.rules
echo"[INFO] 规则已保存到 /etc/iptables.rules"
fi
# 显示最终规则
echo""
echo"========== 当前规则 =========="
iptables -L -n --line-numbers
echo""
echo"[INFO] 配置完成,请从外部验证SSH连接是否正常!"
线上排障时经常需要快速收集网络信息,每次手动敲命令太慢。这个脚本把常用的诊断命令打包在一起,一键生成诊断报告。
#!/bin/bash
# 文件路径:/usr/local/sbin/net-diag.sh
# 功能:网络诊断信息一键收集
# 使用方法:sudo bash /usr/local/sbin/net-diag.sh [目标IP] [目标端口]
# 输出:诊断报告保存到 /tmp/net-diag-时间戳.txt
TARGET_IP=${1:-""}
TARGET_PORT=${2:-""}
REPORT_FILE="/tmp/net-diag-$(date +%Y%m%d_%H%M%S).txt"
log() {
echo"========== $1 ==========" | tee -a "$REPORT_FILE"
}
run_cmd() {
echo">>> $1" | tee -a "$REPORT_FILE"
eval"$1" 2>&1 | tee -a "$REPORT_FILE"
echo"" | tee -a "$REPORT_FILE"
}
echo"网络诊断报告 - $(date)" | tee "$REPORT_FILE"
echo"主机名: $(hostname)" | tee -a "$REPORT_FILE"
echo"目标IP: ${TARGET_IP:-未指定}" | tee -a "$REPORT_FILE"
echo"目标端口: ${TARGET_PORT:-未指定}" | tee -a "$REPORT_FILE"
echo"" | tee -a "$REPORT_FILE"
log"1. 网卡信息"
run_cmd "ip addr show"
log"2. 路由表"
run_cmd "ip route show"
log"3. DNS配置"
run_cmd "cat /etc/resolv.conf"
log"4. TCP连接状态统计"
run_cmd "ss -s"
log"5. 连接状态分布"
run_cmd "ss -tan | awk 'NR>1 {print \$1}' | sort | uniq -c | sort -rn"
log"6. 监听端口"
run_cmd "ss -tlnp"
log"7. 连接数Top20 IP"
run_cmd "ss -tn state established | awk 'NR>1 {print \$5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20"
log"8. iptables规则"
run_cmd "iptables -L -n -v --line-numbers"
run_cmd "iptables -t nat -L -n -v --line-numbers"
log"9. 内核网络参数"
run_cmd "sysctl net.ipv4.tcp_tw_reuse"
run_cmd "sysctl net.ipv4.tcp_max_tw_buckets"
run_cmd "sysctl net.core.somaxconn"
run_cmd "sysctl net.ipv4.tcp_max_syn_backlog"
run_cmd "sysctl net.core.netdev_max_backlog"
run_cmd "sysctl net.nf_conntrack_max 2>/dev/null || echo 'conntrack模块未加载'"
log"10. 网卡丢包统计"
run_cmd "ip -s link show"
log"11. 连接跟踪表使用情况"
run_cmd "cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null || echo 'N/A'"
run_cmd "cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null || echo 'N/A'"
# 如果指定了目标IP,做连通性测试
if [ -n "$TARGET_IP" ]; then
log"12. 目标连通性测试"
run_cmd "ping -c 5 -W 2 $TARGET_IP"
run_cmd "traceroute -n -w 2 $TARGET_IP 2>/dev/null || echo 'traceroute未安装'"
if [ -n "$TARGET_PORT" ]; then
log"13. 目标端口测试"
run_cmd "nc -zv -w 3 $TARGET_IP$TARGET_PORT 2>&1"
run_cmd "curl -s -o /dev/null -w 'HTTP状态码: %{http_code}\nDNS解析: %{time_namelookup}s\nTCP连接: %{time_connect}s\n首字节: %{time_starttransfer}s\n总耗时: %{time_total}s\n' http://$TARGET_IP:$TARGET_PORT/ 2>/dev/null || echo '非HTTP服务'"
fi
fi
echo""
echo"诊断报告已保存到: $REPORT_FILE"
场景描述:某微服务集群中,服务A调用服务B的接口,偶发超时(约2%的请求耗时超过3秒)。应用日志只显示"connection timeout",没有更多信息。
排查过程:
第一步,在服务A所在机器上抓包:
# 抓取与服务B(10.0.3.20:8080)的通信,保存pcap
tcpdump -i eth0 -nn -tttt -w /tmp/svc_debug.pcap host 10.0.3.20 and port 8080 -c 50000
# 等待5分钟,收集足够样本
第二步,分析pcap文件中的TCP握手:
# 提取所有SYN包(新建连接的第一个包)
tcpdump -nn -r /tmp/svc_debug.pcap 'tcp[tcpflags] = tcp-syn' | wc -l
# 输出:3847
# 提取所有SYN重传(同一个源端口出现多次SYN)
tcpdump -nn -r /tmp/svc_debug.pcap 'tcp[tcpflags] = tcp-syn' | awk '{print $3}' | sort | uniq -c | sort -rn | awk '$1>1' | head -20
# 发现有78个连接出现了SYN重传
# 查看重传的SYN包时间间隔
tcpdump -nn -tttt -r /tmp/svc_debug.pcap 'tcp[tcpflags] = tcp-syn' | grep "10.0.1.10.49832"
# 输出:
# 2024-03-15 15:23:01.123456 IP 10.0.1.10.49832 > 10.0.3.20.8080: Flags [S]
# 2024-03-15 15:23:02.125123 IP 10.0.1.10.49832 > 10.0.3.20.8080: Flags [S]
# 2024-03-15 15:23:04.129456 IP 10.0.1.10.49832 > 10.0.3.20.8080: Flags [S]
# SYN重传间隔1s、2s,符合TCP指数退避
第三步,在服务B上检查:
# 检查全连接队列
ss -tlnp | grep 8080
# 输出:LISTEN 129 128 0.0.0.0:8080 0.0.0.0:*
# Recv-Q(129) > Send-Q(128),全连接队列溢出!
# 确认溢出次数
nstat -az TcpExtListenOverflows
# TcpExtListenOverflows 12847 0.0
# 溢出了12847次
# 查看当前backlog设置
sysctl net.core.somaxconn
# net.core.somaxconn = 128
解决方案:
# 调大系统级backlog上限
sysctl -w net.core.somaxconn=65535
echo"net.core.somaxconn = 65535" >> /etc/sysctl.conf
# 调大半连接队列
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
echo"net.ipv4.tcp_max_syn_backlog = 65535" >> /etc/sysctl.conf
sysctl -p
# 同时需要修改应用的listen backlog参数
# Nginx: listen 8080 backlog=65535;
# Java Tomcat: server.tomcat.accept-count=65535
# Go: net.Listen设置后调用 syscall.SetsockoptInt
修改后超时率从2%降到0.01%以下。
场景描述:需要持续监控服务器的TCP连接状态变化,在连接数异常时自动告警。这个脚本我们跑在所有Web服务器和中间件服务器上,配合crontab每分钟执行一次。
实现代码:
#!/bin/bash
# 文件路径:/usr/local/sbin/tcp-monitor.sh
# 功能:TCP连接状态监控,超阈值发送告警
# crontab配置:* * * * * /usr/local/sbin/tcp-monitor.sh
# ========== 配置 ==========
HOSTNAME=$(hostname)
LOG_FILE="/var/log/tcp-monitor.log"
ALERT_SCRIPT="/usr/local/sbin/send-alert.sh"
# 告警阈值
ESTABLISHED_WARN=5000
ESTABLISHED_CRIT=10000
TIME_WAIT_WARN=10000
TIME_WAIT_CRIT=30000
CLOSE_WAIT_WARN=100
CLOSE_WAIT_CRIT=500
TOTAL_WARN=20000
TOTAL_CRIT=50000
# ========== 配置结束 ==========
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
# 获取各状态连接数
get_count() {
ss -tan state "$1" 2>/dev/null | tail -n +2 | wc -l
}
ESTAB=$(get_count established)
TW=$(get_count time-wait)
CW=$(get_count close-wait)
FW1=$(get_count fin-wait-1)
FW2=$(get_count fin-wait-2)
SYN_RECV=$(get_count syn-recv)
LAST_ACK=$(get_count last-ack)
TOTAL=$(ss -tan | tail -n +2 | wc -l)
# 记录日志
echo"$TIMESTAMP ESTAB=$ESTAB TW=$TW CW=$CW FW1=$FW1 FW2=$FW2 SYN_RECV=$SYN_RECV LAST_ACK=$LAST_ACK TOTAL=$TOTAL" >> "$LOG_FILE"
# 告警判断
alert() {
local level=$1
local msg=$2
echo"$TIMESTAMP [$level] $msg" >> "$LOG_FILE"
if [ -x "$ALERT_SCRIPT" ]; then
$ALERT_SCRIPT"$level""$HOSTNAME""$msg"
fi
}
if [ "$TOTAL" -ge "$TOTAL_CRIT" ]; then
alert "CRITICAL""TCP总连接数=$TOTAL 超过阈值$TOTAL_CRIT"
elif [ "$TOTAL" -ge "$TOTAL_WARN" ]; then
alert "WARNING""TCP总连接数=$TOTAL 超过阈值$TOTAL_WARN"
fi
if [ "$ESTAB" -ge "$ESTABLISHED_CRIT" ]; then
alert "CRITICAL""ESTABLISHED=$ESTAB 超过阈值$ESTABLISHED_CRIT"
# 自动收集Top IP信息
TOP_IPS=$(ss -tn state established | awk 'NR>1{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -5)
echo"$TIMESTAMP [INFO] Top5 IP: $TOP_IPS" >> "$LOG_FILE"
elif [ "$ESTAB" -ge "$ESTABLISHED_WARN" ]; then
alert "WARNING""ESTABLISHED=$ESTAB 超过阈值$ESTABLISHED_WARN"
fi
if [ "$TW" -ge "$TIME_WAIT_CRIT" ]; then
alert "CRITICAL""TIME_WAIT=$TW 超过阈值$TIME_WAIT_CRIT"
elif [ "$TW" -ge "$TIME_WAIT_WARN" ]; then
alert "WARNING""TIME_WAIT=$TW 超过阈值$TIME_WAIT_WARN"
fi
if [ "$CW" -ge "$CLOSE_WAIT_CRIT" ]; then
alert "CRITICAL""CLOSE_WAIT=$CW 超过阈值$CLOSE_WAIT_CRIT,应用可能未正确关闭连接"
# CLOSE_WAIT多了一定是应用的问题,自动收集进程信息
CW_PROCS=$(ss -tnp state close-wait | awk 'NR>1{print $6}' | sort | uniq -c | sort -rn | head -5)
echo"$TIMESTAMP [INFO] CLOSE_WAIT进程: $CW_PROCS" >> "$LOG_FILE"
elif [ "$CW" -ge "$CLOSE_WAIT_WARN" ]; then
alert "WARNING""CLOSE_WAIT=$CW 超过阈值$CLOSE_WAIT_WARN"
fi
# 日志轮转(保留7天)
find /var/log/ -name "tcp-monitor.log.*" -mtime +7 -delete 2>/dev/null
运行结果示例:
2024-03-15 15:30:01 ESTAB=2341 TW=892 CW=3 FW1=0 FW2=12 SYN_RECV=0 LAST_ACK=1 TOTAL=3249
2024-03-15 15:31:01 ESTAB=2356 TW=901 CW=3 FW1=1 FW2=8 SYN_RECV=0 LAST_ACK=0 TOTAL=3269
2024-03-15 15:32:01 ESTAB=8923 TW=15234 CW=3 FW1=2 FW2=45 SYN_RECV=12 LAST_ACK=3 TOTAL=24222
2024-03-15 15:32:01 [WARNING] TCP总连接数=24222 超过阈值20000
2024-03-15 15:32:01 [WARNING] ESTABLISHED=8923 超过阈值5000
2024-03-15 15:32:01 [WARNING] TIME_WAIT=15234 超过阈值10000
生产环境抓包不是随便抓的,搞不好会影响业务性能或者撑爆磁盘。我们团队总结了几条铁律:
限制抓包数量或时间:永远不要不加-c或-G参数就跑tcpdump。实测万兆网卡满载时,不限制的tcpdump每秒能产生500MB以上的pcap文件。
# 正确做法:限制包数量
tcpdump -i eth0 -nn -c 10000 -w /tmp/debug.pcap port 80
# 正确做法:限制时间(300秒后自动停止)
timeout 300 tcpdump -i eth0 -nn -w /tmp/debug.pcap port 80
# 正确做法:限制文件大小,100MB轮转,最多5个文件
tcpdump -i eth0 -nn -w /tmp/debug.pcap -C 100 -W 5 port 80
尽量缩小过滤范围:过滤条件越精确,对系统的影响越小。
# 不推荐:抓所有流量
tcpdump -i eth0 -w /tmp/all.pcap
# 推荐:精确过滤
tcpdump -i eth0 -nn -w /tmp/debug.pcap host 10.0.2.100 and port 3306 -c 5000
抓包文件放/tmp或专用目录:不要放在根分区或业务数据分区,万一文件太大不会影响业务。
抓完及时清理:pcap文件很大,抓完分析完就删。我见过有人在生产服务器上留了200GB的pcap文件忘了删,直接把磁盘撑满导致服务挂了。
iptables规则是线性匹配的,规则越多性能越差。实测1000条规则时,每个包的匹配延迟约增加0.1ms。看起来不多,但高并发场景下累积起来很可观。
高频匹配的规则放前面:ESTABLISHED,RELATED那条规则必须放在INPUT链的最前面,因为绝大多数包都是已建立连接的数据包,一条就能匹配掉90%以上的流量。
# 第一条规则永远是这个
iptables -I INPUT 1 -m state --state ESTABLISHED,RELATED -j ACCEPT
用ipset替代大量IP规则:如果要封禁几百个IP,不要一条一条加iptables规则。用ipset创建IP集合,一条规则搞定。
# 创建IP集合
ipset create blacklist hash:ip maxelem 100000
# 添加IP到集合
ipset add blacklist 1.2.3.4
ipset add blacklist 5.6.7.8
# 从文件批量导入
whileread ip; do
ipset add blacklist "$ip" 2>/dev/null
done < /tmp/bad_ips.txt
# 一条iptables规则匹配整个集合
iptables -I INPUT 2 -m set --match-set blacklist src -j DROP
# 查看集合内容
ipset list blacklist
实测对比:500条独立的iptables规则 vs 1条ipset规则,后者匹配速度快50倍以上。
规则注释:给每条规则加注释,方便后续维护。
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT -m comment --comment "允许应用8080端口-工单#12345"
改防火墙规则是高危操作,改错了可能直接把自己锁在外面。我们团队的做法:
变更前备份当前规则:
iptables-save > /tmp/iptables-backup-$(date +%Y%m%d_%H%M%S).rules
设置自动回滚:改规则之前,先设一个定时任务在10分钟后恢复原规则。确认新规则没问题后再取消定时任务。
# 备份当前规则
iptables-save > /tmp/iptables-rollback.rules
# 设置10分钟后自动回滚
echo"iptables-restore < /tmp/iptables-rollback.rules" | at now + 10 minutes
# 执行变更
iptables -I INPUT 5 -p tcp --dport 9090 -j ACCEPT
# 确认没问题后,取消回滚任务
atq # 查看任务编号
atrm 任务号 # 删除回滚任务
远程操作时保持两个SSH会话:一个做变更,另一个保持不动。万一第一个断了,第二个还能用来恢复。
永远不要远程执行iptables -P INPUT DROP后再加规则:应该先加好ACCEPT规则,最后再改默认策略。顺序搞反了就等着去机房接显示器吧。
网络排障经常涉及内核参数调整,这里列出生产环境建议值:
# /etc/sysctl.conf 网络相关参数
# TCP连接复用(客户端角色时有效)
net.ipv4.tcp_tw_reuse = 1
# TIME-WAIT socket最大数量(超过后直接销毁)
net.ipv4.tcp_max_tw_buckets = 50000
# 全连接队列上限(配合应用的backlog参数)
net.core.somaxconn = 65535
# 半连接队列上限
net.ipv4.tcp_max_syn_backlog = 65535
# 网卡接收队列长度
net.core.netdev_max_backlog = 65535
# TCP接收/发送缓冲区(最小值、默认值、最大值,单位字节)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 连接跟踪表大小(每条记录约300字节,100万条约300MB内存)
net.nf_conntrack_max = 1000000
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
# 本地端口范围(客户端发起连接时使用)
net.ipv4.ip_local_port_range = 1024 65535
# TCP keepalive参数
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
# 开启SYN Cookie(防SYN Flood)
net.ipv4.tcp_syncookies = 1
# TCP FIN超时时间(默认60秒,可以适当调小)
net.ipv4.tcp_fin_timeout = 15
# 使参数生效
sysctl -p
ip addr | ||
sysctl -w net.ipv4.ip_forward=1 | ||
iptables vs nftables:CentOS 8+和Ubuntu 20.04+默认用nftables后端。iptables命令仍然可用(通过iptables-nft兼容层),但iptables-save的输出格式可能不同。如果需要在新旧系统间迁移规则,建议统一用iptables-legacy或全部切换到nft命令。
ss版本差异:老版本的ss(iproute2 < 4.15)不支持部分过滤语法,比如ss -tn state time-wait在某些老系统上需要写成ss -tn -o state time-wait。遇到语法报错时先检查版本。
tcpdump在容器中的使用:在Docker容器内抓包需要--cap-add=NET_ADMIN权限。更好的做法是在宿主机上用nsenter进入容器的网络命名空间抓包:
# 获取容器PID
PID=$(docker inspect -f '{{.State.Pid}}' 容器名)
# 进入容器网络命名空间抓包
nsenter -t $PID -n tcpdump -i eth0 -nn port 80 -c 100
网络问题的日志分散在好几个地方,要养成习惯逐个检查:
# 查看系统日志中的网络相关信息
journalctl -k | grep -iE "drop|reject|error|link|eth|net" | tail -50
# 查看iptables丢弃日志(前面配置了LOG规则的话)
grep "IPTABLES-DROP" /var/log/messages | tail -30
# Ubuntu系统
grep "IPTABLES-DROP" /var/log/kern.log | tail -30
# 查看dmesg中的网络错误
dmesg | grep -iE "link|eth|net|drop|error|nf_conntrack" | tail -30
# 查看conntrack表满的告警
dmesg | grep "nf_conntrack: table full"
# 实时监控内核日志
dmesg -wH
问题一:端口监听正常但外部访问不通
这是最常见的网络问题,排查思路是从内到外逐层检查:
# 第一步:确认服务在监听,且绑定地址不是127.0.0.1
ss -tlnp | grep 8080
# 如果显示 127.0.0.1:8080 说明只监听了本地,需要改成 0.0.0.0:8080
# 第二步:本机测试
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/health
# 返回200说明服务本身没问题
# 第三步:检查iptables
iptables -L INPUT -n -v --line-numbers | grep -E "8080|DROP|REJECT"
# 看8080端口是否有ACCEPT规则,以及DROP/REJECT规则的位置
# 第四步:临时关闭防火墙测试(确认是不是防火墙的问题)
# 注意:生产环境慎用,测完立刻恢复
iptables-save > /tmp/iptables-backup.rules
iptables -F INPUT
iptables -P INPUT ACCEPT
# 测试完恢复
iptables-restore < /tmp/iptables-backup.rules
# 第五步:检查云平台安全组(如果是云服务器)
# AWS/阿里云/腾讯云都有安全组,这个在OS层面看不到
# 需要去云控制台检查
解决方案:
问题二:TCP连接建立慢(首包延迟高)
用户反馈接口偶尔响应慢,但服务端日志显示处理时间正常。这种情况大概率是网络层的问题。
# 用curl精确测量各阶段耗时
curl -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总计: %{time_total}s\n" -o /dev/null -s http://10.0.2.100:8080/api/test
# 如果TCP连接时间异常(正常内网应该<1ms)
# 在客户端抓SYN和SYN-ACK
tcpdump -i eth0 -nn -tttt 'tcp[tcpflags] & (tcp-syn) != 0' and host 10.0.2.100 -c 200
# 检查服务端的全连接队列是否溢出
ssh 10.0.2.100 "ss -tlnp | grep 8080; nstat -az TcpExtListenOverflows"
# 检查网络路径上的丢包
mtr -n -r -c 100 10.0.2.100
解决方案:
问题三:CLOSE_WAIT连接持续增长
CLOSE_WAIT状态说明对端已经关闭连接(发了FIN),但本端应用没有调用close()。这100%是应用的bug。
# 查看CLOSE_WAIT连接数量和归属进程
ss -tnp state close-wait
# 按进程统计CLOSE_WAIT数量
ss -tnp state close-wait | awk '{print $NF}' | sort | uniq -c | sort -rn
# 查看具体是连接到哪些远程地址
ss -tn state close-wait | awk 'NR>1{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
问题四:conntrack表满导致间歇性丢包
高并发服务器上经常遇到的问题。conntrack表满了之后,新连接会被直接丢弃,表现为间歇性连接超时。
# 检查conntrack表使用情况
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 如果count接近max,就是表满了
# 查看dmesg确认
dmesg | grep "nf_conntrack: table full"
# 查看conntrack表中各状态的分布
conntrack -L 2>/dev/null | awk '{print $4}' | sort | uniq -c | sort -rn
解决方案:
# 方案一:调大conntrack表(每条约300字节,100万条约300MB)
sysctl -w net.nf_conntrack_max=1000000
echo"net.nf_conntrack_max = 1000000" >> /etc/sysctl.conf
# 缩短各状态的超时时间
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_close_wait=60
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
# 方案二:对不需要跟踪的流量跳过conntrack(比如Web服务器的80/443端口)
iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
iptables -t raw -A PREROUTING -p tcp --sport 80 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 80 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --dport 80 -j NOTRACK
# 注意:NOTRACK的流量不能使用state匹配,需要单独加ACCEPT规则
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
有些网络问题比较隐蔽,需要开启更详细的调试信息。
# 开启iptables的TRACE功能(跟踪数据包经过的每条链和规则)
modprobe nf_log_ipv4
sysctl -w net.netfilter.nf_log.2=nf_log_ipv4
# 对特定流量开启TRACE
iptables -t raw -A PREROUTING -p tcp --dport 8080 -s 10.0.1.50 -j TRACE
iptables -t raw -A OUTPUT -p tcp --sport 8080 -d 10.0.1.50 -j TRACE
# 查看TRACE日志
journalctl -k -f | grep TRACE
# 或者
tail -f /var/log/kern.log | grep TRACE
# 输出示例:
# TRACE: raw:PREROUTING:policy:2 IN=eth0 ... SRC=10.0.1.50 DST=10.0.2.100 ... DPT=8080
# TRACE: filter:INPUT:rule:3 IN=eth0 ... SRC=10.0.1.50 DST=10.0.2.100 ... DPT=8080
# 排查完毕后关闭TRACE(TRACE会产生大量日志)
iptables -t raw -D PREROUTING -p tcp --dport 8080 -s 10.0.1.50 -j TRACE
iptables -t raw -D OUTPUT -p tcp --sport 8080 -d 10.0.1.50 -j TRACE
TCP层面的调试:
# 查看TCP重传统计
nstat -az | grep -i retrans
# TcpRetransSegs 重传的段数
# TcpExtTCPSlowStartRetrans 慢启动阶段的重传
# 查看TCP错误统计
nstat -az | grep -iE "abort|reset|overflow|drop"
# 实时监控TCP统计变化(每秒刷新)
watch -n 1 "nstat -az | grep -iE 'retrans|overflow|drop|abort' | grep -v ' 0 '"
# 查看某个连接的详细TCP信息(RTT、拥塞窗口、重传次数)
ss -ti dst 10.0.2.100
# 输出示例:
# cubic wscale:7,7 rto:204 rtt:0.523/0.05 ato:40 mss:1448 pmtu:1500
# rcvmss:1448 advmss:1448 cwnd:10 bytes_sent:1234 bytes_acked:1234
# bytes_received:5678 segs_out:50 segs_in:60 data_segs_out:25 data_segs_in:30
# send 221.5Mbps lastsnd:100 lastrcv:50 lastack:50 pacing_rate 442.9Mbps
# delivery_rate 180.2Mbps delivered:26 busy:200ms
日常巡检和故障排查时需要关注的核心网络指标:
# 网卡流量(每秒刷新)
watch -n 1 "cat /proc/net/dev | awk 'NR>2{printf \"%-12s RX: %10d bytes TX: %10d bytes\n\", \$1, \$2, \$10}'"
# 更直观的方式用sar(需要安装sysstat)
sar -n DEV 1 5
# 网卡错误和丢包
ip -s link show eth0
# TCP连接状态实时监控
watch -n 2 "ss -s"
# 连接跟踪表使用率
watch -n 5 "echo \"conntrack: $(cat /proc/sys/net/netfilter/nf_conntrack_count)/$(cat /proc/sys/net/netfilter/nf_conntrack_max)\""
# 网卡队列统计(需要ethtool)
ethtool -S eth0 | grep -E "rx_|tx_" | grep -v ": 0$"
# 软中断分布(网络密集型服务器要关注)
cat /proc/softirqs | grep -E "NET_RX|NET_TX"
# 查看各CPU的软中断处理情况(判断是否需要开启RPS/RFS)
cat /proc/interrupts | grep eth0
用node_exporter采集网络指标,配合Prometheus告警规则,实现自动化监控。node_exporter默认就会采集网络相关的指标,不需要额外配置。
# Prometheus告警规则文件:/etc/prometheus/rules/network_alerts.yml
groups:
-name:network_alerts
rules:
# TCP连接数过高
-alert:TcpConnectionsHigh
expr:node_netstat_Tcp_CurrEstab>10000
for:5m
labels:
severity:warning
annotations:
summary:"TCP连接数过高 {{ $labels.instance }}"
description:"当前ESTABLISHED连接数: {{ $value }},超过10000阈值"
# TIME_WAIT过多
-alert:TcpTimeWaitHigh
expr:node_sockstat_TCP_tw>20000
for:5m
labels:
severity:warning
annotations:
summary:"TIME_WAIT连接过多 {{ $labels.instance }}"
description:"当前TIME_WAIT数: {{ $value }}"
# conntrack表使用率过高
-alert:ConntrackTableNearFull
expr:node_nf_conntrack_entries/node_nf_conntrack_entries_limit*100>85
for:2m
labels:
severity:critical
annotations:
summary:"conntrack表即将满 {{ $labels.instance }}"
description:"使用率: {{ $value | printf \"%.1f\" }}%"
# 网卡丢包
-alert:NetworkInterfaceDrops
expr:rate(node_network_receive_drop_total[5m])>0
for:5m
labels:
severity:warning
annotations:
summary:"网卡丢包 {{ $labels.instance }}{{ $labels.device }}"
description:"丢包速率: {{ $value | printf \"%.2f\" }}/s"
# TCP重传率过高
-alert:TcpRetransmissionHigh
expr:rate(node_netstat_Tcp_RetransSegs[5m])/rate(node_netstat_Tcp_OutSegs[5m])*100>1
for:10m
labels:
severity:warning
annotations:
summary:"TCP重传率过高 {{ $labels.instance }}"
description:"重传率: {{ $value | printf \"%.2f\" }}%"
# 全连接队列溢出
-alert:ListenOverflows
expr:rate(node_netstat_TcpExt_ListenOverflows[5m])>0
for:2m
labels:
severity:critical
annotations:
summary:"全连接队列溢出 {{ $labels.instance }}"
description:"溢出速率: {{ $value | printf \"%.2f\" }}/s,应用处理不过来"
# 网卡带宽使用率
-alert:NetworkBandwidthHigh
expr:(rate(node_network_transmit_bytes_total{device="eth0"}[5m])*8)/1000000000*100>80
for:10m
labels:
severity:warning
annotations:
summary:"网卡带宽使用率过高 {{ $labels.instance }}"
description:"出方向带宽使用率: {{ $value | printf \"%.1f\" }}%"
防火墙规则是服务器安全的最后一道防线,必须有备份。我们团队的做法是每天自动备份一次,保留30天。
#!/bin/bash
# 文件路径:/usr/local/sbin/iptables-backup.sh
# crontab配置:0 2 * * * /usr/local/sbin/iptables-backup.sh
BACKUP_DIR="/opt/backup/iptables"
KEEP_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
HOSTNAME=$(hostname)
mkdir -p "$BACKUP_DIR"
# 备份iptables规则
iptables-save > "${BACKUP_DIR}/${HOSTNAME}_iptables_${DATE}.rules"
# 备份内核网络参数
sysctl -a 2>/dev/null | grep -E "^net\." > "${BACKUP_DIR}/${HOSTNAME}_sysctl_${DATE}.conf"
# 如果有ipset,也备份
ifcommand -v ipset &> /dev/null; then
ipset save > "${BACKUP_DIR}/${HOSTNAME}_ipset_${DATE}.rules" 2>/dev/null
fi
# 清理过期备份
find "$BACKUP_DIR" -name "*.rules" -mtime +${KEEP_DAYS} -delete
find "$BACKUP_DIR" -name "*.conf" -mtime +${KEEP_DAYS} -delete
echo"[$(date)] iptables备份完成: ${BACKUP_DIR}/${HOSTNAME}_iptables_${DATE}.rules"
恢复防火墙规则的操作步骤。恢复前一定要确认备份文件的内容是正确的,不要恢复了一个有问题的规则把自己锁在外面。
检查备份文件内容:
# 先看一下备份文件的内容,确认规则是合理的
cat /opt/backup/iptables/web01_iptables_20240315_020000.rules
# 确认里面有SSH的ACCEPT规则
grep "dpt:22" /opt/backup/iptables/web01_iptables_20240315_020000.rules
恢复规则:
# 恢复iptables规则
iptables-restore < /opt/backup/iptables/web01_iptables_20240315_020000.rules
验证规则:
# 查看恢复后的规则
iptables -L -n --line-numbers
# 从外部测试SSH连接
# 从外部测试业务端口
持久化:
# CentOS
service iptables save
# Ubuntu
netfilter-persistent save
-nn -tttt -w三个常用选项,过滤表达式掌握host、port、tcp flags这几个就够应付大多数场景。ss -s看全局概览,ss -tn state xxx按状态过滤,ss -tlnp看监听队列。CLOSE_WAIT持续增长一定是应用bug,TIME_WAIT过多要用连接池。网络问题的排查有一套固定的方法论,按层次从下往上排查:
ip -s link show)ping、traceroute、mtr)ss、tcpdump)iptables -L -n -v)curl、应用日志)90%的网络问题在第3和第4层,所以tcpdump、ss、iptables这三个工具用熟了,排障效率会提升很多。
eBPF/BCC工具集:比tcpdump更底层的网络观测工具,能在内核态做过滤和聚合,对性能影响更小。推荐从tcplife、tcpretrans、tcpconnect这几个BCC工具开始学。
nftables:iptables的替代品,语法更统一,性能更好。CentOS 9和Ubuntu 22.04已经默认使用nftables。迟早要迁移,建议提前学。
nft list ruleset查看当前规则,对比iptables-save的输出Wireshark深度分析:tcpdump抓包后用Wireshark做图形化分析,能看到TCP流的时序图、重传统计、RTT分布等。排查复杂的TCP性能问题时非常有用。
# ==================== tcpdump 速查 ====================
tcpdump -i eth0 -nn host 10.0.1.50 # 抓指定IP的包
tcpdump -i eth0 -nn port 80 # 抓指定端口
tcpdump -i eth0 -nn tcp and host 10.0.1.50 and port 80 # 组合过滤
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0'# 只抓SYN包
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'# 只抓RST包
tcpdump -i eth0 -nn -c 5000 -w /tmp/cap.pcap port 80 # 保存到文件
tcpdump -nn -r /tmp/cap.pcap # 读取pcap文件
tcpdump -i eth0 -nn -A -s 0 port 80 # 显示HTTP内容
tcpdump -i eth0 -nn not port 22 # 排除SSH流量
tcpdump -i eth0 -nn -tttt port 53 # 抓DNS带时间戳
# ==================== ss 速查 ====================
ss -s # 连接状态摘要
ss -tlnp # 查看所有TCP监听端口
ss -tn # 查看所有TCP连接
ss -tn state established # 只看ESTABLISHED
ss -tn state time-wait # 只看TIME-WAIT
ss -tn state close-wait # 只看CLOSE-WAIT
ss -tn 'dport = :3306'# 过滤目标端口
ss -tn 'src 10.0.1.0/24'# 过滤源网段
ss -ti dst 10.0.2.100 # 查看TCP内部信息
ss -tnp state close-wait # CLOSE-WAIT带进程信息
# ==================== iptables 速查 ====================
iptables -L -n --line-numbers # 查看filter表规则
iptables -L -n -v # 查看规则带包计数
iptables -t nat -L -n --line-numbers # 查看nat表规则
iptables-save # 导出所有规则
iptables-restore < rules.file # 导入规则
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 添加规则
iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT # 插入到第3行
iptables -D INPUT 5 # 删除第5条规则
iptables -F INPUT # 清空INPUT链
iptables -P INPUT DROP # 设置默认策略
# ==================== 辅助工具速查 ====================
ping -c 10 10.0.2.100 # 连通性测试
mtr -n -r -c 100 10.0.2.100 # 路径丢包分析
nc -zv -w 3 10.0.2.100 3306 # TCP端口测试
dig +short api.example.com # DNS查询
dig @8.8.8.8 api.example.com # 指定DNS服务器
curl -w "TCP:%{time_connect}s Total:%{time_total}s\n" -o /dev/null -s URL # HTTP耗时分析
nstat -az | grep -i retrans # TCP重传统计
conntrack -L | wc -l # conntrack表条目数
ethtool -S eth0 | grep -i drop # 网卡丢包统计
ip -s link show eth0 # 网卡流量统计
net.core.somaxconn | |||
net.ipv4.tcp_max_syn_backlog | |||
net.core.netdev_max_backlog | |||
net.ipv4.tcp_tw_reuse | |||
net.ipv4.tcp_max_tw_buckets | |||
net.ipv4.tcp_fin_timeout | |||
net.ipv4.tcp_keepalive_time | |||
net.ipv4.tcp_keepalive_intvl | |||
net.ipv4.tcp_keepalive_probes | |||
net.ipv4.tcp_syncookies | |||
net.ipv4.ip_local_port_range | |||
net.ipv4.ip_forward | |||
net.nf_conntrack_max | |||
net.netfilter.nf_conntrack_tcp_timeout_established | |||
net.netfilter.nf_conntrack_tcp_timeout_time_wait | |||
net.ipv4.tcp_rmem | |||
net.ipv4.tcp_wmem |