跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

tcpdump 的 BPF 过滤语法:从入门到精通

前言

在网络排障、安全分析和日常运维中,tcpdump 是最常用的网络抓包工具之一。它的强大之处不仅在于能抓包,更在于它内置了一套极其灵活的过滤语言 —— BPF(Berkeley Packet Filter)语法。这套语法让你可以像编写查询语句一样,精确地告诉 tcpdump 你需要什么样的数据包。

本文将从零开始,由浅入深地系统讲解这套语法,让你读完就能上手操作,并能理解那些看起来很复杂的过滤表达式。

一、认识 BPF

1.1 什么是 BPF?

BPF 全称 Berkeley Packet Filter,直译为”伯克利包过滤器”。它最初由 Steven McCanne 和 Van Jacobson 在 1992 年提出,目的是为 Unix 系统提供一种高效的数据包捕获机制。

我们可以通过一个生活化的场景来理解它:

你站在一条高速公路上,每分钟有上万辆车通过。你只想记录红色的小轿车,但如果你把每辆车都拦下来看颜色,交通就瘫痪了。

BPF 就相当于在高速公路入口安装了一个智能摄像头:你告诉它规则,它在车辆通过时实时判断,只有符合条件的车辆才会被记录,不匹配的直接放过,不影响正常交通。

在 Linux 系统中,BPF 工作在网络协议栈的最底层。当数据包到达网卡时,内核会先执行你编写的 BPF 过滤规则,只有通过过滤的包才会被复制一份交给 tcpdump 等用户态程序。这种在内核层面就完成过滤的机制,极大地提高了抓包效率。

1.2 BPF 与 tcpdump 的关系

tcpdump 本身是一个用户态程序,它依赖 libpcap 库来实现抓包功能。当我们执行 tcpdump 并传入过滤表达式时,整个流程是这样的:

  1. tcpdump 将你输入的文本表达式交给 libpcap 库
  2. libpcap 调用 pcap_compile() 函数,将文本表达式编译成 BPF 虚拟机的字节码指令
  3. 这些字节码通过 pcap_setfilter() 被注入到内核中的 BPF 虚拟机
  4. 内核在网络层对每个数据包执行这些指令
  5. 只有匹配成功的包才会被复制到用户态的 tcpdump

这套机制的核心优势在于:过滤逻辑在内核中执行,避免了大量无关数据从内核到用户态的拷贝开销。

二、BPF 语法基础

2.1 三个核心限定词

BPF 过滤表达式由一个个”原语”(Primitive)通过逻辑运算符组合而成。每个原语通常包含一个标识符(如 IP 地址、端口号)和若干个限定词(Qualifier)来精确描述这个标识符的含义。

限定词分为三类:

类型限定词(Type)

指明标识符的性质是什么。

关键词含义示例
host表示一个 IP 地址或主机名host 192.168.1.1
net表示一个网段net 192.168.0.0/16
port表示一个端口号port 80
portrange表示一个端口范围portrange 8000-9000

方向限定词(Dir)

指明数据包的传输方向。

关键词含义示例
src数据包来源src host 192.168.1.1
dst数据包目标dst port 80
不指定默认为双向port 80 表示源或目标为 80 端口

协议限定词(Proto)

指明数据包的网络协议类型。

关键词含义
tcpTCP 协议
udpUDP 协议
icmpICMP 协议(ping 命令使用)
arpARP 协议(局域网地址解析)
ipIP 协议(所有 IP 包)
ip6IPv6 协议
ether以太网帧

2.2 逻辑运算符

多个条件可以通过逻辑运算符组合成更复杂的表达式。

运算符含义示例
and 或 &&逻辑与,所有条件都满足host 1.1.1.1 and port 80
or 或 ||逻辑或,至少一个条件满足port 80 or port 443
not 或 !逻辑非,排除某些条件not port 22

括号 () 可以用来明确运算的优先级:

tcpdump 'host 1.1.1.1 and (port 80 or port 443)'

⚠️ 重要提醒:在 shell 命令行中使用括号时,必须用单引号将整个表达式包起来,否则 shell 会误解括号的含义。

2.3 基础实战演练

下面从最简单的例子开始,逐步增加复杂度:

# 1. 最简单的抓包(无过滤)
tcpdump -i eth0

# 2. 只看特定主机的所有流量
tcpdump host 192.168.1.100

# 3. 只看特定端口的流量(HTTP)
tcpdump port 80

# 4. 只看从某主机发出的流量
tcpdump src host 192.168.1.100

# 5. 只看发给某个端口的流量
tcpdump dst port 443

# 6. 组合条件:某主机访问网页的流量
tcpdump 'host 192.168.1.100 and (port 80 or port 443)'

# 7. 排除干扰:不看自己的 SSH 连接
tcpdump not port 22

# 8. 只看 TCP 协议
tcpdump tcp

# 9. 只看网段内的所有流量
tcpdump net 192.168.0.0/16

这些例子涵盖了大部分日常运维场景。掌握了以上内容,你已经可以应对 80% 的抓包需求了。

三、高级过滤技术

3.1 理解数据包的结构

要使用高级过滤,首先需要理解数据包在内存中的存储方式。对计算机来说,网络数据包就是一串连续的字节,像这样:

45 00 00 3C 1A 2B 40 00 40 06 8A 9C C0 A8 01 64 ...

我们可以通过 协议[偏移量] 的方式来访问这串字节中的特定位置。这就像访问数组元素一样:packet[0]、packet[1]……每个字节都有唯一的偏移位置。

不同的网络协议规定了各自头部各字段的位置:

IP 头部结构(关键字段)

偏移(字节)字段含义
0版本号 + 头部长度
8TTL(Time To Live,生存时间)
9上层协议类型(6=TCP,17=UDP,1=ICMP)
12-15源 IP 地址
16-19目的 IP 地址

TCP 头部结构(关键字段)

偏移(字节)字段含义
0-1源端口
2-3目的端口
4-7序列号
8-11确认号
12数据偏移
13标志位(Flags)
14-15窗口大小

3.2 访问特定字节

基本的访问格式是:协议[偏移量]

# 检查 IP 头部的 TTL 字段(第 8 个字节)
tcpdump 'ip[8] < 10'

# 检查 IP 头部协议字段(第 9 个字节),6 表示 TCP
tcpdump 'ip[9] = 6'

# 检查 ICMP 头部类型字段(第 0 个字节),8 表示 ping 请求
tcpdump 'icmp[0] = 8'

如果需要访问连续多个字节,使用 协议[偏移量:长度] 格式:

# TCP 源端口是 2 个字节,偏移 0 开始
tcpdump 'tcp[0:2] = 80'

# TCP 目的端口是 2 个字节,偏移 2 开始
tcpdump 'tcp[2:2] = 443'

# HTTP 请求行开头 4 个字节等于 "GET "
tcpdump 'tcp[20:4] = 0x47455420'

3.3 TCP 标志位的深入理解

TCP 标志位是高级过滤中最常见的应用场景。TCP 头部第 13 个字节包含了 8 个标志位:

二进制位:  7    6    5    4    3    2    1    0
         +-----+-----+-----+-----+-----+-----+-----+-----+
         | CWR | ECE | URG | ACK | PSH | RST | SYN | FIN |
         +-----+-----+-----+-----+-----+-----+-----+-----+

每个标志位对应一个比特(bit)。tcpdump 预定义了以下常量来帮助我们检查这些标志位:

常量十六进制二进制含义
tcp-fin0x0100000001关闭连接
tcp-syn0x0200000010建立连接
tcp-rst0x0400000100重置连接
tcp-push0x0800001000立即推送数据
tcp-ack0x1000010000确认数据
tcp-urg0x2000100000紧急数据

检查标志位使用按位与(&)操作:

# 抓所有 SYN 包(包括 SYN-ACK)
tcpdump 'tcp[tcpflags] & tcp-syn != 0'

# 抓纯 SYN 包(不包括 SYN-ACK):SYN=1 且 ACK=0
tcpdump 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

# 抓 RST 包(连接重置)
tcpdump 'tcp[tcpflags] & tcp-rst != 0'

# 抓 FIN 包(关闭连接)
tcpdump 'tcp[tcpflags] & tcp-fin != 0'

理解按位与:tcp[tcpflags] & tcp-syn != 0 的意思是”把标志位字节和 SYN 的二进制值做按位与运算,结果不为 0 说明 SYN 位是 1″。这就像一个灯泡开关,你要检查某个开关是否打开。

3.4 比较运算和数值匹配

除了检查标志位,你还可以用各种比较运算符来匹配数值:

运算符含义
= 或 ==等于
!=不等于
<小于
>大于
<=小于等于
>=大于等于
# 抓取包大小大于 1500 字节的包(可能涉及巨帧)
tcpdump greater 1500

# 抓取包大小小于 64 字节的包
tcpdump less 64

# 抓取 TTL 小于等于 10 的包
tcpdump 'ip[8] <= 10'

# 抓取 ICMP 类型为 8(ping 请求)或 0(ping 回复)的包
tcpdump 'icmp[0] = 8 or icmp[0] = 0'

3.5 十六进制值的使用技巧

在很多情况下,我们需要用十六进制来表示特定的字节模式。常用的 ASCII 字符与十六进制对应关系:

字符串十六进制
GET(四个字符:G E T 空格)0x47455420
POST0x504f5354
HTTP0x48545450
2000x323030
4040x343034
# 抓 HTTP GET 请求(数据开头是 "GET ")
tcpdump -A 'tcp port 80 and tcp[20:4] = 0x47455420'

# 抓 HTTP POST 请求
tcpdump -A 'tcp port 80 and tcp[20:4] = 0x504f5354'

# 抓 HTTP 响应码为 200 的包(偏移量可能需要调整)
tcpdump -A 'tcp port 80 and tcp[20:4] = 0x323030'

3.6 网卡级别的过滤

你还可以在以太网帧层面进行过滤:

# 抓特定源 MAC 地址的包
tcpdump 'ether src 00:11:22:33:44:55'

# 抓特定目标 MAC 地址的包
tcpdump 'ether dst aa:bb:cc:dd:ee:ff'

# 抓特定 MAC 地址的双向流量
tcpdump 'ether host 00:11:22:33:44:55'

# 抓 VLAN 标签(802.1Q)为 100 的包
tcpdump 'vlan 100'

# 抓 MPLS 标签的包
tcpdump 'mpls'

3.7 协议栈嵌套场景

在实际网络中,数据包经常是多层协议嵌套的(例如以太网 → IP → TCP → HTTP)。BPF 允许你处理这种嵌套:

# 抓 IPv6 的 TCP 包
tcpdump 'ip6 proto 6'

# 抓 IPv6 的 ICMP 包
tcpdump 'ip6 proto 58'

# 抓非 IP 包(如 ARP)
tcpdump 'not ip'

# 抓 IP 但非 TCP 非 UDP 的包
tcpdump 'ip and not tcp and not udp'

第四部分:完整参考速查表

4.1 常用过滤器速查

需求表达式
特定 IPhost 10.0.0.1
特定源 IPsrc host 10.0.0.1
特定目标 IPdst host 10.0.0.1
特定网段net 192.168.0.0/16
特定端口port 80
多个端口port 80 or port 443
端口范围portrange 8000-9000
特定协议tcp / udp / icmp
排除端口not port 22
包大小greater 1000 / less 64
MAC 地址ether host 00:11:22:33:44:55
VLANvlan 100
TCP SYNtcp[tcpflags] & tcp-syn != 0
TCP ACKtcp[tcpflags] & tcp-ack != 0
TCP RSTtcp[tcpflags] & tcp-rst != 0
TCP FINtcp[tcpflags] & tcp-fin != 0

4.2 常用偏移量速查

表达式含义
ip[8]IP 头部的 TTL 字段
ip[9]IP 头部的协议字段(6=TCP,17=UDP)
ip[12:4]源 IP 地址
ip[16:4]目的 IP 地址
tcp[0:2]TCP 源端口
tcp[2:2]TCP 目的端口
tcp[tcpflags] 或 tcp[13]TCP 标志位
udp[0:2]UDP 源端口
udp[2:2]UDP 目的端口
icmp[0]ICMP 类型(8=请求,0=回复)

五、tcpdump 常用选项

除了过滤表达式,tcpdump 本身还有大量实用选项,这里整理最常用的一部分:

选项含义示例
-i指定网络接口-i eth0
-c抓取指定数量的包后退出-c 100
-n不解析主机名(加速)-n
-nn不解析主机名和端口名-nn
-v / -vv / -vvv输出详细信息-vv
-s抓取每个包的前 N 个字节-s 0(抓完整包)
-w写入文件(pcap 格式)-w capture.pcap
-r从文件读取-r capture.pcap
-A以 ASCII 格式打印数据-A
-X以十六进制和 ASCII 打印-X
-e打印链路层头信息-e
-q精简输出-q
-D列出所有可用网卡-D

六、实用案例合集

6.1 安全分析场景

# 1. 检测 SYN 洪水攻击(大量 SYN 包)
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0' | wc -l

# 2. 检测端口扫描(大量来自同一 IP 的 SYN)
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0' -c 1000

# 3. 查看 DNS 请求(UDP 53 端口)
tcpdump -i eth0 'udp port 53'

# 4. 检查 ARP 欺骗(异常的 ARP 响应)
tcpdump -i eth0 'arp and arp[7] = 2'

6.2 应用排查场景

# 1. 详细查看 HTTP 交互
tcpdump -i eth0 -A 'tcp port 80 and (tcp[20:4] = 0x47455420 or tcp[20:4] = 0x504f5354)'

# 2. 查看 HTTPS 连接建立
tcpdump -i eth0 'tcp port 443 and tcp[tcpflags] & tcp-syn != 0'

# 3. 查看 MySQL 数据库查询(3306 端口)
tcpdump -i eth0 -A 'port 3306'

# 4. 跟踪某个请求的完整 TCP 会话
tcpdump -i eth0 'host 192.168.1.100 and port 80' -c 100

6.3 网络诊断场景

# 1. 查看 TTL 异常低的包
tcpdump -i eth0 'ip[8] < 5'

# 2. 查看重传包(重复的 ACK)
tcpdump -i eth0 'tcp[tcpflags] & tcp-ack != 0 and tcp[8:4] = tcp[4:4]'

# 3. 查看 ICMP 错误消息
tcpdump -i eth0 'icmp[0] = 3 or icmp[0] = 11'

# 4. 查看 DHCP 交互
tcpdump -i eth0 'udp port 67 or udp port 68'

七、常见问题与排障

7.1 抓不到包的排查步骤

  1. 确认网卡名称:tcpdump -D 列出所有接口
  2. 检查网卡是否启用:ip link show
  3. 检查权限:大多数情况下需要 sudo
  4. 不加过滤器试试:sudo tcpdump -i eth0 -c 10
  5. 确认流量确实存在:用 ping 测试

7.2 命令报错的解决方法

错误信息解决方案
Permission denied使用 sudo 运行
No such device exists网卡名写错了,用 -D 查看正确的名称
syntax error检查括号是否配对,是否用引号包住了表达式
pcap_compile: not supported某些 BPF 语法在旧内核中不支持

7.3 性能优化建议

  1. 尽量使用 -n 选项:禁止 DNS 反向解析,显著提升速度
  2. 在内核过滤:BPF 在内核执行,比 grep 输出快得多
  3. 慎用 -A 和 -X:打印数据内容会增加 CPU 开销
  4. 限制抓包数量:使用 -c 参数自动退出

结语

BPF 过滤语法虽然看起来复杂,但它的核心逻辑非常清晰:通过类型、方向、协议三个维度定位数据包,通过逻辑运算组合多个条件,通过字节偏移实现精细化控制。

在实际使用中,大部分场景只需要基础语法就够了。高级的字节偏移技巧可以作为”备用武器”,遇到复杂问题时才拿出来使用。建议从简单的过滤开始,逐步尝试更复杂的组合,最终你会发现自己已经能熟练地写出各种过滤表达式了。

作者

老丹

关注我
其他文章
上一个

BCC 完全指南:从原理到实战,深入理解BPF编译工具集

下一个

YUV色彩空间完全指南:从概念原理到工程实战

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

近期文章

  • Ubuntu 防火墙迁移指南:从 UFW 到 firewalld 的完整实践 2026年9月12日
  • Nano 编辑器完全操作指南:从入门到熟练 2026年9月12日
  • SSCG:让自签名证书不再“危险”的生成工具 2026年9月12日
  • Ubuntu Samba 服务安装与配置完全指南 2026年9月12日
  • 从零开始:用 Docker 部署 Jellyfin 并启用英特尔核显硬件加速 2026年9月11日

文章分类

  • C/C++开发 (22)
  • Docker容器 (5)
  • Linux工具包 (17)
  • Linux服务配置 (50)
  • Linux系统 (16)
  • OpenWrt路由 (3)
  • Shell脚本 (3)
  • 代码管理 (1)
  • 安防技术 (4)
  • 数据安全 (36)
  • 未分类 (1)
  • 网络协议 (25)
  • 计算机理论 (23)
  • 音视频技术 (5)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号