TcpDump 使用笔记

文章目录


一、tcpdump 是什么

  • 本质 :一个基于 libpcap 的命令行抓包工具,能够捕获并解析网络接口上收发的数据包。
  • 核心原理
    • 将网卡设置为混杂模式,接收所有流经该接口的数据包(不限于发给本机的包)。
    • 内核层 使用 BPF (Berkeley Packet Filter) 过滤规则,只把用户关心的包复制到用户态,极大减少性能开销和丢包。
  • 与后端开发的关系:当我们怀疑网络问题,但代码和日志查不出时,它就是最终的"证据"------可以看到三次握手是否完成、HTTP请求体是否发送完整、对端是否回了RST等。

二、快速安装

bash 复制代码
# Debian/Ubuntu
sudo apt-get install tcpdump
# CentOS/RHEL
sudo yum install tcpdump

注意 :运行 tcpdump 通常需要 root 权限,或给用户赋予 CAP_NET_RAW 能力。

macOS 用户注意 :macOS 自带的 tcpdump 与 Linux 下的行为基本相同,但在接口名称、权限模型和默认捕获长度上略有差异。例如,无线网卡通常是 en0,且需要 sudo;默认快照长度可能更大,但为了安全仍建议使用 -s 0。此外,macOS 中 -i any 不受支持,必须指定具体接口。


三、命令核心结构

bash 复制代码
tcpdump [选项] [过滤表达式]

选项控制显示和抓取行为,表达式决定抓什么。


四、必知必会的选项

选项 作用 说明
-i eth0 指定监听的网络接口 任何接口用 -i any;回环接口用 -i lo
-n 不解析主机名 直接显示 IP,避免 DNS 查询干扰和延迟
-nn 不解析主机名和端口名 直接显示 127.0.0.1.8080,不显示 localhost.hobart
-v / -vv / -vvv 详细程度 -v 多 TTL/ID;-vv 多窗口/选项;-vvv 极其详细
-c 100 抓取 100 个包后自动停止 避免在生产环境无限抓取
-w file.pcap 把原始包保存到文件 不解析内容,直接写二进制,效率高
-r file.pcap 从文件中读取包并解析 相当于离线分析,方便反复筛选
-s 0 抓取完整数据包,不做截断 默认可能只抓 68/96 字节,对分析应用层数据必须用 -s 0
-X 同时以十六进制和 ASCII 显示包内容 调试自定义二进制协议时最常用
-A 只以 ASCII 文本显示包内容 适合抓 HTTP 请求这样的文本协议
-e 显示链路层头部信息 (MAC 地址) 排查 ARP 或交换机问题时用
-t 不显示时间戳 精简输出
-tttt 显示人类可读的日期时间 2026-08-11 10:30:00.123456

五、核心:BPF 过滤表达式 (重中之重)

过滤表达式可看成组合多个原语 ,用 andornot 连接,支持括号分组。

1. 常用原语

  • 主机host 10.0.0.5src host 10.0.0.5dst host 10.0.0.5
  • 端口port 80src port 8080dst port 3306
  • 端口范围portrange 8000-8100
  • 网络net 192.168.1.0/24
  • 协议tcpudpicmparp
  • 指定 TCP 标志位 :(常用)
    • 只抓 SYN 包(连接建立第一个包):'tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) == 0'
    • 只抓 SYN+ACK 包:'tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) != 0'
    • 只抓 RST 包:'tcp[tcpflags] & (tcp-rst) != 0'
  • 包长度greater 100 (包长大于 100 字节),less 64

2. 组合示例

bash 复制代码
# 来自10.0.0.5且目标端口80的TCP包
tcpdump -nn 'src host 10.0.0.5 and tcp dst port 80'
# 主机A和主机B之间的所有UDP流量
tcpdump -nn 'host 10.0.0.5 and host 10.0.0.6 and udp'
# 不是SSH(22)端口的流量,括号和引号很重要
tcpdump -nn 'not port 22'
# 抓取发往本机8080端口,且包长度大于500的SYN包
tcpdump -nn 'tcp dst port 8080 and greater 500 and tcp[tcpflags] & tcp-syn != 0'

六、输出格式精读

一条典型的 TCP 输出行:

text 复制代码
08:10:20.123456 IP 192.168.1.10.54321 > 192.168.1.20.8080: Flags [S], seq 1000, win 65535, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0

分解如下:

  • 时间戳08:10:20.123456
  • 协议IP
  • 源与目标源IP.源端口 > 目标IP.目标端口
  • Flags :这是重点
    • [S]:SYN (连接请求)
    • [S.]:SYN+ACK (连接应答)
    • [.]:ACK (确认,单个点)
    • [P.]:PSH+ACK (数据推送)
    • [F.]:FIN+ACK (连接结束请求)
    • [R] / [R.]:RST (重置连接)
  • seq :当前包序列号,seq 1000
    • 后续包如果带数据,会显示 seq 1001:1500,表示该包携带了序号1001到1499的字节。
  • ack:期望收到的下一个序列号,仅当ACK标志置位时显示。
  • win:窗口大小,流量控制依据。
  • options :TCP选项,如 mss 最大报文段长度、wscale 窗口缩放因子等,排查窗口问题时很有用。
  • length :有效载荷长度,此处的0代表纯TCP头(如SYN包)。

七、C++后端实战场景

1. 最简单的调试:本机回环请求

调试本机两个微服务(如一个C++服务调用另一个的8080端口):

bash 复制代码
tcpdump -i lo -nn 'port 8080'

我们会看到请求和响应包流,立即能判断是请求没发出,还是对端没回应。

2. 确认完整HTTP请求/响应

抓取并显示整个HTTP请求内容(文本):

bash 复制代码
tcpdump -i eth0 -nn -A -s 0 'tcp port 80 and host 10.0.0.5'

-A 会打印出 GET /api/... 和 HTTP头,甚至响应体。如果乱码,记得加上 -X 看十六进制。

3. 排除健康检查或心跳噪声

我们的服务端口是8080,但编排系统每分钟都来健康检查(来自固定IP 10.0.0.1),想忽略它:

bash 复制代码
tcpdump -nn -i eth0 'port 8080 and not src host 10.0.0.1'

4. 分析 TCP 连接生命周期

抓取一次完整的 TCP 流(只抓握手和挥手包):

bash 复制代码
tcpdump -i eth0 -nn 'tcp port 8080 and (tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0)'

观察有没有大量 RST 返回,或者挥手过程缺了 FIN。

5. 调试自定义二进制协议

-X 打印十六进制和ASCII内容,并保存为 pcap 文件,再用 Wireshark 写 C++ 一样的协议解析器(自定义 Lua/C 插件)去分析。

bash 复制代码
tcpdump -i eth0 -w debug.pcap -s 0 'port 33000'

6. 确认客户端真实IP是否可达

C++ 后端获取到客户端IP后,要反连客户端提供某些服务?先抓包看看有没有来自该 IP 的包到达网卡:

bash 复制代码
tcpdump -i eth0 -nn 'host 1.2.3.4'

7. 观察重传和窗口问题

bash 复制代码
tcpdump -i eth0 -nn -vv 'port 8080 and (tcp[tcpflags] & tcp-syn == 0)'

加上 -vv 可以清楚看到窗口大小变化、是否有重传 (seq 1000 重复出现) 等现象。


八、进阶技巧

  • 循环保存文件-W 5 -C 100 -w file.pcap 会循环使用5个文件,每个最大100MB,避免撑爆磁盘。
  • 精细化时间戳 :在 -tttt 的基础上,某些版本支持 --time-stamp-precision=nano 获得纳秒时间。
  • 配合 Wireshark 解密 TLS
    tcpdump 本身不解密,但可以设置环境变量 SSLKEYLOGFILE=~/sslkeylog.txt 并启动我们的 C++ 程序(程序需使用 OpenSSL/BoringSSL 等且不覆盖该钩子),然后用 tcpdump 抓包,在 Wireshark 中导入该密钥文件即可看到明文。这对调试 C++ 写的 HTTPS/gRPC 服务至关重要。
  • 管道直接输出给其他工具
bash 复制代码
tcpdump -i eth0 -w - not port 22 | nc remote_host 12345  
复制代码
可以把包流式传输到远端分析(小心带宽)。
  • 结合 strace :若怀疑 C++ 程序在系统调用层就有问题,可以 strace -e trace=network ./your_server 的同时,抓包对比 tcpdump 的结果,确认数据是在发送前还是发送后出错。

九、注意事项(后端程序员要避的坑)

  1. 生产环境抓包务必谨慎
    • 一定要加严格过滤器,例如限定端口和主机。
    • 使用 -c 限制数量,或用 -W -C 限制文件大小。
    • 大量抓包会占用 CPU,且磁盘 I/O 可能影响服务响应。
  2. 注意截断-s 0 是好习惯,否则应用层数据不全,我们很可能误判。
  3. 权限问题
    • 标准用户必须使用 sudo
    • 在容器 (Docker) 中,默认可能无法使用 tcpdump,需要赋予 NET_ADMIN 能力。
  4. 时间同步
    • 对比多台机器抓的包时,务必确保机器时间已经 NTP 同步,否则顺序错乱。
  5. 接口选择
    • -i any 会抓所有接口,但无法用于向 pcap 文件写入(某些系统),且混杂模式表现可能不同。调试具体服务时,明确指定 -i eth0-i lo 更好。
  6. 区分进出
    • 过滤器 srcdst 是相对于监控的接口而言。如果我们在服务端机器上抓 -i eth0,进来的请求是 dst host 本机IP,出去的响应是 src host 本机IP

十、什么时候会真正打开 tcpdump(感悟与场景)

有一次,我在写一个个人项目,一个简单的 TCP 服务在测试时突然开始间歇性拒绝连接。端口监听正常,但总有大约 5% 的新连接直接失败,客户端收到 "Connection reset by peer"。查看应用日志,发现只有少数连接建立成功,大部分请求根本没进入业务逻辑。我检查了文件描述符限制、线程池状态、系统内存,一切正常。那种感觉就像程序在闹鬼。

后来我在服务器上跑了这样一条命令:

bash 复制代码
tcpdump -i eth0 -nn 'tcp port 8080 and (tcp[tcpflags] & (tcp-syn|tcp-rst) != 0)'

不到 10 秒,谜底揭晓:每个失败的连接都是三次握手完成之后,服务端立刻回了一个 RST 包。进一步对比正常连接发现,这些 RST 均来自一个特定的工作线程,而那个线程刚好在那几分钟内因为一个死锁卡住了 accept 调用。它没有崩溃,端口也还在监听,但新连接分配到它时,它根本无法处理,内核替它回了 RST。日志之所以干净,是因为连接根本没走到应用层。没有 tcpdump,我可能还在傻傻地调内核参数。

这次经历让我们深刻体会到:当所有日志都沉默时,网络包不会撒谎。

什么时候该请出 tcpdump

作为 C++ 后端开发,我们打交道最多的是网络编程(socket、epoll、TCP 连接、自定义协议、HTTP/gRPC 等)。下面这些情况,tcpdump 会直接派上用场:

  1. "我明明发了数据,对端说没收到"

    • 我们调用 send() 成功了,对端却说没收到包。
    • 这时打开 tcpdump,一看便知:数据包到底出没出本机网卡?如果抓到了发出的包,说明是网络或对端的问题;如果没抓到,说明我们的程序根本没发出去,或者被本机防火墙拦截了。
  2. TCP 连接异常断开(突然被 RST)

    • 线上服务日志频繁打印 "Connection reset by peer"。
    • 我们抓包看到对端回了一个 RST 标志的包,结合上下文可能发现:是对端进程重启了?还是请求了一个已经关闭的端口?或者防火墙发了 RST?没有 tcpdump 我们只能猜。
  3. 怀疑粘包/拆包问题

    • 我们写了一个自定义二进制协议,接收端有时解析出错。
    • tcpdump -X 抓下数据包的实际十六进制内容,就能看到 TCP 流里,我们的数据是不是被分成了好几个包发出,或者粘在一起,从而验证我们的封包解包逻辑是否正确。
  4. 延迟突然变高,但代码里没加计时器

    • 服务间调用从 5ms 变成了 200ms,不知道是网络问题还是业务逻辑变慢。
    • tcpdump 抓包并保存为 pcap 文件,用 Wireshark 打开,可以直接看到每个请求和响应之间的时间差,精确定位是网络传输慢还是服务器处理慢(三次握手的时间、请求发出到第一个响应字节的时间,都一目了然)。
  5. 学习 TCP 协议本身时

    • 自己写一个简单的 socket 程序,故意不调用 close() 就退出,然后用 tcpdump 观察对端是否收到 RST。
    • 调整系统 tcp_keepalive 参数,抓包验证 keep-alive 探测包是否如预期发出。
    • 这些动手实验能让我们对 TCP 状态机、三次握手四次挥手的理解"刻进骨头里"。
  6. 容器/微服务排查

    • 服务部署在 Docker 里,curl localhost:8080 超时。可能是端口没映射?进程没监听?还是 iptables 规则拦截了?
    • tcpdump -i any port 8080 能直接看到 SYN 包有没有到达网卡,从而准确判断问题层面。

为什么现在觉得"用不到"

  • 我们现在多数是在本机写代码,简单的客户端/服务端通信,一切都在掌控中。
  • 真正的网络问题往往出现在多机协作、复杂网络环境、高并发或生产环境下,这些问题在个人学习阶段较少遇到。
  • tcpdump 是一个"事后侦探",不像 GDB 调试那样需要我们主动介入,它是网络真出"悬案"时才请出来的。

一个简单的比喻

  • printf 日志 相当于我们装在家里的监控,平时记录日常。
  • tcpdump 相当于我们在房子周围装的高精度传感器,只有怀疑有人偷东西(网络丢包、攻击)或者电路异常(TCP 断开)时,我们才会去看它的详细记录。

现在可以做什么,让以后用起来更顺畅?

即使暂时用不到,也可以提前做一次"排练":

  1. 用 C++ 写一个简单的 TCP echo 服务器和客户端。

  2. 在本机运行,然后开一个终端执行:

    bash 复制代码
    sudo tcpdump -i lo -nn port 你的端口
  3. 观察连接建立时的 SYNSYN+ACKACK,发送数据时的 PSHACK,以及关闭时的 FIN 序列。

这样一次亲手实践,比看十次笔记都管用,而且会让我们对以后排查网络问题充满信心。

等到那天我们真面对一个顽固的线上网络故障,其他人都一筹莫展时,我们默默敲下 tcpdump 命令,看到那个关键的 RST 包,一切豁然开朗------那种感觉,就是后端工程师的浪漫。

tcpdump 的"盲区"

也要知道 tcpdump 看不到什么:

  • 本机防火墙 (iptables) 影响:tcpdump 的抓包点位于网络协议栈中较早期的 AF_PACKET 层,直接捕获网卡收发的原始帧。这意味着,即使 iptables 后续在内核中将包丢弃,tcpdump 通常还是能抓到这些包(因为捕获发生在过滤之前)。不过,对于本地发出的某些包,不同 hook 点的处理顺序可能导致盲区,但绝大多数情况下,我们能"看到"被防火墙拦掉的包。这一点在排查"明明对方说发过来了,但我的服务没收到"时格外有用------如果 tcpdump 上都没出现这个包,那问题基本不在本机防火墙,而是更上游的网络或对端。
  • 应用层协议细节 :tcpdump 能显示原始字节,但如果我们不熟悉 HTTP 协议格式,我们只能看到乱码,还需要用 -A 或 Wireshark 解析。
  • 浏览器渲染过程:完全在 tcpdump 视野之外。

十一、关于业务日志和tcpdump是否冲突?

这是一个非常好的问题,说明我们开始思考不同工具在开发流程中的定位了。

答案很明确:不冲突,它们是不同层面的东西,解决的是完全不同的问题。

我们可以把它们理解为:业务日志是我们的"自述",tcpdump 是"监控录像"。 一个是"我说我做了什么",一个是"实际发生的客观事实"。

一张表看清区别

维度 业务日志 (我们在代码里写的 printf/LOG) tcpdump (网络抓包)
负责回答的问题 "程序逻辑是否正确?" 为什么请求被拒绝了?这个变量值是多少?代码走到这行了没? "网络通信是否发生?" 包发出去了吗?对方回了吗?连接断了吗?
视角 程序内部视角,只记录程序自己认为发生过的事情。 操作系统/网络视角,记录网络上真实流过的比特。
谁"写"的 我们写的 LOG_INFO("用户搜索: %s", keyword) 内核的网络协议栈和网卡驱动。
它"说谎"吗 。程序在调用 send() 前崩溃了,日志就不会有这条记录;或者我们的 if/else 分支根本没进,导致日志逻辑被跳过。 基本不会。只要包经过了网卡,它就能抓到。它代表了物理层真实发生的通信。
关联性 我们有意识地把它和业务逻辑绑在一起,是"业务状态"的体现。 完全不懂业务,只看 IP、端口、TCP 标志位和原始字节。

为什么必须两者共存?三个经典场景告诉我们

场景一:日志说发了,tcpdump 说没发 (互斥时,tcpdump 更权威)
  • 我们的日志[INFO] 成功发送 256 字节响应给客户端
  • 实际情况 :程序在调用 write() 之前就宕机了,或者我们的 LOG 语句放在 write() 之前,而 write() 本身失败了但我们没检查返回值。
  • tcpdump 的作用 :我们在线上抓包,根本没看到服务器发出的响应包。铁证如山,是我们的程序发送逻辑出了问题。 日志骗了我们。
场景二:日志没记,但 tcpdump 抓住了 (日志有盲区)
  • 现象:服务器频繁告警 "Connection reset by peer",但我们的日志里找不到任何对应客户端的请求记录。
  • tcpdump 的作用 :抓包一看,某个客户端 IP 发来了一个 RST 包,直接重置了连接。也许是我们设置的 keepalive_timeout 太短,或者是中间防火墙干的。这些底层的网络事件,我们的业务日志根本感知不到,但 tcpdump 一清二楚。
场景三:互相印证,缩小范围 (组合拳)
  • 问题:前端说请求超时。
  • 我们的排查步骤
    1. 看日志[NORMAL] 用户搜索:boost → 说明业务层收到了请求,代码逻辑执行到了。
    2. 看 tcpdump :抓到 [P.] 响应包,length 和我们生成的响应体大小一致。→ 说明响应也成功发出了。
    3. 结论问题不在后端。 立刻去前端或反向代理层排查。
  • 这背后是我们的决策逻辑
    • 如果日志有,抓包无 → 后端发送环节出 Bug。
    • 如果日志无,抓包有 → 请求进来了,但后端处理异常 (崩溃/未进分支)。
    • 如果日志有,抓包有 → 后端工作完美,问题在别处。

核心思想:tcpdump 是用来否决我们的日志,或者为我们的日志背书的

它不是用来替代日志的。我们永远会先看日志,因为日志有业务上下文。

  • tcpdump 是最后的测谎仪 。当故障原因扑朔迷离,各方开始"甩锅"------前端说后端没返回,后端说代码没问题,运维说网络不通时------tcpdump 抓出来的二进制数据包是唯一不会说谎的、端到端的证据
  • 日志是我们业务系统的内脏监控,告诉我们消化好不好。
  • tcpdump 是马路上装的监控摄像头,告诉我们人到底进没进门,出没出门。

所以,我们写业务时的日志不仅没有和 tcpdump 冲突,而且我们正在建立一种非常专业的后端思维:用业务日志监控内部状态,用 tcpdump 监控外部通信,两者一合,天下无敌。

相关推荐
程序猿编码1 小时前
不用PyTorch,不用CUDA,我用C++手写了一个能跑GPT和Whisper的推理库
c++·pytorch·gpt·大模型·whisper
疯狂打码的少年1 小时前
【数据结构】队列的应用:循环队列
数据结构·笔记·算法
MC皮蛋侠客1 小时前
TDengine C++ 系列(1):全景与最小闭环——从时序数据到第一个 C++ 读写
大数据·c++·tdengine
跳跃的芋头人2 小时前
RefactoringUI 阅读笔记1 - 从草稿开始
笔记
是上好佳佳佳呀2 小时前
【深度学习|DAY03】神经网络深度学习笔记(上):框架总览、参数初始化与激活函数
笔记·深度学习·神经网络
MC皮蛋侠客2 小时前
TDengine C++ 系列(5):C/C++ 连接器——连接管理与查询 API
c语言·c++·tdengine
探物 AI2 小时前
yolo检测中的激活函数19:ReLU激活函数 (Rectified Linear Unit)
网络·人工智能·深度学习·yolo
MC皮蛋侠客2 小时前
TDengine C++ 系列(8):流式计算与最新值缓存——库内实时处理
c++·缓存·tdengine
zhangrelay2 小时前
ROS 2 Lyrical 第2章 ROS 2系统架构与核心概念
linux·笔记·学习·ubuntu·ros2