ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

iperf3 UDP测速为0?从抓包到MTU的完整排查指南

iperf3 UDP测速为0?从抓包到MTU的完整排查指南

如果你手里的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 --version

iperf3 和老的 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 10

2. 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。

排查过程按顺序是这样走的:

  1. 服务端执行ss -tlnp | grep 5201,TCP监听正常。
  2. 测试运行中执行ss -ulpn | grep 5201,UDP socket在,说明iperf3进程本身已经进入UDP接收流程。
  3. 服务端tcpdump直接抓udp port 5201,结果一个包都没有。
  4. 因为抓不到包,怀疑链路或外部安全组。检查本机防火墙,iptables计数器没变化,排除本机。
  5. 登录云平台控制台看安全组入站规则,发现只配了一条TCP 5201,UDP 5201完全没配。
  6. 添加入站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。

排查过程:

  1. 服务端tcpdump看到分片报文的UDP长度是8192,立刻怀疑MTU问题。
  2. 两端执行ip link show,确认接口MTU都是9000。
  3. 用ping探测路径MTU:ping -M do -s 1472通,ping -M do -s 8972不通,说明双端虽然都是9000,但中间链路存在1500的瓶颈设备。
  4. 客户端改成-l 1400重新测试,UDP立即恢复了正常,丢包率0.1%。
  5. 后续把两端接口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跑一轮干净的测试,把丢包率记录下来当基线,再逐步把参数恢复到目标速率和默认报文长度。哪一步开始丢包,问题就在哪一步——这套增量恢复的思路,比一次性拉满带宽再抓瞎要高效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进