
抓 UDP 的包翻车点从来不在 tcpdump 这个工具本身而在你敲下的那一串过滤表达式以及你对 UDP 协议栈行为的预判。我见过太多人在机器上敲了tcpdump -i eth0 udp屏幕上哗哗刷屏然后CtrlC一按说一句抓到了但看不懂也见过有人抓了半天抓不到大包最后发现是 IP 分片把后续分片全过滤掉了。tcpdump 是命令行抓包工具里的老将配合 libpcap 的 BPF 过滤引擎能在内核态就把不关心的包扔掉这个能力是很多图形化工具替代不了的尤其在只有 SSH 通道的服务器、没有图形界面的嵌入式板子、以及要长时间后台抓包的场景里。这篇东西面向的是已经会敲几条基础命令、但一碰到 UDP 就开始抓瞎的运维、开发和测试同学。我会把 UDP 抓包里最容易踩的坑、过滤表达式的写法、离线安装 tcpdump 的完整流程、iperf3 打流验证、以及前后两包时间间隔的统计方法,从实操角度完整过一遍。你看完至少能做到两件事一是能精确写出只抓我要的那几个 UDP 包的表达式二是抓到包之后知道用什么姿势把丢包、抖动、响应超时这些数据从 pcap 里挖出来。1. 先把 UDP 和 TCP 在抓包视角下的差异摸清楚1.1 没有握手就没有天然的分析锚点TCP 抓包的时候你的眼睛是有依靠的SYN、SYN-ACK、ACK 三次握手FIN 四次挥手SEQ 和 ACK 号一路递增丢包重传能直接从序列号跳变里看出来。UDP 什么都没有。它只有 8 字节的头部源端口 2 字节、目的端口 2 字节、长度 2 字节、校验和 2 字节后面直接跟 payload。没有序列号没有确认号没有重传标记。这意味着两件事第一你没法在 tcpdump 输出里一眼判断这个包是响应还是重传第二你没法靠协议自身的字段判断有没有丢包只能靠抓两端的包做对比或者干脆用 iperf3 这类自带统计的工具。UDP 的无连接还体现在抓包的时机上。TCP 服务端没监听的时候客户端发 SYN 会收到 RST抓包看得很清楚。UDP 服务端没监听的时候内核会回一个 ICMP 端口不可达type 3 code 3但很多应用根本没处理这个 ICMP客户端就是干等到超时报一句连接超时就完事了。你在客户端这边抓包只能看到自己发出去的 UDP 包看不到任何回复到服务端那边抓发现包压根没到或者到了但没有进程监听。所以 UDP 排障的一个基本原则是两端同时抓客户端抓一份、服务端抓一份做完时间对齐再比对。还有一个更隐蔽的差异。TCP 有连接状态NAT 设备和防火墙会维护会话表UDP 是无状态的很多防火墙对 UDP 会话的超时设置短得离谱30 秒甚至更短超时之后就把映射表项删了之后回来的包直接被丢弃。你 ping 一下是通的TCP 端口也是通的但 UDP 就是收不到回复很大概率是这个原因。抓包的时候表现为发出去有记录回来的一直没有且服务端那边明明已经发出去了。1.2 tcpdump 在这个场景里的不可替代性很多人问有 Wireshark 了为什么还要用 tcpdump。答案很现实生产环境的服务器你只可能通过 SSH 上去装不了图形界面抓包文件动辄几百 MB 到几个 GB通过图形工具实时抓会把网络拖垮还有一些设备交换机、路由器、嵌入式网关上只有有限的存储和内存跑不动重量级工具。这时候 tcpdump 就是唯一选择它依赖极少运行态内存占用小写文件模式几乎不做解析非常适合先抓下来回头再分析的工作流。另一个理由是-w写文件模式。这个模式下 tcpdump 不解析协议、不做地址反解、不打印只把链路层原始字节按 pcap 格式写盘。我在千兆甚至万兆口上抓包时一定会加-w因为一旦让它往终端打印光是格式化输出就够 CPU 喝一壶的还会疯狂刷屏把 SSH 会话卡死。抓完之后把文件拷到本地用 Wireshark 或者 tshark 慢慢啃这才是正常姿势。最后说一下 tcpdump 的过滤能力。它的过滤表达式会被编译成 BPF 字节码然后在内核里执行。也就是说不匹配的包在进入 socket 缓冲区之前就被丢掉了不占用用户态 CPU也不会因为应用层处理慢而丢包。这是 tcpdump 相比抓全部再过滤的最大优势。你在-w写文件的时候过滤表达式一样生效抓下来的文件体积可以小一到两个数量级。2. 环境准备在线安装、离线部署、权限配置2.1 三种常见系统的常规安装路径在能联网的环境里安装 tcpdump 是最省事的一步。CentOS、Rocky、AlmaLinux 这类 RHEL 系发行版直接yum install -y tcpdump或者dnf install -y tcpdump它会自动把 libpcap 依赖一起装上。Ubuntu、Debian 系用apt-get update apt-get install -y tcpdump。openEuler、Anolis、Kylin 这些国内发行版也都有对应的仓库包命令大差不差。装完之后先确认版本tcpdump --version重点看两个东西一是 tcpdump 自己的版本号4.99 之后的版本默认 snaplen 变成了 262144抓大包不用再手动加-s 0了二是它链接的 libpcap 版本libpcap 1.9 之后对时间戳的支持更细Linux 上还能用--time-stamp-precisionnano拿到纳秒级时间戳这对分析 UDP 的抖动很有用。有些最小化安装的系统里连ss和netstat都没有排查的时候会很难受。我的习惯是装 tcpdump 的时候顺手把iproute、net-tools、bind-utils一起装上ss -lunp看 UDP 监听、dig验证 DNS 查询DNS 就是典型的 UDP 应用这些后面排查都用得到。至于 macOS 和 FreeBSD系统自带 tcpdump不用装但版本可能偏老而且它们的网卡命名是 en0、em0 这种接口参数要换一下。Windows 上原生没有 tcpdump只能靠 Wireshark 自带的 dumpcap或者用 WSL 里的 Linux 版本抓不过 WSL 抓的是虚拟网卡看不到宿主机的真实流量这一点要注意。2.2 离线环境里把 libpcap 一起搬过去离线安装 tcpdump 是很多内网同学的真实痛点。核心原则只有一条tcpdump 依赖 libpcap两个包必须一起拿而且要在和目标机器相同或更低版本的系统上下载。RHEL 系的做法是找一台同版本、能联网的机器用yumdownloader --resolve tcpdump把 tcpdump 和它所有依赖一次性下载到本地目录。如果机器上没有 yumdownloader先装yum-utils。下载完你会看到几个 rpmlibpcap-1.5.3-12.el7.x86_64.rpm、tcpdump-4.9.2-4.el7.x86_64.rpm之类的。拷到目标机器上rpm -ivh *.rpm一把装完。如果提示依赖冲突先rpm -qa | grep libpcap看看系统里是不是已经有老版本有的话用rpm -Uvh升级而不是-ivh安装。这里有个坑必须提醒不要在 CentOS 8 的机器上下载包然后拿去 CentOS 7 上装。rpm 的依赖里包含 glibc 版本高版本系统编译出来的包在低版本系统上会因为 glibc 符号找不到而直接失败报 Failed dependencies: libc.so.6(GLIBC_2.28)(64bit) is needed。反过来倒通常没问题。所以下载源机的系统版本要等于或低于目标机。Debian、Ubuntu 系用apt-get install --download-only -y tcpdump下载的 deb 包在/var/cache/apt/archives/下面把它们拷过去dpkg -i就行。但 dpkg 的依赖报错比 rpm 更啰嗦经常要手动补libpcap0.8、libssl、libc6这一串。更稳妥的办法是在能联网的机器上用apt-get -d配合--print-uris把 URL 全部打出来然后用脚本批量下载或者干脆在目标机同版本的系统上做一个本地离线仓库目录用dpkg-scanpackages生成 Packages 索引再配/etc/apt/sources.list.d/指向这个目录这样apt-get install能自动解依赖。这个方案稍重但一次性做好后面所有离线机器都能用。最原始的办法是源码编译。下载 libpcap 和 tcpdump 的源码包先./configure make make install装 libpcap再编 tcpdump。编译 libpcap 需要 flex、bison、gcc、make有些发行版还要 libnl 或者蓝泽相关的开发库。这条路适合架构特殊的机器比如 ARM 或者国产 CPU 平台找不到现成二进制包的场景。编译参数上我一般会加--prefix/usr让装出来的路径和系统包一致避免后面脚本里写死绝对路径出问题。注意离线环境下不要用rpm -ivh --nodeps强行绕过依赖。libpcap 缺失或版本不匹配时tcpdump 能装上但运行会直接报 error while loading shared libraries: libpcap.so.1而且这种错误在装的时候完全看不出来。2.3 让非 root 用户也能抓包的两种做法tcpdump 默认要 root 权限因为它需要打开混杂模式、创建原始套接字。但让所有人用 root 登录抓包显然不现实也不安全。有两种更规范的做法。第一种是给二进制文件加能力位。Linux 从 2.6.24 开始支持 capabilities抓包只需要cap_net_raw和cap_net_admin两个能力。命令是sudo setcap cap_net_raw,cap_net_admineip /usr/sbin/tcpdump加完之后用普通用户直接跑 tcpdump 就行。这个方案的缺点是包升级会覆盖文件、丢掉能力位所以每次 yum update 之后要重新加一次。可以写个 udev 规则或者 rpm 的 post 脚本来固化但大多数团队觉得没必要升级完手动执行一遍就行。第二种是用 tcpdump 自带的-Z参数。它的原理是 tcpdump 以 root 启动、打开抓包句柄之后主动把进程的权限降到你指定的用户。写法是sudo tcpdump -i eth0 -Z nobody -w /tmp/cap.pcap。注意-Z只降 tcpdump 进程本身的权限不解决谁来启动它的问题所以你还是要通过 sudo 来跑。实际部署时通常配合 sudoers 里的一条白名单规则只允许特定用户免密执行/usr/sbin/tcpdump这是我认为最平衡的方案。还有一种情况是容器环境。容器里抓包要加--cap-addNET_RAW --cap-addNET_ADMIN而且默认抓的是容器自己的网络命名空间看不到宿主机或其他容器的流量。想抓宿主机网卡得用--nethost或者直接进宿主机的命名空间跑。Kubernetes 里通常是用一个 DaemonSet 起 debug 容器挂hostNetwork: true这个后面有机会再展开。3. 过滤表达式UDP 抓包的真正核心3.1 从四条基础表达式开始tcpdump 的过滤表达式语法继承自 pcap-filter学起来不难但要写得准。跟 UDP 相关的最基础四条先记住# 抓所有 UDP 流量 tcpdump -i eth0 -nn udp # 抓指定端口的 UDP源或目的任意一侧匹配 tcpdump -i eth0 -nn udp port 5000 # 只抓目的端口是 5000 的 tcpdump -i eth0 -nn udp dst port 5000 # 抓一段端口范围 tcpdump -i eth0 -nn udp portrange 5000-5100-nn这两个 n 分别表示不做地址反解和不做端口名反解。DNS 解析是同步阻塞的在流量大的时候会严重拖慢甚至丢包所以生产环境抓包我建议永远带上-nn。只有在明确要确认某个 IP 的域名归属时才临时去掉它。port和portrange的区别很直观但有个细节udp port 5000是源端口或目的端口任一为 5000 都匹配这在你想看双向流量时很方便但如果你只想看客户端发出去的那部分就要用udp dst port 5000。很多人一开始搞混抓出来的包数量比预期多一倍就是因为没区分方向。还有个组合是协议加主机udp and host 192.168.1.50。这里要特别注意运算符优先级and的优先级比or高所以udp port 5000 or 5001 and host 1.2.3.4会被解释成udp port 5000 or (5001 and host 1.2.3.4)第二个5001前面没有udp port前缀这个表达式其实是有问题的。凡是混用 and/or一律加括号这是血泪教训。3.2 组合过滤、方向控制与网段匹配真实场景里过滤条件往往不止一层。举几个我经常用的例子。抓某台客户端和某台服务端之间的 UDP别的全不要tcpdump -i eth0 -nn -s 0 -w /tmp/cli.pcap \ udp and host 192.168.1.50 and host 10.0.0.20注意这里两个host是 AND 关系表示两端都必须是这两个地址之一等价于只抓这两个 IP 之间的包。如果你想抓的是这台机器与整个 10.0.0.0/24 网段之间的流量写成host 192.168.1.50 and net 10.0.0.0/24。看流进还是流出用-Q参数需要 tcpdump 4.99 以上、Linux 3.15 以上内核# 只看从本机发出去的 tcpdump -i eth0 -nn -Q out udp port 5000 # 只看进本机的 tcpdump -i eth0 -nn -Q in udp port 5000这个-Q比用src host判断本机地址更省事因为你不用去查本机现在有几个 IP。但要注意-Q依赖内核的PACKET_QDISC_BYPASS相关能力在有些虚拟化环境或者老内核上不生效表现是加了这个参数一个包都抓不到。遇到这种情况就退回src/dst写法。接口选择上-i any在 Linux 上会抓所有网卡看起来很方便但有两个代价。第一用它抓到的包链路层类型是 LINUX_SLLLinux cooked capture不是标准的以太网帧有些模块的ether host过滤会失效第二高流量下-i any的抓包性能明显低于指定单网卡。所以我一般只在不确定流量走哪张网卡的初筛阶段用-i any定位到具体网卡后立刻换成-i eth0。3.3 按 payload 字节做精细过滤UDP 排障最有价值的一个技巧是按 payload 内容过滤。因为 BPF 支持任意偏移量的字节比较。UDP 头部固定 8 字节所以 payload 的第一个字节下标就是 8。基本格式是udp[offset:size]配合比较运算符# 抓 payload 前两个字节是 0x0102 的 UDP 包 tcpdump -i eth0 -nn -X udp[8:2] 0x0102 # 抓 payload 第 3、4 字节大于 0x0100 的包 tcpdump -i eth0 -nn -X udp[10:2] 0x0100这个用法在调试自定义二进制协议时简直是神器。假设你的设备上报协议的前两字节是固定的魔数0x55 0xAA那么直接写tcpdump -i eth0 -nn -X udp[8:2] 0x55aa一秒钟就能在海量 UDP 流量里把你关心的那批包挑出来比抓全量再在 Wireshark 里用udp.payload contains过滤高效得多因为过滤是在内核里做的。还有个不太为人知的语法是pcap-filter里的len关键字它是 IP 层的总长度不是 payload 长度。ip[2:2]读出来的也是 IP 总长度。如果你想过滤UDP payload 超过 1000 字节的包得算一下IP 总长 20IP 头 8UDP 头 payload所以条件写成ip[2:2] 1028。这个技巧在排查大包分片的时候特别有用配合下面要说的分片过滤基本能覆盖大 UDP 包的所有问题。4. 三类实战场景的完整抓包过程4.1 两台电脑用网络调试助手互发 UDP这是最典型的入门场景。两台机器上都跑 UDP 调试工具A 发 B 收或者双向收发。配置大概是 A 绑定本地 9000 端口往 B 的 10000 端口发B 绑定 10000往 A 的 9000 回。先确认两件事B 的机器上 UDP 10000 端口有没有进程在监听用ss -lunp看sudo ss -lunp | grep 10000如果没有输出说明服务端根本没起A 发再多也是白搭。这时候在 A 侧抓包能看到包正常发出在 B 侧抓什么也抓不到因为内核在协议栈入口发现没进程监听回了 ICMP 端口不可达就丢了。防火墙也要先看。Linux 上用iptables -L INPUT -n -v或者nft list ruleset关注有没有针对 UDP 的 DROP 规则以及规则的包计数器有没有在涨。如果计数器在涨说明包确实到了被防火墙丢了。两端同时抓包的命令分别是# A 机器 sudo tcpdump -i eth0 -nn -tttt -s 0 -w /tmp/a.pcap udp and port 10000 # B 机器 sudo tcpdump -i eth0 -nn -tttt -s 0 -w /tmp/b.pcap udp and port 9000注意 A 侧过滤目的端口 10000B 侧过滤目的端口 9000这样过滤出来的是单向的发出方向方便做发出去的包和收到的包的一一对应。如果你想一次抓双向把port 10000 or port 9000在一起写就够分析的时候靠源端口区分方向。抓完把两个文件拉到同一台机器上用 Wireshark 打开对比。重点看三个指标A 发出的包数、B 收到的包数、两者之差。差值就是这段链路上丢掉的包。如果差值集中在某几个时间点往往是瞬时拥塞或者链路抖动如果 A 发了 1000 个 B 只收到 3 个那要么是 MTU 分片被中间设备丢了要么是防火墙做了限速。提示USB 转网口、无线网卡这类设备的抓包结果会有偏差。无线侧抓到的包数量往往比有线侧少因为无线驱动在抓包驱动之前就可能把包丢了。这种场景下要抓交换机镜像口或者干脆抓对端。4.2 iperf3 UDP 打流定位丢包与抖动网络调试助手只能验证通不通要量化质量得用 iperf3。它在 UDP 模式下会自己算丢包率和抖动同时它发的包也方便用 tcpdump 交叉验证两边对照着看基本没跑。服务端先起来iperf3 -s -p 5201客户端发起 UDP 打流iperf3 -c 10.0.0.20 -p 5201 -u -b 100M -t 30 -l 1400 --get-server-output参数逐个说一下。-u表示 UDP 模式不加就是 TCP。-b 100M是目标带宽UDP 模式下必须指定因为 UDP 没有拥塞控制不指定的话 iperf3 只能不知道怎么发。-t 30打 30 秒。-l 1400是每个 UDP 包的 payload 长度这个值很关键后面细说。--get-server-output会把服务端的统计一并回显给客户端省得来回切换终端。-l为什么选 1400 而不是默认的 1460算一下标准以太网 MTU 是 1500减去 IP 头 20 字节、UDP 头 8 字节剩下 1472 是 payload 上限。但链路上如果有 PPPoE、GRE 隧道、VXLAN 封装可用 MTU 会更小1400 是留出余量的安全值。如果你非要用 1472 或者更大包就会被 IP 层分片分片包一旦有任何一个分片丢失整个 UDP 报文就废了而 iperf3 不会告诉你是分片丢的只会报丢包。这也是为什么我在打流验证的时候永远优先选小包。打流的同时两端各抓一份包sudo tcpdump -i eth0 -nn -s 0 -w /tmp/iperf_s.pcap udp port 5201 sudo tcpdump -i eth0 -nn -s 0 -w /tmp/iperf_c.pcap udp port 5201iperf3 跑完会输出一段总结重点看这几行[ ID] Interval Transfer Bitrate Total Datagrams [ 5] 0.00-30.00 sec 357 MBytes 99.8 Mbits/sec 267878 [ 5] 0.00-30.00 sec 139 MBytes 38.9 Mbits/sec 104390 [ 5] 0.00-30.00 sec 0.0000 ms Jitter [ 5] 0.00-30.00 sec 1024/104390 (0.98%) Lost这里Total Datagrams是客户端发出的包数中间那行是服务端实际收到的Lost是丢包统计Jitter是抖动。如果客户端发 267878 个、服务端只收 104390 个丢包率接近 61%这就是非常严重的问题了先去查链路是不是跑不满、交换机是不是有端口错误计数。抓下来的两个 pcap 可以做交叉验证用 tshark 统计各自的包数看看和 iperf3 报的数字能不能对得上。对不上的话说明 tcpdump 自己抓包时丢包了这时候要么调大-B缓冲区要么把过滤条件写得更精确一点减少要处理的数据量。4.3 抓取并计算 UDP 前后两包的时间间隔这是 UDP 分析里最实用也最容易做错的一件事。很多人第一反应是 Wireshark 里看Time列相减但包一多根本数不清。正确姿势是用时间戳格式和字段导出。tcpdump 抓包时的时间戳参数有这么几个-t完全不打印时间戳-tt打印 Unix 时间戳秒.微秒-ttt打印相对上一个包的时间间隔-tttt打印带年月日的可读时间-ttttt打印相对第一个包的时间间隔。想直接看前后两包的间隔用-ttt最省事sudo tcpdump -i eth0 -nn -ttt udp port 5000输出长这样00:00:00.000000 IP 192.168.1.50.53422 10.0.0.20.5000: UDP, length 32 00:00:00.010213 IP 192.168.1.50.53422 10.0.0.20.5000: UDP, length 32 00:00:00.009876 IP 192.168.1.50.53422 10.0.0.20.5000: UDP, length 32 00:00:05.432101 IP 192.168.1.50.53422 10.0.0.20.5000: UDP, length 32前三个包的间隔都是 10ms 左右第四个突然变成 5.4 秒这就是典型的应用层卡顿或者被调度延迟了。这种一眼能看出来的还好包多了就得靠工具。批量计算的做法是用 tshark 导出字段再用 awk 做差分tshark -r /tmp/cap.pcap -Y udp ip.src192.168.1.50 \ -T fields -e frame.number -e frame.time_relative -e ip.src -e udp.srcport -e udp.length \ /tmp/udp.txt awk NR1{printf %s 间隔 %.6f 秒\n, $1, $2-prev} {prev$2} /tmp/udp.txt | head -20frame.time_relative是相对第一个包的秒数精度到微秒。两两相减就得到每个包与它前一个包的间隔。如果你只想要显示出来的包之间的间隔把字段换成frame.time_delta_displayed它已经算好了相对前一个可见包的差值直接输出就行不需要自己减。Wireshark 里的对应操作是在列设置里把frame.time_delta_displayed加成自定义列或者在某个重要的包上右键Set/Unset Time Reference快捷键CtrlT之后再往下看这一列的数值就全部相对那个参考包了。排查某个请求发出后多久才收到响应这种问题时这个功能比手算快得多。要注意一个精度问题。tcpdump 默认的时间戳精度在 Linux 上是微秒抓包量大时同一个微秒内可能有好几个包时间间隔算出来就是 0。如果你的场景需要更细的精度加--time-stamp-precisionnano重新抓一次让 tcpdump 用纳秒时间戳同时在 Wireshark 里也要把时间显示精度调到纳秒否则它默认还是按微秒显示看起来全是 0。5. 抓下来的包怎么读、怎么挖5.1 tcpdump 自己读包-r 搭配 -ttt抓到 pcap 之后不一定要马上上 Wireshark。tcpdump 自己就能读包-r参数指定文件过滤表达式照常写。这个能力在服务器上做快速定位非常好用。# 看文件里 UDP 5000 端口的前 50 个包带相对时间 tcpdump -r /tmp/cap.pcap -nn -ttt -c 50 udp port 5000 # 看 payload 内容ASCII 形式 tcpdump -r /tmp/cap.pcap -nn -A udp port 5000 # 看十六进制加 ASCII tcpdump -r /tmp/cap.pcap -nn -X udp port 5000-A和-X的区别值得说一下。-A只打印 payload 的 ASCII 内容如果你的协议是文本的比如某些日志上报、简单的 JSON看起来非常直观。-X打印十六进制和对应的 ASCII 两栏二进制协议用它。-XX会连链路层头也打出来一般没必要。还有一个组合是-r加-w做二次过滤。比如抓的时候条件太宽抓了 2GB 的文件现在只想要里面某个 IP 的包tcpdump -r big.pcap -w small.pcap -nn host 192.168.1.50 and udp这个操作读一点写一点内存占用很低比用 Wireshark 打开再另存的效率高得多尤其在服务器上处理大文件时能省不少事。5.2 tshark 批量导出字段做统计tshark 是 Wireshark 的命令行版本做字段提取和批量统计比 tcpdump 强太多。我在做 UDP 质量分析时最常用的几个命令。按流统计 UDP 会话看哪些流在跑、各多少包tshark -r cap.pcap -q -z conv,udp输出是一张表列出每个 UDP 会话的源地址端口、目的地址端口、包数、字节数、起止时间、持续时间。这个表能让你一眼看出哪个流占了大部分流量、哪个流的持续时间异常长。统计每秒的 UDP 包数和字节数看流量是否有毛刺tshark -r cap.pcap -q -z io,stat,1,COUNT(frame)frame,SUM(udp.length)udp.length那个1是统计间隔单位秒。改成0.1就是 100 毫秒一个桶用来观察短时间内的突发。UDP 打流测试里这个图能直接反映出是不是有周期性丢包。导出时间间隔做分布分析前面已经说过用frame.time_delta_displayed。补充一个统计用法把间隔导出后用 awk 算分位数tshark -r cap.pcap -Y udp -T fields -e frame.time_delta_displayed d.txt sort -n d.txt | awk {a[NR]$1} END{print P50:,a[int(NR*0.5)], P95:,a[int(NR*0.95)], P99:,a[int(NR*0.99)], MAX:,a[NR]}P99 和 MAX 这两个值在评估 UDP 实时性的时候特别关键。平均值好看不代表没问题如果你的实时音频流 P99 间隔是 500ms那每隔一段时间就会卡一下。5.3 Wireshark 里几个省时间的操作图形化工具本身没什么好教的我列几个 UDP 分析里真正省时间的操作。第一个是Statistics Conversations UDP标签页。这里能看到所有 UDP 会话的列表点列头排序可以快速找出包数最多的流、字节数最大的流、持续时间最长的流。选中某一行点Apply as Filter主界面自动只显示这个流比手打过滤条件快。第二个是过滤表达式收藏。Wireshark 的显示过滤和 tcpdump 的捕获过滤语法不同udp.port 5000而不是udp port 5000很容易记混。把常用表达式存成按钮一键切换。第三个是Statistics Flow Graph。对 UDP 来说它就是按时间顺序把两个方向的包画成箭头图看得清哪一头发了、哪一头没回。虽然 UDP 没有 TCP 那些标志位但看着时间轴上单边持续输出、另一边完全静默问题一眼就出来了。第四个是Follow UDP Stream。对文本协议可以直接看到两端的对话内容比一个个包点开看 payload 快太多了。注意 UDP 是无连接的Wireshark 是靠地址和端口的五元组来拼流的同一对地址端口之间不同时间发的包会被算作同一条流时间跨度大时要留意。6. 常见问题速查与长期抓包策略6.1 抓不到包时的排查顺序抓不到包是最高频的问题我按经验排一个排查顺序从最可能的原因往下走。第一看接口对不对。ip -br addr看机器的网卡列表和地址ip route get 10.0.0.20看去目标地址走哪张网卡。很多人抓的是 eth1流量其实走 eth0自然什么都没有。临时可以用-i any确认流量到底在哪个接口上。第二看过滤表达式是不是写太死了。把过滤条件全部去掉只留-i eth0看看有没有任何包。如果裸抓能看到目标包说明是表达式的问题逐项加条件缩小范围。常见错误包括端口写错、源和目的方向搞反、少了括号导致优先级错乱。第三考虑混杂模式的限制。默认 tcpdump 会开混杂模式能抓到经过网卡但不发给本机的帧。但虚拟化环境里虚拟交换机的安全组策略经常会禁止混杂模式此时只能抓到自己收发的包。用-p显式关掉混杂模式试试如果行为一致说明就是被限制了。第四看流量是不是走了环回。本机进程之间的 UDP 通信走的是lo网卡你在 eth0 上抓当然抓不到。加-i lo或者用-i any。第五考虑是不是被硬件卸载到网卡上了。有些智能网卡会做 checksum offload抓到的包校验和字段是错的显示为 incorrect这是正常的不是丢包。但如果是 GRO/LRO 这类聚合卸载抓到的一个大包其实是多个小包聚合的看到的包数会比实际少。可以用ethtool -K eth0 gro off lro off临时关掉再抓对比。6.2 现象与原因对照表把 UDP 抓包里最常见的几个现象整理成表方便对着查。现象可能原因验证方法客户端抓到发包服务端完全抓不到路由不通、中间设备丢弃、防火墙拦截两端traceroute对比路径iptables -L -n -v看 DROP 计数服务端抓到了客户端收不到回复NAT 会话超时、回程路由不对称、服务端发出的包被防火墙拦两端同时抓比对服务端发出和客户端收到的包数小包正常大包1400 字节全丢链路 MTU 小于 1500IP 分片被丢弃用ping -M do -s 1472探测实际 MTU抓包看分片抓包时只看到第一个分片后续分片消失BPF 过滤udp port对非首分片不匹配改用host x and (udp port p or ip[6:2] 0x1fff ! 0)tcpdump 报告 N packets dropped by kernel抓包缓冲区不够或磁盘写入慢加大-B用-w写本地高速盘缩小过滤范围抓到的包 checksum 显示错误网卡 checksum offload正常现象对比对端抓到的同一包或关掉 offload 再抓同一微秒内多个包时间间隔全是 0时间戳精度只到微秒加--time-stamp-precisionnano重抓发包速率正常但接收速率只有一半中间设备限速、UDP 被 QoS 降级换端口重试检查交换机 QoS 策略这个表里我要重点提一下分片那条。BPF 的udp port过滤器生成的字节码里有个隐含判断如果这个 IP 包是后续分片fragment offset 不为 0它不包含 UDP 头所以直接不匹配。结果就是一个大 UDP 报文被分成 3 个分片你只抓到第 1 个后两个完全看不到。如果第 2 个分片在链路中丢了你在抓包文件里只会看到第一个分片孤零零地在那儿看起来包发出去了实际上对端永远拼不完整。这个坑不知道的话能查一整天。正确的抓法是这样tcpdump -i eth0 -nn -s 0 \ host 192.168.1.50 and (udp port 5000 or ip[6:2] 0x1fff ! 0)ip[6:2]读的是 IP 头第 7、8 字节也就是 flags 加 fragment offset 那 16 位 0x1fff取出低 13 位的偏移量不为 0 就说明是后续分片。加上这个条件所有分片才能一网打尽。6.3 缓冲区、轮转与磁盘保护长时间抓包要考虑两件事抓包本身别丢包以及别把磁盘写爆。缓冲区用-B控制单位是 KB。默认值在 Linux 上是 2MB在高流量链路上明显不够。我一般按峰值带宽 × 抓包时长 × 0.5来估比如万兆口抓 10 秒理论上要存 12.5GB缓冲区给 64MB 到 256MB 比较合理tcpdump -i eth0 -nn -s 0 -B 262144 -w /data/cap.pcap udp-B 262144就是 256MB。加完之后注意看 tcpdump 结束时的 dropped 统计如果还是丢就把过滤写得更精确减少进入缓冲区的数据量这比一味加内存更有效。磁盘保护靠轮转参数。-C按文件大小轮转单位 MB-G按时间轮转单位秒-W限制文件数量超过就从头覆盖。举个例子按每 100MB 一个文件、最多保留 20 个总共占用不超过 2GBtcpdump -i eth0 -nn -s 0 -w /data/cap_%Y%m%d_%H%M%S.pcap \ -G 3600 -W 20 -C 100 udp port 5000注意-G和-C同时用的时候谁先触发就按谁轮转。文件名里的%Y%m%d_%H%M%S是时间占位符会把抓包开始时间填进去方便事后按时间找文件。如果不需要按时间命名直接用-w /data/cap.pcap加-W也可以tcpdump 会自动加序号后缀。还有个容易被忽略的点是-U参数。默认情况下 tcpdump 是带缓冲写文件的进程被kill -9的时候缓冲区里的数据会丢。加上-U让它每收一个包就写一次盘代价是 IO 压力大但在需要随时 kill 也不丢数据的场景下必须加比如抓偶发的、不知道什么时候出现的异常包。最后提醒一句关于磁盘的事。我遇到过有人抓包忘了加轮转一个周末下来把/var分区写满机器上的服务全挂了。抓包的输出目录不要放在系统盘尽量放到单独挂载的数据盘并且提前用df -h确认剩余空间再配合监控告警。这件事没有技术含量但出事的时候是真的疼。补充一个嵌入式场景。在设备端跑 UDP 测试比如基于事件组做收发同步的那种时设备上往往没有 tcpdump能用的手段是交换机端口镜像把设备口的流量镜像到一台 PC 上抓。镜像口抓到的包和真实收发有时会有细微差异比如镜像丢包、时间戳由镜像设备打点这些在分析时都要留意。