BPF 表达式是什么
tcpdump、dumpcap 以及 Wireshark 的捕获过滤器共用同一套 libpcap 表达式语法,习惯上称为 BPF。它由限定符加原语组成:类型限定符(host、net、port、portrange)决定匹配什么对象,方向限定符(src、dst)决定哪个方向,协议限定符(tcp、udp、ip、ip6、arp、ether)决定哪一层。多个原语用 and、or、not 连接,可以用括号分组。理解这个结构之后,写过滤器就从试错变成了翻译。
基本信息
| 基本结构 | 协议限定符 方向限定符 类型限定符 值 |
|---|---|
| 关系运符 | != 表示包含该位,= 表示精确相等 |
| 常用原语 | host、net、port、portrange、less、greater、ether 及协议名 |
| 连接词 | and / or / not,也可写 && / || / ! |
| 引用 | 表达式含括号或竖线时必须用单引号包住 |
| 手册 | man 8 pcap-filter |
限定符对照
| 类别 | 取值 | 说明 |
|---|---|---|
| 类型 | host / net / port / portrange | 决定匹配地址、网段、端口还是端口区间 |
| 方向 | src / dst / src or dst | 默认双向,加 src 或 dst 限定单向 |
| 协议 | tcp / udp / icmp / ip / ip6 / arp / ether | 限定匹配发生在哪一层 |
| 组合规则 | 一个原语最多一个类型加一个方向加一个协议 | 同类限定符不可叠加,如 tcp tcp 是非法写法 |
| 字节访问 | tcp[13] 这类下标写法 | 读取指定偏移的字节,用于标志位与载荷匹配 |
典型使用场景
- 只看某台主机:host 10.0.0.9,双向匹配最省心
- 只看某个网段:net 10.10.0.0/16,排查机房级问题
- 只看某个端口:port 443,或 dst port 3306 收窄方向
- 按协议号过滤:ip proto 47 抓 GRE、ip proto 89 抓 OSPF
- 按载荷字节匹配:tcp 偏移加四字节比较,精确抓 HTTP GET
- 用排除法减噪音:not (arp or icmp)
从简单到复杂
从简单到复杂
tcpdump -i eth0 -nn host 10.0.0.9 # 一台主机
tcpdump -i eth0 -nn 'host 10.0.0.9 and tcp port 443' # 主机加端口
tcpdump -i eth0 -nn 'host 10.0.0.9 and (port 80 or port 443)' # 用括号分组
tcpdump -i eth0 -nn 'not (arp or icmp)' # 排除法
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0' # 标志位
tcpdump -i eth0 -nn 'ip proto 47' # 协议号
tcpdump -i eth0 -nn 'udp dst portrange 5000-5100' # 端口区间
tcpdump -i eth0 -nn 'ether host 00:11:22:33:44:55' # 二层地址写复杂过滤器时先把需求拆成四问:谁发的、哪个方向、什么协议、哪个端口,再逐项翻译成原语。这个方法比直接憋一个长表达式可靠得多。
括号、竖线、尖括号在 shell 里都是特殊字符,含这些字符的表达式必须用单引号包住整体,否则过滤器会被 shell 提前拆解。
字节偏移示例
字节偏移示例
tcpdump -i eth0 -nn -s 0 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' # 匹配 GET 请求
arp[6:2] = 1 # 只看 ARP 请求,等于 2 是应答
icmp[icmptype] = icmp-echo # 只看 ping 请求两条表达式的等价关系
- host 10.0.0.9 等于 src host 10.0.0.9 or dst host 10.0.0.9,方向默认为双向
- port 443 等于 tcp port 443 or udp port 443,协议默认为传输层两者都含
- not (arp or icmp) 与 not arp and not icmp 等价,前者可读性更好
- tcp[tcpflags] & tcp-syn != 0 与 tcp[tcpflags] & 0x02 != 0 等价,名字写法更直观
常见需求与写法
| 想做什么 | 表达式写法 | 要点 |
|---|---|---|
| 只看一台主机与某服务端口 | host 10.0.0.9 and dst port 443 | 方向修饰只作用于紧跟其后的原语 |
| 只看两个网段之间 | net 10.10.0.0/16 and net 172.16.0.0/12 | 两个 net 分别匹配源与目的,双向都命中 |
| 排除某类协议 | not icmp6 | IPv6 邻居发现依赖 ICMPv6,排除前想清楚影响 |
| 抓带 VLAN 标签的帧 | vlan and tcp port 443 | vlan 原语会跳过一次标签,写完整需要考虑双层标签 |
| 抓分片报文 | ip[6:2] & 0x1fff != 0 | 首片带传输层头部,后续分片没有端口信息 |
| 只抓某个方向的响应 | src port 443 and dst host 10.0.0.9 | 用源端口加目的地址两步限定最明确 |
复杂表达式建议先在命令行里用 -c 少量取样验证命中效果,再挂到落盘命令上。写错条件直接跑长时间抓包,最坏的结果是抓了一个小时却什么也没抓到。
写表达式的顺序
- 先确定分析目标:是要统计数量、看内容,还是确认某类报文是否存在
- 再选粒度:越靠前的条件越便宜,先用主机与网段收窄,再加端口与协议
- 然后确认方向:只有需要区分请求与响应时才加 src 或 dst 限定
- 最后考虑排除:用 not 去掉已知噪音,但要确认排除项不会误伤目标流量
- 写完先用少量计数验证命中量级,量级明显不对说明条件写反了