排查网络问题排查到后半夜的人大概都有过这种体验:日志里看不出所以然,服务端说没收到请求,客户端说超时了,中间隔着一堆设备和一层层转发,谁也说不清那一瞬间链路上到底跑了什么。这时候能救命的只有一个动作——在正确的位置抓一段包,然后用一条准确的过滤表达式把可疑的那几十个包捞出来。Wireshark 抓包过滤命令看着像是一堆记忆题,实际上是一套有严格分工的表达式体系:捕获阶段用 BPF 粗筛,显示阶段用协议字段精筛,两套语法各管一段,混着用就是各种“我明明写了却过滤不出来”。
这篇内容适合三类人看:刚接手线上排障、刚开始学抓包的新人;能抓到包但每次都要翻半天才找到目标流量的中级选手;以及想把排查流程沉淀成脚本、让同事也能一键复现的老手。下面这些表达式和组合基本都是我在真实排障场景里反复用过的,不是从手册上抄下来的全集,而是按“什么时候该用哪条、为什么这么写、写错了会怎样”这个顺序组织的。
1. 捕获过滤器和显示过滤器,用错地方等于白抓
很多人第一次被过滤表达式坑,不是因为语法写错了,而是因为写对了语法、放错了位置。Wireshark 里名字里带“filter”的地方至少有三个:捕获过滤器、显示过滤器、还有着色规则里的过滤表达式。这三者的执行时机、语法体系、作用范围完全不同,用错一个就够你折腾半小时。
1.1 执行时机的差别决定了它们能写什么
捕获过滤器在数据包进入 Wireshark 之前就生效了,它工作在抓包引擎(libpcap/npcap)那一层,用的是 BPF 语法。它只能看到链路层、网络层、传输层的头部字段——换句话说,它能理解tcp、udp、port 443、host 10.0.0.5,但完全不知道什么是 HTTP 请求方法、什么是 DNS 查询名。你把http.request.method == "GET"填进捕获过滤器,Wireshark 会直接给你语法错误,因为 BPF 里根本没有这个字段。
显示过滤器是数据包已经落盘或者在内存缓冲里之后,在分析界面上做的筛选。它用的是 Wireshark 自己那套基于协议字段名的表达式体系,能深入到应用层的每一个字段,包括http.request.uri、tls.handshake.extensions_server_name、dns.qry.name这种。代价是它不减少抓到的数据量,只是让你看不见——磁盘照样被写满,CPU 照样在解码。
| 维度 | 捕获过滤器 | 显示过滤器 |
|---|---|---|
| 语法体系 | BPF(libpcap 语法) | Wireshark 显示过滤语法 |
| 执行时机 | 抓包前 | 抓包后或抓包中 |
| 可识别层级 | 链路层到传输层为主 | 全部协议层,含应用层字段 |
| 出错表现 | 提示语法错误,无法启动抓包 | 输入框变红,不生效 |
| 对性能的影响 | 显著降低抓包量和丢包率 | 不降低抓包量,只影响显示 |
| 典型写法 | tcp port 443 and host 10.0.0.5 | http.response.code >= 500 |
这个表建议直接记住。判断一条表达式该往哪填,只需要问一句:这里面有没有出现应用层的字段名?有就一定是显示过滤器;全是 IP、端口、协议名,那两边都能写,但优先写进捕获过滤器,因为能省资源。
1.2 为什么捕获阶段必须“宁粗勿细”
有个很常见的误区:觉得捕获过滤器写得越精确越好,抓到的包越少越干净。这个想法在低流量环境下没毛病,在高流量链路上就是灾难的反面——你会发现写得太细,反而漏了关键证据。
举个具体的例子。某业务系统之间偶发连接超时,你判断是 TCP 握手阶段出了问题,于是捕获过滤器写tcp port 8443 and host 10.20.30.40。抓了十分钟,一个超时的包都没有。为什么?因为超时的原因是 DNS 解析卡住了,客户端压根没发出 SYN 包,链路上一开始跑的是 UDP 53 端口的查询。你的过滤器把 DNS 流量全部挡在门外,等于把最关键的证据提前扔掉了。
所以捕获过滤器的正确策略是:按“可疑范围”而不是“确定目标”来写。确定是这台机器的问题,就先只限主机;怀疑是某个端口的服务,就把这个端口的双向流量都放进来,再加一条“不相关协议排除”。比如host 10.20.30.40 and (port 53 or port 8443 or icmp),这样 DNS、业务端口、ICMP 都在视野里,量也不算大。
提示:如果实在拿不准范围,第一轮抓包干脆不加捕获过滤器,用环形缓冲限制文件大小和数量,先抓后筛。宁可多存几个文件,也别在第一轮就把可能性掐死。
1.3 一个真实的反面案例
早些年我参与排查一个接口偶发丢响应的问题,同事信誓旦旦说“肯定是服务端没返回”,理由是他在客户端侧抓包,过滤表达式写的是tcp.srcport == 443,结果只看到请求包,没看到响应的数据包。
这条表达式的问题在于:响应的源端口是 443,但请求的那个包里,源端口是客户端的随机高端口。他这条过滤器的语义是“只看从 443 端口发出的包”,那么客户端的请求包本身就被排除了,而响应包因为源端口是 443 又被保留下来——看起来就像“只有响应没有请求”,结论完全反过来。
改成tcp.port == 443之后,双向流量都进来了,一眼看到服务端确实回了响应,但响应之后客户端回了一个 RST,说明是客户端侧的应用层提前把连接关了。一个srcport和port的区别,把排查方向带偏了大半天。
2. 捕获过滤器:BPF 那点语法其实够用了
BPF 语法看起来原始,但它的表达力覆盖了日常排障的绝大部分需求。真正需要记的只有三类词:类型限定符、方向限定符、协议限定符,加上逻辑运算符和括号,组合起来就够了。别被手册上那几十页吓到,常用的其实不到二十种组合。
2.1 三类限定符拼出九成表达式
类型限定符决定你匹配的是什么:host(主机)、net(网段)、port(端口)、portrange(端口范围)。方向限定符决定方向:src、dst,不写就是双向。协议限定符限定协议:tcp、udp、icmp、arp、ip、ip6。
这三类按[协议] [方向] [类型] [值]的顺序拼起来,就能覆盖绝大多数需求:
# 单主机全部流量 host 10.20.30.40 # 指定网段 net 10.20.30.0/24 # 某主机的 443 端口,双向 host 10.20.30.40 and tcp port 443 # 只要进入本机 8080 的流量 dst port 8080 and tcp # 排除掉运维通道和本地回环,减少噪音 not port 22 and not port 3389 and not host 127.0.0.1 # 端口段,常用于服务端一组实例 portrange 8000-8100 # 排除整个网段 not net 10.0.0.0/8括号的优先级要留意。BPF 里and的优先级高于or,写host 10.20.30.40 and port 80 or port 443的实际语义是(host 10.20.30.40 and port 80) or port 443,后半段的 443 是全量的,跟你想要的可能差很远。养成加括号的习惯,host 10.20.30.40 and (port 80 or port 443)才是常见意图。
2.2 用vlan和pppoe处理带封装的环境
在虚拟化或者经过某些接入设备的链路里,抓到的包可能带 VLAN 标签。这时候host 10.20.30.40有可能匹配不上,因为 BPF 默认按“第一个 IP 头”去偏移,VLAN 标签插进来之后偏移量变了。
处理办法是在表达式里显式声明封装层级:
# 抓 VLAN 100 里的指定主机 vlan 100 and host 10.20.30.40 # 双层 VLAN vlan and vlan and host 10.20.30.40 # PPPoE 会话里的流量 pppoes and host 10.20.30.40跑虚拟化环境的时候这个坑踩得特别多。现象是显示过滤器能筛出来包,但同一条件写到捕获过滤器就抓不到,多数情况下就是链路层多了东西,偏移对不上。判断方法很简单:先在无过滤的情况下抓几个包,看 Packet Details 里 EtherType 是不是 0x8100,是的话就加vlan。
2.3 抓本机回环和“抓了个寂寞”
本地起服务、本地调用,流量走的是 loopback 接口。在 Linux 上就是lo,在 Windows 上通常没有独立的回环接口,Wireshark 会显示成Npcap Loopback Adapter或者Adapter for loopback traffic capture。选了物理网卡去抓回环流量,抓到的一定是空的。
另外一个更容易被忽略的点:抓包位置决定了你能看到什么。在本机抓,只能看到本机网卡进出的包;在交换机镜像口抓,能看到镜像策略覆盖的所有流量;在服务端抓,看到的是经过负载均衡之后的那一段。同一个请求在三处抓到的内容可能完全不同,因为中间设备可能改写了源地址、加了代理头、或者直接做了 TLS 卸载。
所以动手之前先问自己三个问题:我要看的是哪一段链路?这段链路上有哪些设备会改写报文?我抓的位置能看到改写前还是改写后?这三个问题想清楚了,捕获过滤器写起来才有意义。
3. 显示过滤器:从字段名到组合表达式的进阶路
显示过滤器是日常用得最多的一块,也是表达式最容易写错的一块。它的核心逻辑很简单:拿一个协议字段,跟一个值做比较,多个条件用逻辑运算符连起来。难点在于字段名要记、比较语义要理解、边界情况要避开。
3.1 字段名不用背,但得会查
Wireshark 提供了很顺手的字段名查询方式:在包详情面板里展开某一层,点中你想过滤的那个字段,状态栏左下角会直接显示对应的过滤字段名。想直接拿来用,右键该字段选“作为过滤器应用 → 选中”,表达式自动填进过滤框。
常用的字段名值得形成肌肉记忆,下面这张表是我自己整理的高频字段,按用途分组:
| 用途 | 字段名 | 说明 |
|---|---|---|
| IP 地址(双向) | ip.addr | 含源和目的 |
| IP 源地址 | ip.src | 只匹配源 |
| IP 目的地址 | ip.dst | 只匹配目的 |
| TCP 端口(双向) | tcp.port | 含源和目的端口 |
| TCP 源端口 | tcp.srcport | 只匹配源端口 |
| TCP 负载长度 | tcp.len | 大于 0 表示带数据 |
| TCP 标志位 | tcp.flags.syn、tcp.flags.fin | 值为 1 或 0 |
| 帧长度 | frame.len | 抓到的整帧字节数 |
| 帧时间 | frame.time、frame.time_delta | 绝对时间和相邻帧间隔 |
| HTTP 请求方法 | http.request.method | GET、POST 等 |
| HTTP 响应码 | http.response.code | 数值比较 |
| HTTP 主机头 | http.host | 字符串匹配 |
| DNS 查询名 | dns.qry.name | 字符串匹配 |
| TLS 服务器名 | tls.handshake.extensions_server_name | SNI 字段 |
| TCP 异常分析 | tcp.analysis.retransmission等 | 需开启分析选项 |
字段名里带不带点、带不带下划线是有讲究的。ip.addr和ip.addr ==中间必须有空格,写成ip.addr==10.0.0.1现代版本也能识别,但老版本可能出问题,建议统一加空格。
3.2!=是个陷阱,!(==)才是你想表达的意思
这是显示过滤器里最经典的一个坑,也是我见过最多人栽跟头的地方。
ip.addr != 10.20.30.40的语义是什么?直觉上你以为它表示“排除掉跟这个 IP 有关的流量”。但实际上,一个 IP 包同时有源地址和目的地址两个ip.addr值。表达式ip.addr != 10.20.30.40只要其中一个地址不等于这个值就成立——那么一个“从 10.20.30.40 发出、发往 10.20.30.41”的包,它的目的地址不等于 10.20.30.40,条件成立,这个包照样显示出来。
正确的排除写法是取反:
# 错误:依然会显示跟该 IP 相关的包 ip.addr != 10.20.30.40 # 正确:完全排除该 IP !(ip.addr == 10.20.30.40) # 同理,端口也一样 !(tcp.port == 3389)同样的道理适用于http.host、dns.qry.name这类可能在一帧里出现多次的字段。一条过滤表达式如果作用在多值字段上,!=的行为永远先想清楚再写,拿不准就一律用取反包裹。
3.3contains、matches和切片操作,各有各的适用面
精确匹配用==,模糊包含用contains,正则匹配用matches,位置取值用切片[]。这四种方式的性能和适用场景差别不小。
# 精确匹配,性能最好 http.host == "api.example.com" # 包含匹配,子串查找,不区分大小写 http.host contains "example" # 正则匹配,能力最强,但开销明显更大 http.request.uri matches "^/v2/(users|orders)/[0-9]+" # 取字段的前 N 个字节做比较,常用于二进制协议分析 tcp.payload[0:2] == 16:03 # 取帧中某一段字节,按十六进制比较 frame[54:2] == 50:4fmatches用的是类似 PCRE 的语法,写复杂正则的时候注意要对特殊字符转义。它的性能开销比contains大一个量级,在几十万包的会话里用一个复杂正则,界面会明显卡住。日常排障优先用==和contains,正则留给确实需要模糊结构匹配的场景。
切片[]的语法是字段[起始偏移:长度]。上面frame[54:2]的意思是取整个帧从第 54 字节开始的两个字节,跟十六进制50:4f比较。这个写法在抓私有二进制协议的时候特别有用——协议没有解码器,你只能按偏移去认特征值。
3.4 时间、长度这些元数据字段常常比协议字段更好用
很多时候你不知道问题出在哪个协议上,但你知道“这个请求花了很久才回”。这种情况下按时间过滤比按协议过滤更直接。
# 相邻两个包间隔超过 0.5 秒,卡顿的嫌疑点 frame.time_delta > 0.5 # 从请求发出到响应到达的整体耗时,配合会话过滤看更清楚 frame.time_delta_displayed > 1 # 绝对时间范围,注意时间格式跟本地时区设置有关 frame.time >= "2024-06-01 10:00:00" and frame.time <= "2024-06-01 10:05:00" # 大包,排查 MTU 和分片问题 frame.len > 1400 # TCP 负载大于 0,把纯 ACK 全部过滤掉 tcp.len > 0 # 只保留带数据的帧,看响应体大小分布 http.content_length > 100000tcp.len > 0这条表达式值得单独说。它把纯粹的 ACK、SYN、FIN 全部挡掉,只留下真正携带数据的帧。在排查“响应体很大导致超时”这类问题时,先用这条把数据流精简出来,再用Follow TCP Stream看完整交互,效率高很多。
4. 按故障场景组织的过滤表达式速查
零散的表达式记不住很正常,按场景组织就好记了。下面这几组是我排障时最常直接拿来用的,基本覆盖了日常八成的需求。
4.1 HTTP 与 TLS:从请求特征到证书排查
明文 HTTP 的排查相对简单,字段都在那儿:
# 只要 GET 请求 http.request.method == "GET" # 排除掉静态资源,只看接口调用 http.request and !(http.request.uri contains ".css") and !(http.request.uri contains ".js") # 4xx 和 5xx 响应 http.response.code >= 400 # 特定主机头,虚拟主机环境下非常必要 http.host contains "api" and http.response.code >= 500 # 请求体很大的 POST http.request.method == "POST" and http.content_length > 10000 # 带 Cookie 的请求 http.cookieHTTPS 的排查思路不一样,因为内容加密了,能看到的基本只有握手过程。这时候关注的重点是 SNI、证书、握手失败原因:
# 只看 TLS 握手的第一条 Client Hello tls.handshake.type == 1 # 按 SNI 定位具体域名 tls.handshake.extensions_server_name contains "example" # 握手失败的告警 tls.alert_message # 证书相关字段,比如签发者和有效期 x509sat.printableString contains "Example"如果确实需要看 HTTPS 的明文内容,标准做法是在客户端侧设置环境变量SSLKEYLOGFILE指向一个文件,然后在 Wireshark 的 TLS 协议首选项里把(Pre)-Master-Secret log filename指向同一个文件。这样浏览器或者支持的客户端在握手时会写出会话密钥,Wireshark 就能解密。注意这个操作只能在你自己控制的客户端上做,对别人的流量、对生产环境里不受你管理的机器,都不适用。
注意:抓包分析涉及的流量可能包含账号、令牌、个人数据。在自己有权限的网络里抓、抓完及时清理、不要把 pcap 文件随手发到公共渠道,这三条是底线。
4.2 DNS 异常:先分清没问出去还是没答回来
DNS 问题的排查逻辑很清晰,就是看“问出去没有”和“答回来没有”。这两个方向用捕获过滤器分开抓,一对比就知道卡在哪一段。
# 所有 DNS 流量 dns # 特定域名的查询 dns.qry.name contains "example" # 只看查询请求 dns.flags.response == 0 # 只看响应 dns.flags.response == 1 # 解析失败的响应码,3 表示 NXDOMAIN dns.flags.rcode == 3 # 查询超时无响应的情况,看有没有重复查询 dns.qry.name contains "example" and dns.flags.response == 0如果发现同一个查询名在短时间内出现多次且没有对应响应,基本可以判断是链路丢包或者上游 DNS 不可达。如果响应很快但 rcode 是 3,那就是域名配置本身的问题,跟网络无关。这两种情况的处理路径完全不同,所以第一步一定是把方向分清楚。
4.3 TCP 层异常:重传、乱序、零窗口,各有各的表达式
TCP 的异常分析依赖 Wireshark 内置的分析字段,前提是首选项里“分析 TCP 序列号”是开启状态(默认开)。这些字段不需要你手动算,Wireshark 会标出来。
| 异常类型 | 过滤表达式 | 一般指向 |
|---|---|---|
| 重传 | tcp.analysis.retransmission | 链路丢包或对端未确认 |
| 快速重传 | tcp.analysis.fast_retransmission | 收到重复 ACK 后触发 |
| 乱序 | tcp.analysis.out_of_order | 多路径转发或链路抖动 |
| 零窗口 | tcp.analysis.zero_window | 接收端应用层处理不过来 |
| 窗口更新 | tcp.analysis.window_update | 接收缓冲区释放 |
| 重复 ACK | tcp.analysis.duplicate_ack | 触发快速重传的前兆 |
| 连接重置 | tcp.flags.reset == 1 | 对端拒绝或中间设备拦截 |
| 握手无响应 | tcp.flags.syn == 1 and tcp.flags.ack == 0 | 目标端口未开放或防火墙拦截 |
零窗口是特别值得留意的信号。看到tcp.analysis.zero_window频繁出现,说明接收方的缓冲区被填满了,应用层读得太慢。这时候再怎么优化网络都没用,得去看接收端应用的消费速度。这个判断我在好几个“网络慢”的工单里用上过,最后查出来都是应用侧某个同步阻塞调用拖住了读取线程。
重传的比例也值得算一下。把重传包的数量除以总包数,超过百分之二就说明链路质量有问题了。计算方式是先过滤tcp.analysis.retransmission看状态栏的显示包数,再清空过滤器看总包数,两个数字一除就行。别嫌麻烦,这个比例比任何单一指标都更能说明链路健康状况。
4.4 其他常见协议:ARP、DHCP、ICMP 与 QUIC
# ARP 冲突检测,看有没有同一个 IP 被两个 MAC 应答 arp.duplicate-address-detected # 只看 ARP 请求 arp.opcode == 1 # DHCP 交互全流程 dhcp # DHCP 分配失败的 NAK dhcp.type == 6 # ICMP 不可达,排查链路 MTU 问题常用 icmp.type == 3 # ICMP 超时 icmp.type == 11 # QUIC 流量,现在越来越多的服务走这个了 quic # 按 SNI 过滤 QUIC(字段名跟 TLS 不同) quic.tls.handshake.extensions_server_name contains "example"arp.duplicate-address-detected这个字段很多人不知道,它能直接告诉你网络上存在 IP 地址冲突,不用自己去人工比对。遇到“间歇性连不上某个服务”这种玄学问题,先跑一下这条表达式,能省掉大量猜测。
5. 把常用过滤固化成随手可用的工具链
每次排障都手敲一遍表达式,效率低而且容易出错。Wireshark 提供了几种把过滤表达式沉淀下来的机制,配合命令行工具能形成一套完整的流程。
5.1 过滤器按钮和快捷方式
界面过滤框右边有个加号,可以把当前表达式存成一个按钮。我自己会存这么几个:tcp.analysis.flags(所有 TCP 异常)、http.response.code >= 400、!(arp or icmp or dns)(去掉噪音)、tls.handshake.type == 1(只看 Client Hello)、tcp.len > 0(只看数据帧)。
按钮的好处是点一下就切换,不用切窗口去改表达式。更省事的做法是用Ctrl + Shift + F打开“管理显示过滤器”对话框,把常用表达式一次性录入并命名。命名要有语义,比如叫“服务端5xx”比叫“过滤器1”有用得多,三个月后回来你还知道它是干嘛的。
另外,着色规则值得单独配一套。把重传配成红色、零窗口配成橙色、RST 配成紫色,扫一眼就能看出哪几行有问题。配置入口在视图菜单的着色规则里,规则本身也是过滤表达式,逻辑跟显示过滤器一致,会写过滤就会写着色。
5.2 命令行侧的过滤:tshark 和 dumpcap 的分工
图形界面适合分析,不适合长时间抓包。真正的生产环境抓包基本都用命令行工具,因为它们资源占用低、能跑在没图形界面的服务器上、能配合脚本自动化。
dumpcap只负责抓,不做解码,资源占用最小:
# 按捕获过滤器抓,环形缓冲,单文件 200MB,最多保留 20 个 dumpcap -i eth0 -f "host 10.20.30.40 and (port 443 or port 53)" \ -b filesize:204800 -b files:20 -w /tmp/cap.pcapng # 按时间切分,每 60 秒一个文件 dumpcap -i eth0 -f "tcp port 8080" -b duration:60 -w /tmp/cap.pcapng # 列出可用接口,确认网卡名字 dumpcap -Dtshark负责解码和过滤,可以同时用捕获过滤器和显示过滤器:
# -f 是捕获过滤器,-Y 是显示过滤器,两者的语法不能混 tshark -i eth0 -f "tcp port 443" -Y "tls.handshake.type == 1" -c 100 # 读取已有文件并筛选,输出指定字段,适合做统计分析 tshark -r cap.pcapng -Y "http.response.code >= 500" \ -T fields -e ip.src -e http.host -e http.request.uri # 统计各响应码的数量分布 tshark -r cap.pcapng -Y "http.response" \ -T fields -e http.response.code | sort | uniq -c | sort -rn # 按会话统计流量,找异常大的流 tshark -r cap.pcapng -q -z conv,tcp-T fields -e这个组合是自动化分析的核心。把想要的字段导出来,交给sort、uniq、awk处理,比在界面上肉眼扫快得多。比如上面统计响应码那条命令,几秒钟就能告诉你哪个接口的错误率最高。
另外editcap用来做文件裁剪和格式转换,大文件打不开的时候特别管用:
# 按时间范围裁出一个子集 editcap -A "2024-06-01 10:00:00" -B "2024-06-01 10:05:00" in.pcapng out.pcapng # 按包序号切分,每 100000 个包一个文件 editcap -c 100000 big.pcapng part.pcapng # 只保留指定协议的包,减小文件体积 editcap -F pcapng in.pcapng out.pcapng5.3 抓包丢包这件事,得从缓冲区下手
高流量链路上最容易遇到的问题不是表达式写错,是抓包工具自己丢包了。表现是 Wireshark 界面显示“已丢弃 X 个包”,你以为没流量,实际上是没抓到。
丢包的根本原因是内核到用户态的缓冲区不够。dumpcap 有两个相关参数:-B设置抓包缓冲区大小(单位 MB),-n控制不解析域名和端口名以降低开销。
# 加大缓冲区并关闭名字解析,显著降低丢包 dumpcap -i eth0 -B 256 -n -f "tcp port 443" -w /tmp/cap.pcapng-B的具体数值取决于流量大小。粗略估算是:链路带宽(Mbps)除以 8 得到每秒兆字节数,再乘以你希望缓冲的秒数。千兆链路满跑大约 125 MB/s,想缓冲两秒就设 256。设太大也不会有什么副作用,无非是多占点内存,宁可设大一点。
还有一点:在高流量环境下不要用 Wireshark 图形界面直接抓包。GUI 的实时解码开销很大,包一多必然丢。正确姿势是用 dumpcap 抓,抓完再用 Wireshark 打开文件分析。这个分工是官方推荐的,不是我个人偏好。
6. 抓到了却看不明白:六个高频翻车点复盘
表达式写得再漂亮,抓到的包里如果没有你要的信息,一样白搭。下面这几个坑我基本每年都会重新踩一次,写下来给自己和同行提个醒。
6.1 校验和错误不用管,那是网卡卸载造成的
打开 Wireshark 满屏红黑条纹,提示 TCP checksum incorrect,很多人第一反应是“链路有问题”。绝大多数情况下这是假的。
原因是现代网卡支持校验和卸载(checksum offload),操作系统把计算校验和的工作交给网卡去做。抓包动作发生在网卡计算之前,所以抓到的包校验和字段是空的或者错的。同理,发送端看到的超大包(超过 MTU 的那种)也是网卡的分段卸载(TSO/GSO)造成的,报文在抓到的那一刻还没被切分。
判断方法:如果所有出方向的包都报校验和错误,入方向正常,那基本就是卸载问题,可以放心忽略。真想在抓包时看到真实的分段和校验和,需要临时关闭卸载功能,但这会影响系统性能,排查完记得开回去。
6.2 时间戳的精度和时区会骗人
默认的frame.time显示的是本地时区的时间,但 pcap 文件里存的是 UTC 时间。如果你在 Wireshark 里看到的请求时间和服务器日志里的时间差了八小时,先别怀疑时钟漂移,检查一下视图菜单里的时间显示格式设置。
时间精度也值得注意。默认可能是秒级显示,看起来很多包的时间戳一样,实际上是毫秒或者微秒级的差别。把时间显示格式改成“日期和时间(含毫秒)”或更高精度,才能准确判断两个包之间的间隔。
判断“到底慢在哪一段”的时候,用frame.time_delta比看绝对时间有用得多。它显示的是跟上一个被捕获到的包的时间差,在客户端、服务端两处分别抓包,对比各自的最大时间差出现在哪个环节,就能定位到具体是哪一段链路慢。
6.3 服务器上的抓包权限和接口选择
在 Linux 服务器上抓包需要 root 权限或者CAP_NET_RAW能力。直接sudo当然可以,但更规范的做法是给 dumpcap 加能力:
sudo setcap cap_net_raw,cap_net_admin+eip /usr/bin/dumpcap改完之后普通用户也能抓包,不用每次都提权。这个操作要在有管理权限的前提下做,改完记得确认一下权限设置是否符合所在环境的安全规范。
接口选择上要注意容器的场景。如果服务跑在容器里,在宿主机上抓,看到的是经过网桥 NAT 之后的地址,可能跟容器内的地址对不上。这时候要么进到容器的网络命名空间里抓,要么在宿主机上按宿主机的地址过滤,然后对着 NAT 规则反推。我一般倾向于前者,因为看到的地址跟应用日志里的能对应上,省去一层翻译。
6.4 pcap 文件太大打不开的处理顺序
几百兆的 pcap 直接双击打开,Wireshark 会卡到你以为死机了。正确的处理顺序是先裁后开:
先用editcap按时间或者按包数切出一小段,或者用tshark -Y筛出关心的流量重定向到新文件,再打开新文件分析。如果只是想统计,压根不用打开图形界面,全程用 tshark 加-z参数做统计就够了。
# 先筛选出关心的流量另存,文件会小很多 tshark -r big.pcapng -Y "tcp.analysis.flags" -w suspect.pcapng # 或者直接做统计,不生成文件 tshark -r big.pcapng -q -z io,stat,1,"tcp.analysis.retransmission"最后这条-z io,stat是按秒统计指定过滤条件下的包数,用来画重传的时间分布特别直观。哪一秒重传暴增,问题就出在那几秒的链路上,直接把排查范围从几小时缩到几秒。
6.5 显示过滤器和捕获过滤器混用导致的空结果
这个前面提过一次,但值得再强调,因为它的表现太有迷惑性了:抓包启动正常,文件也在增长,但打开之后一条都过滤不出来,看起来就像“这个条件真的没有匹配的包”。
判断方法很直接:清空显示过滤器,看看有没有包。有包但一加过滤器就空了,说明是过滤条件的问题;连包都没有,说明是捕获过滤器的问题。两步一测,就能分清是哪个环节拦住了。
我自己的习惯是在捕获阶段只写最粗的条件(主机或者端口),把所有的应用层筛选都留给显示过滤器。这样即使一开始判断错了关注点,数据还在文件里,随时能换个角度重新筛。反过来就麻烦了,捕获阶段漏掉的包,只能重新抓。
6.6 私密信息泄露和文件管理
pcap 文件里可能包含 Cookie、Authorization 头、明文密码、个人身份信息。抓完包之后这些东西就落在磁盘上了,随手一放很容易出问题。
我在实践里会做三件事:抓包文件统一放在指定目录并按时间命名,方便定期清理;对外分享前用tshark检查一遍有没有敏感头部的流量;确需分享的话,用editcap或者tracewrangler这类工具做脱敏处理。这些动作花不了几分钟,但能避免很多麻烦。
另外提醒一句,抓包这件事本身要在自己负责或者已获授权的网络范围内进行。在自己机器上抓自己的流量、在负责的服务器上抓自己服务的流量,这些都没问题;对公共网络或者不属于自己管辖的设备做流量分析,性质就完全不同了。这个边界心里得有数。
我个人的经验是,把上面这些表达式和流程整理成一个自己的速查文档,遇到新问题就往里补一条,半年下来就是一份完全贴合自己业务场景的清单,比任何通用手册都好用。真正需要记的不是几十条表达式,而是“先分清哪个过滤器、再确定抓的位置、最后按场景选字段”这个判断顺序。顺序对了,表达式写错两次也能很快改回来。