如果你手里的iperf3测试出现这种情况:TCP跑得飞快,带宽正常,一切看起来都很健康;结果切到UDP,客户端这边明明在往外发包,服务端却一个字节都没收到,面板上挂了个大大的0.00 bits/sec。那么恭喜,你撞上了网络排障里非常经典的一个坑——UDP路径上某个环节把报文丢了,而TCP因为协议自身的特性恰好绕过了这个坑。
我在不同环境里碰到过好几次同样的现象,根源五花八门:有防火墙规则漏配的,有MTU和分片问题的,甚至有iperf3版本不兼容导致的。这篇文章就把这条排查链路完整地捋一遍,从最基础的复现命令,到tcpdump抓包定位,再到MTU、缓冲区、反向测试这些细节,全部按实际排障顺序来讲,希望能帮你少走几圈弯路。
1. 先把现场固定住:复现命令、版本与服务端监听状态
1.1 用最小命令组合把问题"钉死"
不管症状描述得多奇怪,动手排查前一定先把测试场景固定下来。我习惯用这一套最干净的命令组合:
# 服务端 iperf3 -s -i 1 # 客户端 iperf3 -c 192.0.2.10 -u -b 100M -t 10注意服务端我特意加了-i 1,意思是每秒打印一次中间结果。很多人在服务端不加参数直接跑,结果只有最后一行Summary,中间过程全被吞掉了,问题复现时连个"到底哪一秒开始不收包"都看不出来。加了这个参数,客户端一启动,服务端就会逐秒刷新接收情况,是0还是逐渐掉到0,一眼就能看出来。
还有一个容易被忽略的点:如果服务端连Accepted connection from...这行字都没打印,那说明TCP控制面本身就没通,这跟"UDP收不到流量"是两个问题,得先去解决控制面连通性。反过来,如果服务端打印了Accept,但之后所有interval的收包数都是0,那才是典型的"控制面通、数据面挂"——也就是本文要聊的核心问题。
1.2 版本兼容性:先看版本再看别的
这个坑我踩过,所以现在排障第一步永远是先确认两端版本:
iperf3 --versioniperf3 和老的 iperf2 是完全不兼容的两个软件,端口号都不同,服务端一个监听5201一个监听5001,你要是拿iperf3客户端去连iperf2服务端,压根就连不上。就算同样是iperf3,不同小版本之间也出现过UDP测试相关的兼容性问题——控制通道的TCP握手正常,但UDP数据通道或者UDP交换报文解析异常,表现出来就是"TCP正常、UDP收不到"。
所以我的建议很粗暴:先把两端都升级到同一个较新的3.x版本再测。这一步成本最低,却经常能直接解决"看起来哪都没问题但UDP就是不通"的诡异场景。别嫌麻烦,升级五分钟搞定的问题,不值得花两小时查配置。
1.3 服务端到底有没有在等UDP
测试进行中的时候,另开一个终端上服务端看看实际的socket状态:
# 查看UDP监听socket ss -ulpn | grep 5201 # 查看TCP监听socket ss -tlnp | grep 5201正常情况下,UDP测试跑起来后,服务端应该能看到一个状态为UNCONN的UDP socket绑定在5201端口上,接收队列里的数据在增长。如果TCP的监听socket在,但UDP socket压根不存在,说明服务端进程根本没有进入UDP接收流程——这时候要怀疑是不是版本问题、启动参数问题,或者UDP端口被别的进程占住了。
端口被占的情况我也遇过,比如之前某个服务已经占用了UDP 5201,你的iperf3进程起不来或者起来了但绑定失败,客户端那边照样往外发包,但收包的一直是另一个程序。用ss -ulpn看到底是哪个进程在监听,一目了然。确认被占后,用-p参数换一个端口再测:
# 服务端 iperf3 -s -p 6201 -i 1 # 客户端 iperf3 -c 192.0.2.10 -u -p 6201 -b 100M -t 102. TCP能通不代表UDP能通:防火墙、安全组与ACL的两套逻辑
2.1 为什么有状态防火墙"偏爱"TCP
这是整个问题最核心的认知差:很多人想不通,"TCP都通了,UDP怎么可能不通?"原因在于,绝大多数防火墙、云安全组、网络ACL都是按协议分别配规则的。TCP有三次握手,有明确的连接状态,防火墙可以跟踪SYN、SYN-ACK、ACK的过程,建立一条"established"状态,后续报文匹配状态直接放行。而UDP没有握手,没有连接建立的过程,防火墙只能用超时机制模拟一个"伪连接"状态,很多默认策略根本不会为UDP自动创建状态。
更麻烦的是,不少NAT网关和负载均衡设备的端口映射规则只写了TCP协议。你检查端口转发时看到"5201已映射",以为万事大吉,实际上那一行规则往往只对TCP生效,UDP流量到了设备面前就直接被丢弃了。
2.2 三个最容易堵UDP的位置
按出现频率排序,我建议查这三个地方:
第一,主机防火墙本身。Linux上就是iptables/nftables、firewalld或ufw,Windows上就是防火墙入站规则。检查命令分别如下:
# iptables iptables -L -n -v | grep 5201 # nftables nft list ruleset | grep 5201 # ufw ufw status verbose # firewalld firewall-cmd --list-all注意看计数器的变化。跑一次UDP测试,再执行一次iptables -L -n -v,如果某个DROP规则或者默认策略的计数器增长了,基本就锁定了丢包位置。
第二,云平台的安全组和网络ACL。这是我在实际项目中遇到最多的情况。安全组里的规则是按协议逐条列出的,TCP 5201和UDP 5201是两条完全不同的规则。很多人照着网上的iperf3教程配了TCP 5201,UDP却漏了,于是出现"TCP随便跑、UDP全灭"的经典场景。
第三,中间的NAT网关、路由器、负载均衡端口映射。去设备上看端口映射规则时,务必确认协议类型那一栏是"TCP+UDP"还是"Any",而不仅仅是TCP。
2.3 如何快速验证"流量根本没到服务端"
在动手改任何规则之前,先做一个决定性测试:在服务端直接抓包。如果服务端网卡上连一个UDP报文都看不到,那基本可以断定丢包发生在主机之外的链路、安全组或者中间设备上;如果能抓到包但iperf3仍然收不到,那问题就在主机内部。这一步能帮你把排查范围砍掉一半,后面的功夫才花得值。
3. tcpdump一锤定音:先分清楚是"网络没到"还是"进程没接"
3.1 抓包要抓对方向和参数
服务端抓包我一般这么起:
# 测试开始后观察,或者干脆让它一直跑着 tcpdump -i any -nn -vv udp port 5201多网卡的机器建议别用any,直接用具体的物理网卡名,这样能确认报文到底是从哪张网卡进来的。有时候服务器有管理口和业务口,流量进了管理口,业务口的iperf3进程自然是收不到的。
客户端这边也要抓一份,用来确认客户端确实在往正确的目标地址和目标端口发包:
tcpdump -i eth0 -nn udp port 5201抓包时有个容易看错的细节:iperf3的UDP数据报文,目标端口就是控制通道的同一个端口号,比如默认的5201。源端口是客户端随机分配的临时端口。所以服务端抓包过滤udp port 5201就够了。
3.2 抓到包但iperf3显示0,问题出在哪
这是最容易让人上头的一种情况:tcpdump明明看到UDP报文进来了,但iperf3服务端就是显示0.00 bits/sec。遇到这种情况,先别急,按下面几个方向排查:
第一,看抓到的包是不是分片包。如果tcpdump输出的UDP长度字段显示的是8192这种大数值,并且后面跟着frag信息,说明这包被分片了。中间设备如果丢了后续分片,服务端虽然能看到带着UDP头部的首片,但重组一直完不成,数据永远到不了应用程序。这种情况我在下一节详细展开。
第二,看是不是端口搞错了。有时候服务端开的其实是6201,客户端命令里忘了指定或者写错了端口,但恰好TCP控制面用了默认5201连上了——不对,这种根本连不上。更常见的是客户端用了-p指定了UDP目标端口,但服务端启动时用了另一个端口,TCP控制面协商的时候没发现(实际会协商失败),表现出来也是抓包有流量但进程收不到。这个点提醒大家:抓包时同时确认源端口和目标端口跟你配置的是否一致。
第三,小心checksum offload的障眼法。很多网卡开启了硬件校验和卸载,tcpdump会显示bad checksum或者csum bad,这往往是抓包工具的误报,不代表包真的坏了。但如果网卡驱动本身有校验和卸载的bug,内核会在接收路径上直接丢弃这些报文,iperf3自然收不到。可以用ethtool检查一下:
ethtool -k eth0 | grep -E "rx-checksumming|gro|lro"3.3 完全抓不到包,又该怎么继续
如果服务端抓包一个UDP报文都看不到,那就继续往外层查:
先回到主机防火墙,看DROP计数是否在增长。如果本机防火墙没有拦截记录,再把目光放到外部的安全组和中间设备上。一个很实用的招数是把两台机器临时改到同一个二层网络里,或者直接用一根网线互联,排除掉中间设备的影响——如果直连环境下UDP正常,那问题100%在中间的交换路由链路。
客户端这边也确认一下路由出口:
ip route get 192.0.2.10确保流量没被路由到别的接口去。另外,某些环境的网络设备会丢弃所有ICMP报文,这会直接影响PMTUD机制,间接把UDP大型报文坑死,后面拿ping探测MTU时也要注意这一点。
4. 被分片坑掉的UDP:8KB默认报文与MTU问题
4.1 为什么TCP正常而UDP偏偏全军覆没
这是"TCP正常、UDP收不到"这个组合里最隐蔽的一个原因。iperf3的UDP测试默认报文长度是8KB,也就是8192字节。而标准的以太网MTU是1500字节,意味着一个UDP报文要被切成好几段。这里面有一个非常关键的协议差异:
TCP走的是PMTUD(路径MTU发现)机制,报文设置了DF位,中间设备发现报文超过自身MTU时,会丢弃并回传一个ICMP"需要分片"的通知,双方靠这个握手自动收敛报文大小。所以即使链路中间有个MTU 1500的路由器,TCP也能自适应地跑得很好。
UDP则完全不同。iperf3默认不设置DF位,发送端直接按本机MTU把8KB大包切开扔出去。这些分片包到了中间设备的防火墙、入侵检测或者ACL策略时,很容易被针对"非首片分片"的过滤规则处理掉。很多安全策略为了防分片攻击,干脆把非首片分片全部丢弃——结果就是:首片带着UDP头部到达了服务端,tcpdump看起来"有包",但后续分片全被丢掉,重组永远完不成,应用层一个字节都收不到。TCP因为有DF和PMTUD,压根不会产生这种分片场景,所以表现完全正常。
4.2 用-l验证,用ping探测
判断是不是MTU/分片问题,最直接的办法就是用-l参数把UDP报文压到安全大小内:
iperf3 -c 192.0.2.10 -u -b 100M -l 1400 -t 10如果换成1400字节的小包之后测试立刻恢复正常,那基本坐实了是MTU或分片处理的问题。接下来再用ping确认路径MTU:
# 测试标准1500 MTU路径 ping -M do -s 1472 192.0.2.10 # 测试9000 MTU路径(9000 - 28 = 8972) ping -M do -s 8972 192.0.2.10-M do表示禁止分片,-s是ICMP载荷大小,28字节是ICMP头部加IP头部的开销。如果1472能通但8972不通,说明路径上有1500的瓶颈。注意,很多家庭宽带和专用线路还会因为PPPoE这类封装把实际MTU压到1492,这时候安全包长要选1450甚至更小。
4.3 关掉GRO和校验和卸载再验证
虚拟化环境和某些网卡驱动上,UDP大包还有另一个坑:GRO(通用接收卸载)或校验和卸载功能异常。判断方法很简单,临时关掉再测一遍:
sudo ethtool -K eth0 gro off sudo ethtool -K eth0 rx-checksumming off如果关掉之后iperf3的UDP接收恢复了,那就是驱动或虚拟化网络后端的卸载实现有bug。测完确认后建议把优化项开回来,毕竟生产环境还是需要这些硬件加速的,然后从报文大小和缓冲区角度去解决真正的瓶颈,而不是长期关掉offload。
5. 参数细节里的大坑:-b、-R、-w一个都不能马虎
5.1 没写-b,UDP默认只有1Mbps
这个坑太常见了。iperf3的TCP测试不指定带宽会尽力去打满,但UDP测试如果不写-b,默认按1Mbps的速率发送。你以为在测千兆链路,实际上客户端慢吞吞地按1Mbps发包,服务端面板上跳动的数字自然小得可怜。很多人这时候就开始怀疑"服务端收不到流量",其实收是收得到,只是你没让它跑起来。
正确的做法是任何UDP测试都显式写明目标带宽:
iperf3 -c 192.0.2.10 -u -b 100M -t 10这里的100M就是100Mbps。如果想打满带宽,可以用-b 0表示不限速,让应用以最大速率往外发。还有个细节:带宽设得越高,对收发两端的内核缓冲要求越高,丢包率数据才能反映真实情况。
5.2 UDP反向测试别迷信-R
iperf3的-R参数表示反向模式,即服务端发送、客户端接收,用来测上行链路。但如果你要测的是UDP,我得提醒一句:UDP配合-R在部分3.x版本上出现过统计和报文交换方面的问题,有的版本直接表现为反向模式收不到UDP流量或者丢包率统计异常。
我自己更推荐的替代方案是:想测从A到B的上行UDP,就直接在B这台机器上执行iperf3客户端,把A当成服务端。也就是把角色对调,用最标准的正向UDP测试来覆盖你关心的那个方向。这样绕开了反向模式的历史包袱,得到的数据也更容易解释。
5.3 缓冲区、PPS与"收是收到了,只是攒不住"
还有一种容易被误判的"假收不到":服务端其实一直在收包,但速率一上来,内核的UDP接收队列瞬间被打爆,大部分报文在socket缓冲区溢出时被丢弃,iperf3最终显示的接收量接近0,丢包率接近100%。现象和"完全收不到"很相似,但本质不同。
Linux默认的UDP接收缓冲区往往只有几百KB,高速测试下根本不够用。可以这样调大:
sudo sysctl -w net.core.rmem_max=16777216 sudo sysctl -w net.core.rmem_default=16777216然后在iperf3两端同时指定socket缓冲区:
iperf3 -s -w 16M -i 1 iperf3 -c 192.0.2.10 -u -b 100M -w 16M -t 30顺便说一句,UDP测试时长建议至少30秒以上,用-t 30。测试时间太短,统计样本不足,丢包率数字的抖动会非常大,很难判断是链路问题还是测试噪声。Jitter和丢包率这两个指标,iperf3是在服务端计算的,客户端显示的接收数据来自服务端通过UDP返回的控制报文,如果反向UDP通道也有问题,客户端的统计同样会失真。
6. 两个实战案例复盘:从现象到修复的全过程
6.1 案例一:安全组只放行了TCP 5201
这个案例我印象很深。环境是某云平台上的两台Linux实例,同一个安全组,客户机和服务器跨地域通信。现象就是开篇说的:TCP测试跑到940Mbps,稳得一批;切到UDP,客户端显示发送速率100M,服务端每个interval打印的接收量都是0.00 bits/sec。
排查过程按顺序是这样走的:
- 服务端执行
ss -tlnp | grep 5201,TCP监听正常。 - 测试运行中执行
ss -ulpn | grep 5201,UDP socket在,说明iperf3进程本身已经进入UDP接收流程。 - 服务端tcpdump直接抓
udp port 5201,结果一个包都没有。 - 因为抓不到包,怀疑链路或外部安全组。检查本机防火墙,iptables计数器没变化,排除本机。
- 登录云平台控制台看安全组入站规则,发现只配了一条TCP 5201,UDP 5201完全没配。
- 添加入站UDP 5201规则,重新测试,UDP 100M速率下丢包率不到0.5%。
这个案例是最典型的"控制面全通、数据面全断":TCP控制连接走5201被安全组放行,所以服务端能收到控制指令、能打印Accepted,但UDP数据报文在半路就被安全组拦截了。tcpdump抓不到包这个事实,直接把问题定位到了主机之外的链路层,省去了大量无谓的主机内部排查。
6.2 案例二:双端MTU 9000、中间1500,8KB默认包被分片坑掉
另一个案例更隐蔽。环境是跨专线的两台服务器,两端的交换机都开了jumbo frame,接口MTU是9000。现象同样是TCP测试950Mbps一切正常,UDP测试客户端在发包,但服务端iperf3显示接收为0。不过这次服务端tcpdump能抓到包——抓到的都是零零碎碎的分片报文,UDP的头部长度字段明确写着8192。
排查过程:
- 服务端tcpdump看到分片报文的UDP长度是8192,立刻怀疑MTU问题。
- 两端执行
ip link show,确认接口MTU都是9000。 - 用ping探测路径MTU:
ping -M do -s 1472通,ping -M do -s 8972不通,说明双端虽然都是9000,但中间链路存在1500的瓶颈设备。 - 客户端改成
-l 1400重新测试,UDP立即恢复了正常,丢包率0.1%。 - 后续把两端接口MTU统一改回1500,用默认8KB报文也能正常跑通。
这个案例告诉我们一个教训:本地接口MTU不等于端到端路径MTU,中间设备的配置你管不到。TCP有PMTUD机制会自适应,看起来完全正常;UDP没有DF位保护,本地按9000 MTU分片后,中间设备再对"分片的分片"处理不力,就出现了"tcpdump有包但应用层0收包"的反直觉现象。
6.3 一张排查顺序表
最后把我惯用的排查顺序整理成表格,方便你直接照着做:
| 顺序 | 动作 | 关键命令/位置 | 判别结果 |
|---|---|---|---|
| 1 | 统一并升级iperf3版本 | iperf3 --version | 版本差异会导致UDP通道异常 |
| 2 | 复现时看服务端中间输出 | iperf3 -s -i 1 | 是否打印Accepted、interval是否递增 |
| 3 | 服务端抓包 | tcpdump -i any -nn udp port 5201 | 有包/无包决定排查方向 |
| 4 | 小包复测 | iperf3 -c ... -u -l 1400 | 恢复正常=MTU/分片问题 |
| 5 | 检查UDP放行规则 | iptables、安全组、NAT映射 | 补齐UDP 5201规则 |
| 6 | 调大缓冲区再看丢包 | -w 16M+sysctl | 大量丢包时再处理buffer |
我把这套顺序放在手边,每次UDP测试出问题都从第一步走到第六步,绝大多数情况在第三步或者第四步就能锁定根因。最后分享一个小提醒:修好之后别急着直接开大带宽,先用100M、-l 1400、-t 30跑一轮干净的测试,把丢包率记录下来当基线,再逐步把参数恢复到目标速率和默认报文长度。哪一步开始丢包,问题就在哪一步——这套增量恢复的思路,比一次性拉满带宽再抓瞎要高效得多。