
1. 抓包这件事先搞清楚你要捞的是什么很多人第一次用 tcpdump命令敲下去屏幕上刷得飞快抓了几万个包然后呢然后就没有然后了。文件存了一堆打开 Wireshark 一看绝大部分是无用的广播、ARP、心跳噪音真正想看的 UDP 报文淹没在里面。问题不在工具在于动手之前没想清楚我要捞的是什么。我这次的场景很典型一个基于 UDP 的内网采集服务客户端定时往服务端某个端口发数据服务端偶尔收不到但日志里又看不出所以然。TCP 有连接状态、有重传机制、有ss -s能看到队列积压UDP 什么都没有——发出去就像泼出去的水收没收到、有没有丢、丢在哪一跳全靠抓包说话。这就是利用 tcpdump 抓取 UDP 数据这件事最核心的价值它是链路层到应用层之间唯一一个不带立场的观察者。先说清楚 tcpdump 是不是只管 TCP。名字确实有误导性很多新手以为它抓不了 UDP。实际上 tcpdump 工作在网络接口层它看的是链路层的帧TCP、UDP、ICMP、ARP 全都能抓tcp只是它名字的历史包袱。你要抓 UDP核心就两条一是接口选对二是过滤器写对。接口选错抓一百年也抓不到过滤器不写或者写错抓回来的数据量能让你怀疑人生。后面的内容我会从最基础的抓包逻辑讲到 UDP 特有的坑包括混杂模式、抓包位置、输出格式、时间戳精度、以及怎么配合iperf3打流和网络调试助手验证。适合谁看做过一点 Linux 运维、知道ping和nc怎么用但每次抓包都要现查 man 手册的人。如果你现在手上正好有一个UDP 数据丢了但不知道丢在哪的问题那这篇基本可以照着抄。全文涉及的命令我都实际跑过参数含义会解释到位。2. tcpdump 抓 UDP 的命令骨架与过滤器逻辑2.1 最小可用命令长什么样先给一个能直接跑的最短命令再拆解每个部分的必要性sudo tcpdump -i eth0 -nn udp port 9999 -c 50这一行里有五个要素缺一个都会让你多绕路。-i eth0指定网卡接口不指定的话 tcpdump 会默认选一个通常是第一个非 lo 接口但服务器上多网卡是常态选错接口就是白抓。-nn表示不做主机名和服务名解析直接显示 IP 和端口号——这个参数强烈建议常开原因很简单DNS 反查会让输出严重延迟抓高流量场景时你会看到 tcpdump 卡住不动其实是它在等你那个根本不存在的 DNS 服务器返回。udp port 9999是伯克利包过滤表达式BPFudp只匹配 UDP 协议port 9999匹配源端口或目的端口为 9999。注意这里是或如果你只想抓目的端口要写成udp dst port 9999只抓源端口写udp src port 9999。-c 50是抓够 50 个包就退出防止自己忘了 CtrlC 把磁盘写满。这个习惯我建议所有人养成尤其是在生产机器上。一个真实教训有次我在一台 32 核的采集机上抓包忘了加-c也忘了加-w直接写文件结果终端疯狂刷屏把 SSH 会话拖死了。因为 tcpdump 默认把每个包的信息输出到标准输出这个 IO 在高包量下本身就成了瓶颈。所以要么限量要么落盘。2.2 BPF 过滤器把噪音挡在外面过滤器写得好不好直接决定你后面分析的工作量。UDP 常见过滤需求可以分成几类我用一张表来对照这些都是我实际用过的写法需求过滤器写法说明抓某个端口的所有 UDPudp port 9999源或目的任一是 9999只抓发往本机某端口udp dst port 9999排查服务端是否收到只抓从某端口发出udp src port 9999排查客户端是否发出限定某个主机udp and host 192.168.1.50收窄来源两个主机之间的 UDPudp and host 192.168.1.50 and host 192.168.1.60只关心这对通信排除某主机udp and not host 192.168.1.1去掉网关噪音按网段过滤udp and net 10.20.0.0/16大范围收窄按包大小udp and greater 200过滤掉小心跳包这里有个新手最容易踩的点port后面跟的如果是服务名比如domaintcpdump 会去查/etc/services查不到就直接报错退出。所以我前面强调-nn端口一律写数字别依赖系统服务名解析不同发行版的/etc/services内容可能不一样你的命令在 A 机器上能跑在 B 机器上就报unknown port。另一个点值得单独说udp和port之间是隐式的and关系。有人会写成udp port 9999 or 8888这是错的因为8888单独出现时会被解析成端口 8888 上的任意协议语法上虽然能过但语义和你想的不一样。正确写法是udp port 9999 or udp port 8888或者更简洁的udp portrange 9999-9999这类写法不过portrange对单个端口没意义多端口场景才用。2.3 全量抓包落盘为什么一定要-w直接看标准输出适合快速确认有没有包但真正排查问题一定要落盘sudo tcpdump -i eth0 -nn -s 0 -w /tmp/udp_9999.pcap udp port 9999-s 0是关键它表示抓取完整包长度。tcpdump 早期版本默认只抓 68 字节snaplen超过的部分被截断对于 UDP 这种载荷可能很大的协议截断意味着你后面的应用层分析全部失效——你能看到有包但看不到内容等于没抓。现在新版本默认是 262144 字节比绝大多数场景都够但显式写上-s 0是最保险的尤其是你面对的是未知环境版本不确定。-w写出来的文件是 pcap 格式可以直接拖到 Wireshark 里打开。这里有个很多人不知道的细节tcpdump 落盘几乎不解析内容只做过滤和写盘所以它的丢包率远低于终端输出模式。如果你在一个高流量接口上抓包终端模式丢包是必然的-w模式才能尽可能完整。我做过对比在千兆满速接口上终端输出模式 tcpdump 自身报告的 dropped 比例能到百分之十几落盘模式通常低于千分之一。落盘时还有一个坑磁盘空间。UDP 高速打流比如后面要讲的 iperf3在几秒内就能产生几百 MB 的 pcap 文件。所以要么控制抓包时长要么用-C和-W做文件轮转sudo tcpdump -i eth0 -nn -s 0 -w /tmp/udp_%Y%m%d_%H%M%S.pcap -G 60 -Z root udp port 9999-G 60表示每 60 秒切一个文件配合-w里的时间格式占位符就能自动生成带时间戳的滚动文件。-Z root是切文件后降到指定用户权限不过一般直接用 root 抓、抓完手动改权限也行。3. 抓包位置选错所有分析都是徒劳3.1 在客户端抓还是服务端抓这是 UDP 排查里最容易搞错、也最影响结论的一步。UDP 无连接你只有两个可靠的观测点发送方网卡出方向和接收方网卡入方向。抓错位置会让你得出完全相反的结论。判断方法很朴素如果客户端抓到了发包服务端没抓到说明包丢在网络上如果客户端都没抓到那是应用没发出去或者发到了错误的目标如果服务端抓到了那问题在应用层读取。这个三段论几乎可以覆盖所有 UDP 丢包的场景。但难点在于客户端网卡出方向和服务端网卡入方向要用不同的过滤器观察。客户端抓出方向sudo tcpdump -i eth0 -nn -s 0 -w /tmp/client_out.pcap udp src port 9999服务端抓入方向sudo tcpdump -i eth0 -nn -s 0 -w /tmp/server_in.pcap udp dst port 9999两边同时抓然后用时间对一下。这里会出现一个非常有价值的现象如果客户端发出 100 个包、服务端只收到 80 个那丢的 20 个就在网络路径上接着你就该去查中间的网络设备、网卡 ring buffer、防火墙的 UDP session 表项是否满了。如果两边都是 100 个问题百分之百在服务端应用层——比如 socket 接收缓冲区满了导致内核直接丢弃或者应用处理太慢导致队列溢出。3.2 lo 接口上的 UDP 很有意思本地回环通信是很多人忽略的场景。如果你的 UDP 服务端和客户端在同一台机器上走的是lo接口那么抓包必须指定-i lo。默认 tcpdump 不会抓 lo因为它通常不在默认接口列表里被优先选中。在 lo 上抓 UDP 有个特殊之处你会两次看到同一个包。一次是客户端发出源端口 A目的端口 B一次是服务端回给客户端——不对UDP 没有连接不会有自动回包。所以 lo 上你看到的两次是同一个 UDP 报文在回环路径上的两个方向拷贝。这时过滤器要更精确比如只抓udp dst port 9999否则你会被同一份数据出现两遍搞懵以为服务端在重复处理。更极端的是容器环境。如果 UDP 服务跑在 Docker 里你在宿主机eth0上抓能看到包进了宿主机但看不到容器内部要抓容器内的得进容器的 network namespace或者用nsenter切进去抓。这个坑我踩过当年排查一个容器里的 UDP 服务收不到数据在宿主机抓了半天包明明到了docker0网桥但容器里就是没有最后发现是 iptables 的 NAT 规则把目标端口改了。抓包位置差一层结论就全错。3.3 混杂模式到底是什么要不要开tcpdump 默认就把网卡置为混杂模式promiscuous除非你显式加-p。混杂模式的字面意思是接收所有经过网卡的数据帧不管目的 MAC 是不是我。但这里要澄清一个广泛存在的误解在现在的交换式网络中混杂模式基本抓不到别人的流量。因为交换机只会把帧转发到目的端口对应的物理口你的网卡根本收不到不属于你的帧混杂模式也就无从发挥。只有在集线器hub、或者交换机做了端口镜像SPAN、或者你抓的是共享介质的场景下混杂模式才有意义。所以如果你打算开混杂模式抓全网络的 UDP在交换网里是行不通的必须让网络管理员配置镜像口。需要提醒的是个别版本/环境下混杂模式可能触发一些网卡驱动问题或者在某些云主机的虚拟网卡上表现异常。如果你只想抓本机相关的流量加-p关掉混杂模式反而更稳减少不必要的中断。4. 读懂 tcpdump 的 UDP 输出识别真正的异常4.1 一行输出里的每个字段先看一个典型输出15:22:31.482913 IP 192.168.1.50.54321 192.168.1.60.9999: UDP, length 32逐字段拆15:22:31.482913是抓包时间精确到微秒取决于内核时间戳精度。IP表示网络层协议是 IPv4。192.168.1.50.54321是源 IP 加源端口注意 IP 和端口之间是点号分隔。表示方向。192.168.1.60.9999是目的 IP 加目的端口。最后UDP, length 32表示这是 UDP 报文载荷长度 32 字节。这里有个诊断价值极高的细节length 是 UDP 载荷长度不包含 IP 头和 UDP 头。所以如果一个你预期发 1000 字节的业务包抓出来 length 只有 8那基本可以判断是应用写了很少的数据或者被某处截断了。反过来如果你看到 length 是 0 的 UDP 包那不是错误是合法的零长度 UDP 报文有些心跳协议就靠这个维持 NAT 映射。时间戳的精度值得单独说。默认的-t各种变体控制的是显示格式但真正的精度取决于内核。你可以用--time-stamp-precisionmicro或nano来指定不过 nano 精度需要内核和 libpcap 支持。为什么要关心精度因为 UDP 排查经常要做两个包之间的时间间隔分析比如判断抖动jitter。热词里提到的wireshark 如何筛选出 udp 前后两包的时间间隔就是同一类需求——间隔用微秒还是纳秒结论可能差一个数量级。4.2 verbose 模式能多看到什么加上-v、-vv、-vvv后输出会包含更多信息。对于 UDP-v会显示 IP 头的 TTL、总长度、校验和-vv会显示 UDP 的校验和状态-vvv则尝试解码应用层对 UDP 一般没什么效果除非是 DNS 这类已知协议。sudo tcpdump -i eth0 -nn -v udp port 9999 -c 5输出会类似15:22:31.482913 IP (tos 0x0, ttl 64, id 12345, offset 0, flags [DF], proto UDP (17), length 60) 192.168.1.50.54321 192.168.1.60.9999: UDP, length 32这里的ttl 64特别有用。同一个包在客户端抓 TTL 是 64到服务端抓如果还是 64说明中间没有路由跳数变化同一二层网络如果掉了几个说明经过了路由。id 12345是 IP 分片标识如果 UDP 载荷超过 MTU 被分片你会看到多个 IP 包的 id 相同offset 递增这对于排查 UDP 大包被分片丢弃的问题至关重要——很多防火墙和 NAT 设备对分片 UDP 处理有缺陷。length 60是 IP 包总长度等于 20 字节 IP 头 8 字节 UDP 头 32 字节载荷 60可以自己验算。养成看 verbose 输出的习惯你会发现很多莫名其妙的丢包都能从 TTL 和分片信息里找到线索。4.3 校验和错误与丢弃的原因UDP 校验和是可选的IPv4 下但一旦计算了接收端校验失败就会静默丢弃。tcpdump 的-vv会显示校验和是否正确但这里有个大坑如果你在发送方本机抓包UDP 校验和经常显示 incorrect因为网卡有 checksum offload校验和是在网卡硬件里算的内核协议栈里此时那个字段还是占位值。这不是真的错误不要被误导。只有在接收方抓或者关闭了 offload 的场景下校验和显示才可信。要确认是不是 offload 的锅可以用ethtool -k eth0 | grep checksum看看 offload 开关。这个坑让无数人白排查了半天看到一个 incorrect 就以为数据损坏其实数据好好的。另一个常见异常是 ICMP 端口不可达。如果你抓 UDP 的时候顺手把 ICMP 也抓上过滤器写成不带udp限定的port 9999会发现有时候回过来一个 ICMP type 3 code 3意思是目的端口不可达。这是 UDP 服务没起来、或者被防火墙 REJECT 的典型信号。但注意不是所有防火墙都回 ICMPDROP 策略下你就是什么都看不到包凭空消失。5. 用 iperf3 和网络调试助手构造可复现的 UDP 流量5.1 iperf3 打 UDP 流参数怎么设排查 UDP 问题如果没有稳定的复现流量抓包就是盲人摸象。iperf3 是最可控的 UDP 流量发生器。服务端iperf3 -s -p 9999客户端发 UDPiperf3 -c 192.168.1.60 -p 9999 -u -b 100M -l 1400 -t 30参数逐个说。-u指定 UDP不加就是 TCP。-b 100M是目标带宽这是 iperf3 UDP 模式必须显式设置的参数不设默认只有 1Mbps很多人第一次跑 UDP 觉得怎么才这么点速度就是忘了-b。-l 1400是每个 UDP 报文的载荷大小为什么选 1400因为以太网 MTU 是 1500减去 20 字节 IP 头、8 字节 UDP 头再加上 iperf3 自己的头部1400 是接近但不超过 MTU 的常用值可以避免分片。如果你想测分片场景就把-l设成 2000 以上这时 tcpdump 里就能看到 IP 分片。-t 30是持续 30 秒。跑起来之后iperf3 服务端会报告接收到的带宽、丢包率、抖动。这个报告的丢包率和你在 tcpdump 里数出来的丢包数可以互相印证——如果 iperf3 说丢了 5%但你两端 tcpdump 抓到的包数只差 1%那说明丢包可能发生在收取 socket 缓冲区的应用层而不是网络层。带宽选择有个经验值先用低带宽确认链路通再逐步加高。直接上满速打流你会同时面对丢包和 tcpdump 自身丢包两个问题分不清谁是谁。我一般从 10M 起步确认无丢包后翻倍找到第一个开始丢包的临界点那个点才是真正反映链路能力的数字。5.2 网络调试助手这类工具怎么配合抓包热词里提到两台电脑 udp 通信使用网络调试助手这类图形化工具对新手很友好填上对端 IP 和端口点发送就能看到对方有没有收到。它的问题在于大部分版本没有时间戳、没有序号你无法判断包是延迟了还是丢了。所以我的做法是用网络调试助手发固定内容的包同时用 tcpdump 在旁边抓。具体操作调试助手里发的报文内容设成一个可识别的字符串比如PING-0001、PING-0002这样带序号。然后 tcpdump 抓包落盘拉到 Wireshark 里按内容过滤数一下序号连续性就能精确知道丢了哪几个包。图形工具负责发送tcpdump 负责记录两者配合才是完整的调试链路。发送频率也有讲究。手动点击发送间隔不固定而且可能点得太快导致应用层来不及处理。如果要测高频场景用工具里的自动发送功能设置固定间隔比如 100ms这样 tcpdump 里的时间戳就应该是均匀分布的如果出现明显的间隔跳变就能判断是抖动还是丢包。5.3 用 namp 扫描 UDP 端口来验证开放状态热词里有namp 扫描 udp 端口指令这里应该是 nmap 的拼写。nmap 扫 UDP 端口sudo nmap -sU -p 9999 192.168.1.60-sU表示 UDP 扫描。但你要知道 UDP 扫描的原理和局限nmap 发一个 UDP 包过去如果收到 ICMP 端口不可达就判定端口关闭如果没收到任何回应判定为 open|filtered开放或被过滤无法区分如果应用回了数据才是 open。所以 UDP 扫描又慢又不准很多防火墙的不响应策略会让它永远返回 open|filtered。这恰恰能配合 tcpdump 发挥作用。你在被扫的目标机上同时抓包就能看到 nmap 发过来的探测包长什么样、是否有 ICMP 回应。这比单纯看 nmap 的结论可靠得多。我常用这招验证防火墙规则nmap 说端口 filtered但 tcpdump 显示探测包根本没到说明丢在上游如果探测包到了但没回 ICMP说明本机 DROP 了。6. 那些没人告诉你但一定会踩的坑6.1 tcpdump 自己也会丢包这是最反直觉的一点你以为抓到的就是全部其实 tcpdump 自己会丢包。当你停止抓包时tcpdump 会打印类似这样一行统计100 packets captured 100 packets received by filter 5 packets dropped by kernelpackets captured是通过过滤条件的包数received by filter是内核 BPF 过滤器看到的包数dropped by kernel是因为 socket 缓冲区满导致内核直接丢弃的包数。只要 dropped by kernel 不为零你的抓包结果就是不完整的。排查丢包问题时一个不完整的抓包结果可能让你得出错误结论。解决办法有几个。一是加大 tcpdump 的缓冲区-B 4096或更大单位是 KiB。二是收窄过滤器减少进入内核过滤器的包量。三是用-w而不是终端输出。四是在极端高流量下用更专业的工具比如 PF_RING 版本的 tcpdump。我一般在开始正式抓包前先跑一个短时测试看看 dropped 是否为零确认环境没问题再开始长时间抓。6.2 UDP 载荷大了会分片分片了又容易丢前面提到-l 1400避免分片。一旦 UDP 载荷超过路径 MTUIP 层就会分片。分片的问题是只要任何一个分片丢失整个 UDP 报文就报废而 UDP 没有重传应用层只会觉得这个包没来。更麻烦的是很多防火墙和负载均衡设备对分片包处理不好第一条分片带了 UDP 头还能识别端口后续分片没有端口信息可能被直接丢弃。在 tcpdump 里识别分片看 verbose 输出里的offset和flags或者直接看是否出现了(frag ...)标记。如果发现业务 UDP 有分片第一选择是减小应用到 MTU 以下避免分片如果确实需要大包就要确保整条路径的设备都能正确转发分片。这个坑在跨公网的 UDP 场景里极其常见。6.3 时间同步没做好时间戳分析就是玄学两端同时抓包做对比前提是两台机器的时间是同步的。如果客户端和服务端时钟差了几百毫秒甚至几秒你按时间戳去对齐包得出的延迟完全是错的。NTP 精度对抓包分析足够了但要注意容器里的时钟可能被虚拟化层影响。我做多机抓包对比前一定先在两台机器上跑date %s.%N看时间差差得多了先同步时间再抓。这个前置步骤花不了一分钟能省掉后面几小时的困惑。6.4 过滤器语法错抓出来是空的最后说一个高频问题命令跑起来什么都不输出也没有报错。最大的可能是过滤器语法有问题或者过滤条件太严。排查方法先把过滤器去掉只留udp看看能不能抓到任何 UDP 包。如果能抓到说明接口和权限没问题问题在过滤条件如果连udp都抓不到检查接口对不对用ip link或tcpdump -D列出接口。tcpdump -D会列出所有可用接口这个命令建议记牢。有时候接口名不是eth0而是ens33、enp0s3或者云主机上是ens3你按eth0去抓当然抓不到。另外抓包必须 root 权限或者有 CAP_NET_RAW 能力普通用户跑会报权限错误但有些发行版会配置setcap让普通用户也能抓——这是另一个排查方向。7. 从抓到看懂一个完整的 UDP 丢包排查链路把前面的东西串起来我给一个我自己实际排查过的完整流程你可以照着套。场景客户端每秒发一个 512 字节的 UDP 包到服务端 9999 端口服务端日志显示每分钟大约丢 2 到 3 个包网络是普通千兆内网。第一步两端同时抓包客户端抓udp src port 9999服务端抓udp dst port 9999都落盘跑五分钟。第二步用 Wireshark 打开两个文件按时间排序统计包数和序号我让应用在载荷里带了自增序号。第三步对比结果。假设客户端发出 300 个服务端收到 296 个丢了 4 个。再定位丢失序号发现丢的包都是连续的一小段而不是随机散布。这个模式很有价值——连续丢失通常对应瞬时的缓冲区溢出或网络设备队列拥塞随机丢失才更像是链路误码。第四步看丢包时刻的上下文。检查丢包前后的包间隔如果同一时刻客户端发送频率突然变高说明应用侧有突发那么服务端的 socket 接收缓冲区可能在这瞬间被打满。用sysctl net.core.rmem_default和rmem_max看缓冲区大小如果应用没有调用setsockopt主动扩大缓冲区默认值可能只有几十 KB抗突发能力很差。第五步验证和修复。把服务端的SO_RCVBUF调大或者用sysctl -w net.core.rmem_max16777216提高上限后应用重设再跑同样的测试。如果丢包消失结论闭环。整个过程的关键在于每一步都有可观测的数据支撑而不是靠猜。这个流程里tcpdump 承担的是提供客观事实的角色。它不会告诉你答案但你只要抓的位置对、过滤器对、两个抓包文件对得上答案就摆在那里。UDP 调试的全部难点几乎都集中在怎么拿到一份可信的、完整的、时间对齐的抓包数据上剩下的分析反而是最简单的部分。我个人经验里最容易被忽略的是抓包完整性校验——每次抓完都看一眼 dropped 是否为 0比什么都重要。一个自己都在丢包的抓包工具给出的任何结论都不可信。