1. 抓包过滤器到底在解决什么问题
很多人第一次接触 tcpdump,敲下命令之后屏幕上滚出来的东西能把人看晕——几千行数据包记录,密密麻麻的 IP、端口、标志位,真正想看的那几条淹没在里面。抓包过滤器就是干这个的:在数据包进入分析环节之前,先把不关心的流量扔掉,只留下你真正需要的那部分。
打个比方,抓包过滤器就像是在一条繁忙的高速公路上设了一个收费站,只放行特定类型的车辆通过。没有这个收费站,你面对的就是整条路的车流,根本没法分析。tcpdump 的过滤器基于 BPF(Berkeley Packet Filter)语法,这个语法被很多工具复用,包括 Wireshark 的捕获过滤器、tshark 等。学会一套,到处能用。
这篇文章适合谁看?如果你已经会用 tcpdump 抓包,但每次都是tcpdump -i any一把梭,然后对着满屏输出发呆,那这篇就是写给你的。如果你还没用过 tcpdump,也没关系,我会从过滤器的基础语法讲起,配合实际场景一步步展开。核心关键词就一个:tcpdump 抓包过滤器。围绕它,我会把语法规则、组合逻辑、常见场景、排查技巧全部拆开讲透。
先说清楚一个前提:tcpdump 的过滤器表达式是位置参数,写在所有选项之后。比如tcpdump -i eth0 -nn port 80,port 80就是过滤器。这个位置不能搞错,否则 tcpdump 会把过滤器当成接口名或者其他参数去解析,直接报错。
2. 过滤器语法核心:从类型到方向再到协议
2.1 三种限定类型:host、net、port
BPF 过滤器的基本构件是“限定词 + 值”的组合。最常用的三种限定类型是:
- host:指定主机,可以是 IP 或主机名。例如
host 192.168.1.100。 - net:指定网段。例如
net 192.168.1.0/24或net 10.0.0.0 mask 255.0.0.0。 - port:指定端口。例如
port 443、portrange 8000-9000。
这三种类型可以单独使用,也可以组合。单独使用时,host 192.168.1.100表示“源或目的地址是 192.168.1.100 的所有包”。注意这里默认是双向的,源和目的都匹配。
我见过不少人写tcpdump host 10.0.0.1 and host 10.0.0.2,以为能抓到两台主机之间的通信,实际上这个表达式抓的是“任意一端是 10.0.0.1 且任意一端是 10.0.0.2”的包,结果是对的,但逻辑上不够精确。更严谨的写法后面会讲。
2.2 方向限定词:src、dst 以及它们的组合
方向限定词用来缩小匹配范围:
src host 192.168.1.100:源地址是 192.168.1.100。dst host 192.168.1.100:目的地址是 192.168.1.100。src or dst host 192.168.1.100:等价于不加方向限定词。src and dst host 192.168.1.100:源和目的都是这个地址,通常用于本机回环流量。
方向限定词同样适用于 port:src port 53表示源端口是 53,通常是 DNS 服务器的响应;dst port 53表示目的端口是 53,通常是 DNS 查询请求。这个区分在排查 DNS 问题时特别有用。
注意:
src和dst只能修饰紧跟其后的那一个限定词。src host 10.0.0.1 and dst port 80是正确的,但src host 10.0.0.1 and port 80中的port 80不受src影响,仍然是双向匹配。
2.3 协议限定词:tcp、udp、icmp 等
协议限定词直接写协议名即可:tcp、udp、icmp、arp、ip、ip6。它们通常和其他限定词组合使用:
tcpdump -i eth0 -nn tcp and port 443 tcpdump -i eth0 -nn udp and port 53 tcpdump -i eth0 -nn icmp如果不写协议限定词,tcpdump 默认匹配所有协议。但加上协议限定词能显著减少捕获量,尤其是在高流量环境下。比如你只想看 TCP 的重传情况,那就直接tcp,把 UDP 和 ICMP 全部过滤掉。
协议限定词还可以和方向、类型组合,形成非常精确的表达式:
tcpdump -i eth0 -nn 'tcp and src host 10.0.0.5 and dst port 3306'这条命令抓的是:从 10.0.0.5 发出、目标端口是 3306 的 TCP 包。方向、协议、类型三个维度都用上了,精度很高。
2.4 逻辑运算符:and、or、not 的优先级陷阱
BPF 支持三种逻辑运算符:
and(也可以写成&&):与or(也可以写成||):或not(也可以写成!):非
优先级从高到低是:not>and>or。这个优先级顺序和大多数编程语言一致,但很多人会忽略它,导致过滤器行为不符合预期。
举个例子:
tcpdump -i eth0 -nn 'host 10.0.0.1 or host 10.0.0.2 and port 80'由于and优先级高于or,这个表达式实际等价于:
tcpdump -i eth0 -nn 'host 10.0.0.1 or (host 10.0.0.2 and port 80)'也就是说,所有与 10.0.0.1 相关的包都会被捕获,不管端口是什么;而 10.0.0.2 只捕获 80 端口的包。如果你想要的是“10.0.0.1 或 10.0.0.2 的 80 端口流量”,必须加括号:
tcpdump -i eth0 -nn '(host 10.0.0.1 or host 10.0.0.2) and port 80'实操心得:我个人的习惯是,只要表达式里同时出现
and和or,一律加括号。多打两个字符,省去后面排查过滤器逻辑的时间,非常划算。
3. 进阶过滤:字节偏移、标志位与协议头字段
3.1 用字节偏移精确匹配报文内容
BPF 允许你直接访问报文中的任意字节,语法是proto[offset:size]。其中proto是协议名(如ip、tcp、udp、icmp),offset是从协议头开始的字节偏移量,size是要读取的字节数(1、2、4)。
比如,想抓所有 TCP 标志位中 SYN 被置位的包:
tcpdump -i eth0 -nn 'tcp[13] & 2 != 0'这里tcp[13]是 TCP 头的第 14 个字节(从 0 开始计数),也就是标志位字段。& 2是按位与操作,检查 SYN 位(值为 2)是否被置位。!= 0表示只要 SYN 位为 1 就匹配。
类似地,抓 RST 包:
tcpdump -i eth0 -nn 'tcp[13] & 4 != 0'抓 FIN 包:
tcpdump -i eth0 -nn 'tcp[13] & 1 != 0'抓 PSH+ACK 包:
tcpdump -i eth0 -nn 'tcp[13] & 24 = 24'这些表达式在排查连接异常时非常有用。比如服务器收到大量 RST,你可以直接过滤出 RST 包,看是谁发的、发往哪里。
3.2 用 tcpdump 内置的 tcpflags 简化写法
如果你觉得字节偏移太底层,tcpdump 提供了更友好的写法:
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0' tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0' tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'最后一条抓的是“SYN 置位且 ACK 未置位”的包,也就是 TCP 三次握手的第一个包。这个写法比记字节偏移直观多了,推荐优先使用。
3.3 匹配 HTTP 请求方法
BPF 可以读取报文载荷的前几个字节,用来匹配应用层协议的特征。比如抓 HTTP GET 请求:
tcpdump -i eth0 -nn -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'这个表达式看起来复杂,拆开看就清楚了:
tcp[12:1]:读取 TCP 头的第 13 个字节,也就是数据偏移字段的高 4 位。& 0xf0:取高 4 位,表示 TCP 头长度(单位是 4 字节)。>> 2:右移 2 位,得到 TCP 头长度(单位是字节)。tcp[((tcp[12:1] & 0xf0) >> 2):4]:从 TCP 头之后开始读取 4 个字节,也就是 HTTP 请求方法的前 4 个字节。= 0x47455420:0x47 0x45 0x54 0x20对应 ASCII 的 "GET "。
同理,POST 是0x504f5354,HEAD 是0x48454144。这种写法在只需要抓特定 HTTP 方法时非常高效,避免了抓取大量无关流量。
注意:这个表达式只适用于 HTTP/1.x 的明文流量。HTTP/2 和 HTTPS 的载荷是加密的,无法用这种方式匹配。
4. 实战场景:从需求到过滤器表达式的完整推导
4.1 场景一:排查某台服务器与数据库之间的慢查询
需求:应用服务器 10.0.0.5 连接数据库 10.0.0.10 的 3306 端口,怀疑有慢查询导致连接堆积。需要抓取两者之间的 MySQL 流量。
分析:源地址是 10.0.0.5,目的地址是 10.0.0.10,目的端口是 3306,协议是 TCP。过滤器表达式:
tcpdump -i eth0 -nn -s 0 -w mysql_traffic.pcap 'tcp and src host 10.0.0.5 and dst host 10.0.0.10 and dst port 3306'这里加了-s 0表示抓取完整报文,不截断。-w把结果写入文件,方便后续用 Wireshark 分析。如果不写-w,直接在终端输出,MySQL 的二进制协议内容会显示成乱码,意义不大。
如果还想同时抓数据库返回的响应包,可以放宽方向限定:
tcpdump -i eth0 -nn -s 0 -w mysql_traffic.pcap 'tcp and host 10.0.0.5 and host 10.0.0.10 and port 3306'这样双向流量都能抓到。注意这里用了两个host加and,精确匹配两台主机之间的通信。
4.2 场景二:定位 DNS 解析异常
需求:某台机器解析域名时快时慢,需要抓取 DNS 查询和响应,看是查询没发出去,还是响应没回来。
分析:DNS 通常走 UDP 53 端口,源端口是随机高端口,目的端口是 53。过滤器表达式:
tcpdump -i eth0 -nn -s 0 'udp and port 53'这条命令会抓取所有 DNS 流量。如果只想看某台机器的 DNS 请求:
tcpdump -i eth0 -nn -s 0 'udp and src host 10.0.0.20 and dst port 53'如果 DNS 走 TCP(比如响应超过 512 字节时),需要单独抓:
tcpdump -i eth0 -nn -s 0 'tcp and port 53'实操中我一般两条命令同时开两个终端,或者用or合并:
tcpdump -i eth0 -nn -s 0 '(udp or tcp) and port 53'4.3 场景三:抓取特定网段的所有非本机流量
需求:某网段 192.168.10.0/24 内主机通信异常,需要抓取该网段与其他网段之间的流量,但排除本网段内部的互访。
分析:源或目的在 192.168.10.0/24,但源和目的不同时在 192.168.10.0/24。表达式:
tcpdump -i eth0 -nn -s 0 'net 192.168.10.0/24 and not (src net 192.168.10.0/24 and dst net 192.168.10.0/24)'这个表达式用了not和括号,逻辑是:匹配网段流量,但排除源和目的都在该网段的包。实际抓包时,这个过滤器能有效减少内部广播和互访带来的噪音。
4.4 场景四:抓取 HTTP 错误响应
需求:某 Web 服务返回大量 5xx 错误,需要抓取包含 "HTTP/1.1 5" 的响应包。
分析:HTTP 响应状态行以 "HTTP/1.1 " 开头,后面跟状态码。5xx 错误的特征字符串是 "HTTP/1.1 5"。用字节偏移匹配:
tcpdump -i eth0 -nn -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):8] = 0x485454502f312e3120'这个表达式匹配的是 "HTTP/1.1 " 这 9 个字符(0x48 0x54 0x54 0x50 0x2f 0x31 0x2e 0x31 0x20)。但这样只能匹配到所有 HTTP/1.1 响应,无法区分状态码。要精确匹配 5xx,需要再往后读一个字节:
tcpdump -i eth0 -nn -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):9] = 0x485454502f312e312035'最后的0x35是字符 '5' 的 ASCII 码。这样就能只抓 5xx 响应了。不过说实话,这种表达式写起来费劲,实际工作中我更倾向于用-w抓包后用 Wireshark 过滤http.response.code >= 500,效率更高。tcpdump 的字节偏移过滤更适合在资源受限的环境下做初步筛选。
5. 过滤器组合技巧与性能优化
5.1 用括号控制逻辑优先级
前面提过and和or的优先级问题,这里再强调一次:只要表达式里同时有and和or,就加括号。这不是可选项,是必选项。我见过太多人因为少写一对括号,抓了半天发现数据不对,回头排查才发现是优先级问题。
正确的写法:
tcpdump -i eth0 -nn '(host 10.0.0.1 or host 10.0.0.2) and (port 80 or port 443)'这个表达式抓的是:10.0.0.1 或 10.0.0.2 的 80 或 443 端口流量。逻辑清晰,不容易出错。
5.2 用 not 排除噪音流量
在高流量环境下,排除法往往比包含法更高效。比如你在抓 Web 流量,但 SSH 会话的包一直在干扰:
tcpdump -i eth0 -nn 'tcp and port 80 and not host 10.0.0.100'或者排除整个网段的广播:
tcpdump -i eth0 -nn 'not net 192.168.1.0/24'注意:
not的优先级很高,如果后面跟的是复合表达式,一定要加括号。not host 10.0.0.1 and port 80实际是(not host 10.0.0.1) and port 80,而不是not (host 10.0.0.1 and port 80)。
5.3 过滤器对性能的影响
BPF 过滤器是在内核层面执行的,不匹配的包根本不会拷贝到用户空间。这意味着过滤器写得越精确,tcpdump 占用的 CPU 和内存就越少。在高流量服务器上,一个宽松的过滤器可能导致 tcpdump 自身成为性能瓶颈。
我做过一个简单的对比测试:在千兆网卡上抓包,不加过滤器时 tcpdump 的 CPU 占用率约为 15% 到 20%;加上tcp and port 443之后,CPU 占用率降到 3% 以下。差距非常明显。
所以,永远不要裸奔抓包。哪怕你暂时不确定要抓什么,也至少加一个tcp或udp限定词,把流量降下来。
5.4 常用过滤器速查表
| 需求 | 过滤器表达式 |
|---|---|
| 抓取某主机所有流量 | host 10.0.0.1 |
| 抓取某主机发出的流量 | src host 10.0.0.1 |
| 抓取发往某主机的流量 | dst host 10.0.0.1 |
| 抓取某端口流量 | port 8080 |
| 抓取某网段流量 | net 192.168.1.0/24 |
| 抓取 TCP SYN 包 | tcp[tcpflags] & tcp-syn != 0 |
| 抓取 TCP RST 包 | tcp[tcpflags] & tcp-rst != 0 |
| 抓取 ICMP 包 | icmp |
| 抓取 DNS 流量 | port 53 |
| 抓取 HTTP GET 请求 | tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420 |
| 排除某主机 | not host 10.0.0.1 |
| 抓取两台主机间流量 | host 10.0.0.1 and host 10.0.0.2 |
| 抓取非本网段流量 | not (src net 192.168.1.0/24 and dst net 192.168.1.0/24) |
这张表建议存下来,日常排查时直接查,比每次重新推导快得多。
6. 常见问题与排查技巧实录
6.1 过滤器不生效?先检查引号
这是最常见的问题。如果你的过滤器里包含>、<、&、|等 shell 特殊字符,不加引号会被 shell 解释掉,tcpdump 收到的表达式就不完整了。
错误写法:
tcpdump -i eth0 tcp[13] & 2 != 0shell 会把&解释为后台执行,2和!=和0变成其他命令的参数。正确写法是加单引号:
tcpdump -i eth0 'tcp[13] & 2 != 0'实操心得:我个人的习惯是,所有过滤器表达式一律加单引号,不管里面有没有特殊字符。这样永远不会因为 shell 解释而出错。
6.2 抓不到包?检查接口和方向
如果过滤器写对了但抓不到包,按以下顺序排查:
- 接口是否正确:
tcpdump -D列出所有可用接口,确认你抓的接口有流量经过。比如容器环境下,流量可能走docker0或veth接口,而不是eth0。 - 方向是否正确:如果你写的是
src host 10.0.0.1,但实际流量是从 10.0.0.1 发往其他主机的,那应该能抓到。但如果流量是经过本机转发的,源地址可能不是 10.0.0.1。 - 是否被 VLAN 或隧道封装:如果流量带 VLAN 标签,
host和port过滤器可能匹配不到,需要用vlan限定词。 - 是否走了回环接口:本机进程之间的通信走
lo接口,抓eth0是抓不到的。
6.3 过滤器语法报错?用 -d 参数调试
tcpdump 提供了-d参数,可以把过滤器表达式编译成 BPF 指令码并打印出来。如果表达式有语法错误,-d会直接报错,帮你定位问题:
tcpdump -d 'tcp and port 80'输出是一串 BPF 指令,看起来像汇编。虽然不直观,但至少能确认表达式是否合法。如果-d能正常输出,说明语法没问题,抓不到包就是其他原因。
6.4 常见错误速查表
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
syntax error | 过滤器语法错误 | 用-d检查,确认括号和运算符 |
| 抓不到任何包 | 接口选错 | tcpdump -D列出接口,换接口重试 |
| 抓到的包不完整 | 默认截断长度 262144 字节 | 加-s 0抓完整报文 |
| 过滤器被 shell 解释 | 未加引号 | 表达式加单引号 |
and/or逻辑不符预期 | 优先级问题 | 加括号明确逻辑 |
| 高流量下丢包 | 缓冲区不足 | 加-B增大缓冲区,或写文件 |
| 抓不到 VLAN 流量 | 未处理 VLAN 标签 | 加vlan限定词 |
6.5 一个真实的排查案例
某次线上服务出现间歇性超时,应用日志显示连接数据库偶尔要等 3 秒以上。我在应用服务器上抓包:
tcpdump -i eth0 -nn -s 0 -w db_timeout.pcap 'tcp and host 10.0.0.5 and host 10.0.0.10 and port 3306'抓了 5 分钟,把 pcap 文件拉到本地用 Wireshark 分析。过滤tcp.analysis.retransmission后发现,应用服务器发出的 SYN 包有重传,第一次 SYN 发出后 1 秒才收到 SYN-ACK。继续看发现是中间网络设备的问题,不是数据库本身慢。
这个案例说明:tcpdump 的过滤器帮你把无关流量排除掉,但真正的分析往往需要在 Wireshark 里做更细粒度的过滤。tcpdump 负责“抓得准”,Wireshark 负责“看得细”,两者配合才是完整的工作流。
7. 过滤器与抓包策略的配合
7.1 先粗后细:两阶段抓包法
在实际排查中,我通常分两步走:
第一步,用宽松过滤器快速抓一份样本:
tcpdump -i eth0 -nn -s 0 -c 1000 -w sample.pcap 'tcp'-c 1000表示抓够 1000 个包就停,避免文件过大。拿到样本后用 Wireshark 快速浏览,确认问题流量的特征。
第二步,根据第一步的发现,写精确过滤器重新抓:
tcpdump -i eth0 -nn -s 0 -w target.pcap 'tcp and host 10.0.0.5 and port 3306 and tcp[tcpflags] & tcp-rst != 0'这样既能快速定位问题,又不会抓一堆无关数据。
7.2 环形缓冲区:长时间抓包的利器
如果需要长时间抓包,但磁盘空间有限,可以用-C和-W参数实现环形缓冲区:
tcpdump -i eth0 -nn -s 0 -C 100 -W 10 -w /tmp/capture.pcap 'tcp and port 443'-C 100表示每个文件最大 100MB,-W 10表示最多保留 10 个文件,写满后自动覆盖最旧的文件。这样总占用空间控制在 1GB 以内,适合在服务器上长期运行。
配合精确的过滤器,这个方案可以在不影响业务的前提下,持续捕获关键流量,出问题时随时回溯。
7.3 过滤器表达式的可读性维护
如果你需要把过滤器写进脚本或配置文件,建议把复杂表达式拆成变量,加上注释:
FILTER_HOST="host 10.0.0.5" FILTER_PORT="port 3306" FILTER_PROTO="tcp" FILTER_FLAGS="tcp[tcpflags] & tcp-rst != 0" tcpdump -i eth0 -nn -s 0 -w output.pcap "$FILTER_PROTO and $FILTER_HOST and $FILTER_PORT and $FILTER_FLAGS"这样下次修改时一眼就能看出每个部分的含义,不用重新推导整个表达式。我吃过这个亏——半年前写的一个复杂过滤器,半年后回头看完全看不懂,只能重新写一遍。
8. 从过滤器到分析:抓完之后做什么
抓包只是第一步,过滤器帮你拿到干净的数据,接下来的分析才是重头戏。几个常用的后续操作:
- 用 tshark 做命令行分析:
tshark -r capture.pcap -Y 'tcp.analysis.retransmission',直接过滤出重传包。 - 用 capinfos 看抓包统计:
capinfos capture.pcap,快速了解抓包时长、包数量、平均速率。 - 用 editcap 切割大文件:
editcap -c 10000 capture.pcap split.pcap,把大文件切成小份,方便逐个分析。
这些工具和 tcpdump 配合使用,能覆盖从抓包到分析的完整链路。tcpdump 的过滤器是入口,把好入口这一关,后面的分析效率会高很多。
我个人在实际操作中的体会是:过滤器写得好不好,直接决定了排查效率。同样一个问题,过滤器写得精确,可能十分钟就定位到原因;过滤器写得粗糙,抓了一堆无关数据,光筛选就要花半小时。花五分钟研究一下过滤器语法,省下的是后面几十分钟的无效劳动。这个投入产出比,怎么算都划算。