tcpdump 与 Wireshark

本页目录

抓包保存的是某个观察点、某段时间内采集到的报文。tcpdump 适合按条件采集和离线检查,Wireshark/TShark 适合协议字段、重组与时序分析。可靠结论来自“采集范围、协议事实、推断边界”三者一致,而不是某行被标红。

先回答的问题对采集的影响
哪一段连接失败客户端、代理入口、代理出口可能需要不同采集点
用 TCP 还是 UDP只抓 TCP 443 会漏掉 QUIC
是否涉及 DNS只按目标 IP 抓取可能看不到前面的解析失败
要查握手还是内容决定是否提前开始、是否需要完整 snaplen 与解密材料
是否能完整保存决定过滤范围、缓冲、文件轮换与停止条件

观察位置决定你能证明什么

同一次访问,不同观察点看到不同连接与报文形态 客户端 应用 / socket 代理或 NAT 终止连接或改写地址 后端服务 另一段连接 主机抓包可能位于卸载之前 代理后五元组可能变化 先记录接口、namespace、方向和时间基准 看不到包只能说明此处未采到;需要排除路径不同、过滤、截断和采集丢包。

Linux 宿主机、容器网络 namespace、veth、bridge、物理网卡和 loopback 不是同一个视角。-i any 覆盖的是当前 namespace 可采集的接口,不会自动进入所有容器。Linux 的 any 通常使用 cooked capture 链路类型,不应假定每个包都从标准以太网头开始解析。

经过 NAT 后地址和端口可能变化;经过反向代理则通常已是另一条 TCP 连接,不能沿用相同 tcp.stream 或五元组找后端。跨文件关联需结合时间、请求 ID、有效载荷特征和代理日志;各文件的 stream 编号只是本次分析结果中的索引。

两端同时抓包能缩小缺失发生的位置,但时钟未同步时,直接相减时间戳会产生错误的单向时延。微秒或纳秒精度的时间戳也不保证同等级测量准确度。

Capture filter 与 display filter 分别在哪生效

属性Capture filterDisplay filter
常见使用处tcpdump 表达式、TShark -fWireshark 过滤栏、TShark -Y
作用对象采集前筛选或离线读取筛选已有报文经过解析后的字段
示例tcp port 443tcp.port == 443
未匹配数据实时采集时通常不写入捕获文件原文件仍保留,可换条件重新分析
能力边界依赖链路类型、偏移与编译器支持依赖 dissector、重组和可见明文

实时采集中,libpcap 可在支持的平台将过滤程序交给内核,减少送入用户态的包;读取离线文件时也能使用相同表达式,但并不是“所有 BPF 过滤永远发生在内核”。没有给定流量、匹配比例与解码开销时,也不能宣称固定快几个数量级。tcpdump 手册源码

常用捕获条件

# 以下是过滤表达式,传给 tcpdump 时作为一个带引号的参数。
# 双向涉及目标,覆盖 TCP 和 UDP 443:
tcpdump -nn -r capture.pcap 'host 192.0.2.10 and port 443'

# 仅 IPv4 中 SYN 置位且 ACK 未置位;允许其他协商标志同时存在:
tcpdump -nn -r capture.pcap 'ip and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'

# 两个目标之一,且为 TCP 443:
tcpdump -nn -r capture.pcap '(host 192.0.2.10 or host 192.0.2.20) and tcp port 443'

tcp[tcpflags] == tcp-syn 是标志字节严格相等,可能排除同时带其他协商标志的 SYN;按位掩码才准确表达“有 SYN、无 ACK”。原始传输头偏移表达式在 IPv6 扩展头等场景有额外限制,因此这里显式限定 IPv4,不能当成全协议通用建连统计。

端口条件不等于应用识别:443 可以承载不同协议,DNS 加密流量也不会都在 53。非首片 IP 分片没有原始传输层端口,过窄端口过滤可能让重组缺片;VLAN 和隧道封装同样影响偏移和匹配范围。pcap-filter 语法源码

换显示过滤器,能找回没抓到的包吗?

先用严格等值采集,再显示所有,只能看到一个包。改用掩码重新采集才会保存两种SYN。修改采集下拉框本身也不会改写已经保存的文件,必须明确执行新采集。

正在呈现知识画面
换显示过滤器,能找回没抓到的包吗?

显示过滤只能在已保存集合中筛选。严格SYN等值会漏掉同时置ECE/CWR的SYN,事后显示全部也不能补回。

可控地采集,再离线分析

以下为实际排查时按环境调整的命令。网卡名和目标地址需要替换,文件位置应保证可写;采集接口通常需要相应权限,读取保存文件一般不需要。

# 最多处理 5000 个匹配包;-c 限包数,不限制等待时间。
tcpdump -i eth0 -nn -s 0 -B 4096 -c 5000 \
  -w capture.pcap 'host 192.0.2.10 and port 443'

# 按容量循环保存 5 个文件,-C 100 的基础单位是十进制百万字节。
tcpdump -i eth0 -nn -s 0 -C 100 -W 5 \
  -w trace.pcap 'host 192.0.2.10'

# 只读,不启动抓包。
tcpdump -nn -tttt -r capture.pcap

-s 0 使用工具所定义的最大/default snaplen,不是数学上的无限长度;检查文件内的 captured length 与 original length,才能知道某个包是否被截断。-B 的单位是 KiB。-C 轮换可能稍超过阈值,不能当成精确磁盘上限;与 -W 的组合会循环覆盖旧文件。按时间的 -G 与计数选项有不同交互,不应直接照搬容量轮换逻辑。

结束时记录 captured、received by filter 和 dropped by kernel。后两者的口径依赖操作系统;dropped by kernel 表示采集机制的缓冲丢弃,不等于业务网络实际丢包数。即使显示 0,也不保证镜像口、驱动或其他位置没有漏采。

一个粗略存储预算:平均每秒 20,000 个包、平均保留 800 B,经典 pcap 每记录另有 16 B 头,60 秒约 20,000 × 816 × 60 = 979,200,000 B,即 979.2 MB。该估计未计文件系统开销,也不适用于直接套算 pcapng 的所有块结构。

先定位流,再看重组与方向

# TCP conversation 汇总;-q 减少逐包输出。
tshark -r capture.pcap -q -z conv,tcp

# 选择同一个捕获文件中的 stream 3,并导出关键字段。
tshark -r capture.pcap -Y 'tcp.stream == 3' -T fields \
  -e frame.number -e frame.time_relative -e ip.src -e tcp.srcport \
  -e tcp.seq -e tcp.ack -e tcp.len -e tcp.window_size

# 找候选异常,随后回到完整流上下文。
tshark -r capture.pcap \
  -Y 'tcp.analysis.retransmission || tcp.analysis.zero_window'

IPv6 分析应同时选择 ipv6.src 等字段。没有握手时,窗口缩放等协商信息可能未知;字段空白也可能因为本包不含该层,而不是数值为 0。TShark 手册

ip.addr == A 可匹配任一方向的地址。若想排除所有涉及 A 的包,用 !(ip.addr == A) 明确表达,避免在多值字段上凭直觉推导不等操作的语义。显示过滤不会重新补回采集时丢掉的字节。显示过滤器

HTTP 头可能跨 TCP segment,压缩或加密后也未必是可直接搜索的文本。tcpdump -A | grep Host: 只能找到某些单包里的可见片段,不是可靠的 HTTP 请求解析器。使用协议重组和 Follow Stream,并明确捕获是否从连接开始。

“重传”提示是一项基于可见包的分析

序号缺口:先区分路径丢包与抓包缺失 发送端 采集点 接收端 seq=1001,len=1000 seq=2001,len=1000(此点没采到) ACK=3001 接收端确认到了 3001,不能据中间抓包缺口直接判定漏传 这是教学假设;真实判断还需检查完整双向流、重复采集、序号回绕和分析设置。
提示或现象先解释成什么继续核对
Previous segment not captured当前可见序号前有缺口抓包是否中途开始、漏采或过滤过窄
Retransmission / Out-of-order可见序号与已有流状态出现重复或乱序关系双向 ACK、时序、重复采集和分析设置
ZeroWindow该报文发送者通告接收窗口为零这一端应用读取与接收缓冲状态
Bad checksum当前采样字节计算结果不符或验证条件不足本机发送卸载、截断及抓包位置
大于常规 MTU 的“包”主机观察到一个较大的处理单元TSO/GSO/GRO/LRO 是否参与

Wireshark 的 TCP 分析依赖捕获包的次序和可见历史,其分类可以有歧义,不能把每项提示都当成对端 TCP 实现直接报告的事实。TCP Analysis

发送端抓包可能发生在网卡补校验和、分段之前;接收端也可能观察到聚合后的数据。此时错误校验和或“大包”不必然出现在线路上。若要确认真实线上形态,应比较接收端或外部观察点;关闭卸载会改变性能与行为,不应成为看到提示后的默认第一步。Wireshark Offloading

TLS 解密需要正确会话秘密,不是只填一个变量名

SSLKEYLOGFILE 是部分客户端和 TLS 库支持的密钥日志接口,并非操作系统能强制所有程序输出秘密的通用开关。程序必须实际支持、启动时读取配置并产生与捕获会话匹配的日志。

在 Wireshark 的 TLS 协议设置中指定日志文件,或离线使用:

tshark -r capture.pcap -o tls.keylog_file:session.keys -Y http

解密后协议不一定是 HTTP/1,HTTP/2 等需要相应过滤器。只有服务端证书私钥,不能解密采用前向安全交换的 ECDHE 会话或通常的 TLS 1.3 会话;历史 RSA 密钥交换的私钥解密有严格适用条件。握手、记录或日志缺失也会妨碍重组和解密。Wireshark TLS

密钥日志与对应抓包合在一起会暴露该会话内容,应按所含会话数据管理访问和保留范围。解密后的可见内容仍取决于应用是否还有独立的端到端加密。

离线验证过滤语义:两种 SYN 和一个 ACK

下面生成一个临时 Ethernet/IPv4/TCP pcap,用已安装的 tcpdump 读取并过滤,不连接接口、不发送报文。三个包分别是 SYN、SYN+ECE+CWR、ACK;精确等于 SYN 只能匹配一个,掩码条件应匹配两个。

import ipaddress
import struct
import subprocess
import tempfile
from pathlib import Path

def checksum(data):
    if len(data) % 2: data += b'\0'
    total = sum(struct.unpack('>' + 'H' * (len(data) // 2), data))
    while total >> 16: total = (total & 0xffff) + (total >> 16)
    return (~total) & 0xffff

src = ipaddress.ip_address('192.0.2.1').packed
dst = ipaddress.ip_address('192.0.2.10').packed
packets = []
for flags in [0x02, 0xc2, 0x10]:
    tcp = struct.pack('>HHIIBBHHH', 40000, 443, 1000, 0, 0x50, flags, 65535, 0, 0)
    pseudo = src + dst + struct.pack('>BBH', 0, 6, len(tcp))
    tcp = tcp[:16] + struct.pack('>H', checksum(pseudo + tcp)) + tcp[18:]
    ip = struct.pack('>BBHHHBBH4s4s', 0x45, 0, 40, 1, 0, 64, 6, 0, src, dst)
    ip = ip[:10] + struct.pack('>H', checksum(ip)) + ip[12:]
    ethernet = bytes.fromhex('0200000000020200000000010800')
    packets.append(ethernet + ip + tcp)

with tempfile.TemporaryDirectory() as folder:
    path = Path(folder) / 'flags.pcap'
    output = struct.pack('<IHHIIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
    for i, packet in enumerate(packets):
        output += struct.pack('<IIII', 1700000000 + i, 0, len(packet), len(packet))
        output += packet
    path.write_bytes(output)
    filters = [
        ('ip and tcp[tcpflags] == tcp-syn', 1),
        ('ip and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn', 2),
        ('tcp port 443', 3),
    ]
    for expression, expected in filters:
        result = subprocess.run(['tcpdump', '-nn', '-r', str(path), expression],
                                text=True, capture_output=True, check=True)
        assert len(result.stdout.strip().splitlines()) == expected
print('离线 pcap:严格 SYN 1 个,掩码 SYN 2 个,443 共 3 个')

这验证的是捕获表达式与包编码的匹配,不是性能测试,也没有模拟完整 TCP 会话。真实排查应把原始文件、采集命令、工具版本、采集计数和关键帧号一起记录,使结论可以复查。