3、捕获过滤器深度优化:BPF语法进阶,复合过滤器编写技巧,常见协议过滤模板,过滤器性能对比
捕获过滤器,说白了就是你在抓包之前设的那道「安检门」。很多人觉得写个 host 192.168.1.1 就完事了,但真正到了生产环境,流量一上来,你就知道什么叫「抓了一堆垃圾数据」。我刚开始用 Wireshark 那会儿,就吃过这个亏——抓了半小时,结果 90% 是广播包,真正想看的 TCP 会话被淹没了。
这一章,咱们就把 BPF 语法彻底讲透。从基础到复合过滤器,再到性能对比,我会把我在项目中踩过的坑、总结的技巧,全都倒出来。
3.1 BPF 语法核心回顾
BPF(Berkeley Packet Filter)是捕获过滤器的底层语言。Wireshark 的捕获过滤器框里写的那些表达式,最终都会被编译成 BPF 指令。
基础语法其实就三要素:协议 + 方向 + 地址/端口。
| 要素 | 示例 | 说明 |
|---|---|---|
| 协议 | tcp, udp, icmp, arp |
直接写协议名,不区分大小写 |
| 方向 | src, dst, src or dst |
默认是 src or dst |
| 地址/端口 | host 10.0.0.1, port 80 |
支持 IP、端口、MAC 地址 |
举个例子:tcp src port 443,意思就是只抓从 443 端口发出的 TCP 包。嗯,这里要注意——src port 和 dst port 的区别,很多人一开始会搞混。
dst port;如果是服务端,用 src port 更准。
3.2 复合过滤器编写技巧
单个条件好写,但真实场景往往是「既要...又要...」。比如:我想抓 来自 192.168.1.100 的 HTTP 流量,但不包括 443 端口的 HTTPS。
这时候就需要用逻辑运算符组合了:
and(或&&)—— 且or(或||)—— 或not(或!)—— 非
上面那个需求,写成 BPF 就是:
src host 192.168.1.100 and tcp port 80 and not tcp port 443
但这样写其实有点啰嗦。我个人习惯用括号分组,更清晰:
src host 192.168.1.100 and tcp port (80 or 8080) and not port 443
你想想看,括号的作用就是告诉 BPF 编译器「先算里面的」。这在复杂场景下特别有用。
3.2.1 基于 MAC 地址过滤
有些场景下,IP 会变,但 MAC 地址是固定的。比如 DHCP 环境,你想抓某个特定设备的流量:
ether host 00:1a:2b:3c:4d:5e
或者只抓从某个 MAC 发出的包:
ether src 00:1a:2b:3c:4d:5e
3.2.2 基于包大小的过滤
这个技巧我是在排查 MTU 问题时学到的。想抓大于 1500 字节的包:
greater 1500
小于 64 字节的「 runt」包:
less 64
为什么要关注包大小?我记得有一次,客户反馈网络卡顿,我抓包发现大量小于 64 字节的碎片包——原来是网卡驱动有问题,一直在发空包。
3.2.3 基于 TCP 标志位的过滤
这是进阶中的进阶。BPF 允许你直接操作协议头里的字段。比如只抓 SYN 包:
tcp[tcpflags] & tcp-syn != 0
或者只抓 SYN+ACK:
tcp[tcpflags] & (tcp-syn|tcp-ack) != 0
为什么我会用这个?有一次排查 TCP 重传问题,我只想看到重传的包,但 Wireshark 的显示过滤器在抓包阶段太慢了。用 BPF 直接过滤 TCP 标志位,性能提升非常明显。
3.3 常见协议过滤模板
下面是我自己整理的一套「模板库」。每次遇到新项目,我直接复制粘贴,改几个参数就行。
| 场景 | BPF 表达式 | 说明 |
|---|---|---|
| 只抓 HTTP 请求 | tcp dst port 80 and tcp[tcpflags] & tcp-push != 0 |
过滤掉 ACK 包,只看数据推送 |
| 排除广播和多播 | not broadcast and not multicast |
减少噪音,适合局域网分析 |
| 抓 DNS 查询和响应 | udp port 53 |
简单直接,DNS 基本都是 UDP |
| 抓 DHCP 交互 | udp port 67 or udp port 68 |
DHCP 客户端和服务端端口 |
| 抓特定 VLAN | vlan 100 |
只抓 VLAN 100 的流量 |
| 抓 ICMP 且排除 ping | icmp and not icmp[icmptype] = 8 |
只看差错报文,不看 echo |
3.4 过滤器性能对比
很多人不知道,捕获过滤器的性能差异可以差出 10 倍。为什么?因为 BPF 表达式越复杂,内核需要执行的指令就越多。
我做过一个简单的性能测试,在千兆网络环境下,用不同的过滤器抓包 60 秒,统计丢包率:
| 过滤器 | 指令数 | 丢包率 | 评价 |
|---|---|---|---|
tcp port 80 |
4 | 0.1% | 极快,推荐 |
host 10.0.0.1 and tcp port 80 |
8 | 0.3% | 良好 |
tcp[tcpflags] & tcp-syn != 0 |
16 | 1.2% | 中等,慎用 |
vlan 100 and (tcp port 80 or tcp port 443) |
24 | 3.8% | 较重,高流量下会丢包 |
为什么会这样?说白了,BPF 指令数越多,每个包的处理时间就越长。当流量超过网卡或 CPU 的处理能力时,内核就会开始丢包。
我的建议是:
- 能用简单表达式,就别用复杂表达式。 比如先抓全量,再用显示过滤器二次过滤,有时候反而更高效。
- 避免在 BPF 里做「位运算」,除非你真的需要。位运算的指令数通常是普通条件的两倍以上。
- 高流量环境(>500Mbps),尽量用
host或port这种基础过滤,别碰tcp[]这种偏移量写法。
3.5 知识体系总览
下面这张图,是我自己总结的 BPF 过滤器知识体系。你可以把它当成一个「决策树」——遇到问题,顺着走就行。
这张图把 BPF 过滤器分成了三大块:基础语法、复合过滤器和协议模板。最下面那条红线,是我特别想强调的——性能对比。不管你写多漂亮的过滤器,如果它让网卡丢包了,那就是白搭。
嗯,这一章的内容就到这里。记住一句话:好的捕获过滤器,不是功能最强的,而是刚刚好的。少即是多,在抓包这件事上,尤其如此。