☰
pcap文件格式
2026/9/25 21:41:28 网站建设 项目流程

PCAP 文件格式详解:从字节布局到逐层解析

一.前言

pcap 有两种常见格式,一个是经典 pcap(.pcap,libpcap/tcpdump 的老格式),另一个是pcapng(.pcapng,Wireshark 默认的新格式),本文章描述的是经典 pcap,为后面的pcap 解析器——网络抓包项目打下基础,但我们还是简单的说一下这两种格式的背景。

为什么会有两种格式?

  • pcap(经典格式):由 libpcap / tcpdump 在 1990 年代定义,长期是事实标准。结构极简,一个文件只能记录一种链路类型。
  • pcapng(PCAP Next Generation):为弥补 pcap 的局限而生——支持多网卡接口、每接口独立的时间戳精度、注释与元数据、多段拼接。Wireshark 现在默认存 pcapng,但 libpcap 也早已能读写(1.8+ 读、1.9+ 写)。

所以拿到一个抓包文件,第一步永远是看前 4 字节:

前 4 字节格式
a1 b2 c3 d4pcap,微秒时间戳
a1 b2 3c 4dpcap,纳秒时间戳
0a 0d 0d 0apcapng(SHB 块)
1f 8bgzip 压缩过的抓包文件(先解压)
其他不是抓包文件

二.pcap概念

pcap 文件就是一串抓到的原始链路层字节,每条前面加 16 字节的"什么时候抓的、抓了多少"的小标头,整份文件开头再加 24 字节的"这份文件是哪种链路、什么精度"的说明。它没有任何压缩和加密,文件大小 ≈24 + 16 × 包数 + 所有包字节数。

三、pcap整体结构

┌──────────────────────────────────────────┐ │ 全局头 Global Header 固定 24 字节 │ ← 整份文件只有 1 个 ├──────────────────────────────────────────┤ │ 记录头 Packet Header 固定 16 字节 │ │ 包数据 Packet Data incl_len 字节 │ ← 第 1 个包 ├──────────────────────────────────────────┤ │ 记录头 16 字节 + 包数据 … │ ← 第 2 个包 ├──────────────────────────────────────────┤ │ … 一直到文件末尾(EOF) │ └──────────────────────────────────────────┘

注意:文件里没有"包总数"字段,也没有"文件总长"字段。只能一条一条顺序往下读,靠"读不下去"来判断结束;所以文件尾部被截断是常见现象,解析器必须容忍。

四、全局头(24 字节)

偏移长度字段类型含义
04magic_numberu32魔数,同时用来说明字节序和时间戳精度
42version_majoru16主版本号,通常2
62version_minoru16次版本号,通常4
84thiszonei32GMT 与本地时间差,历史遗留,恒为 0
124sigfigsu32时间戳有效位数,历史遗留,恒为 0
164snaplenu32抓包时最多截取多少字节,常见 65535 / 262144
204networku32链路层类型(LinkType),1 = 以太网

4.1 魔数:同时表示字节序和时间戳精度

它的首要职责是判断字节序,但同一个 32 位值还兼任"时间戳精度"标记。原因是:纳秒版是在原始格式上改了一位扩展出来的——把0xa1b2c3d4改成0xa1b23c4d(c3d4→3c4d),既和其他格式区分得开,翻转后的形态(0x4d3cb2a1)也唯一可辨。

下面这张表以"我现在的 x86(小端)机器"为前提:

文件开头 4 字节写入方字节序时间戳精度我这台机器要翻转吗
d4 c3 b2 a1小端微秒不用
a1 b2 c3 d4大端微秒要
4d 3c b2 a1小端纳秒(libpcap 1.5+ 支持)不用
a1 b2 3c 4d大端纳秒要

翻转与否,只取决于"文件字节序"和"读取方字节序"是否相同。读取方是 x86(小端),所以规则就是"小端文件不翻、大端文件翻"。

4.2 snaplen:截断从哪来

抓包时如果每个包都完整保存,磁盘和内存会吃不消(尤其大包和突发流量)。snaplen就是"每个包最多存这么多字节",超出部分直接丢弃。于是:

  • 记录头里的incl_len(实际存了多少)可能小于orig_len(网线上真实长度)
  • 分析时"这个包有多少有效数据"要用incl_len;"这个包在线路上多大"要用orig_len
  • 一个 TCP 报文只抓到前 54 字节(以太网 14 + IP 20 + TCP 20)时,应用层数据就是永远看不到的——这不是 bug,是截断

4.3 network:决定从哪里开始解析

这个字段是整份文件共享的,它告诉我们"包数据的第一个字节是什么协议的头"。常见值:

值LinkType说明
0NULLBSD 回环,开头是 4 字节地址族(主机字节序)
1ETHERNET以太网(绝大多数文件)
9PPP点对点
101RAW裸 IP,包数据直接就是 IPv4 / IPv6 头
105IEEE802_11Wi-Fi 帧
113LINUX_SLLLinux “any” 抓包,16 字节 SLL 头
127IEEE802_11_RADIOTAPWi-Fi + Radiotap
228 / 229IPV4 / IPV6纯 IP
276LINUX_SLL2新版 SLL,20 字节头

五、数据包记录

5.1 包头(记录头,16 字节)

同一个东西有两个叫法:包头和记录头,指的都是这 16 字节。

别和后面包的"以太网头 14 字节"混了:包头是 pcap 文件加的,以太网头是网络包自带的,两者毫无关系,只是都叫"头"。

偏移长度字段含义
04ts_sec秒部分(Unix 时间戳)
44ts_usec微秒部分(纳秒版 magic 时这里是纳秒)
84incl_len本条记录里实际存了多少字节
124orig_len该包在网线上的原始长度

紧接着就是incl_len字节的包数据,没有任何对齐填充,下一条记录紧跟在后面。

5.2 两个长度字段为什么要分开

orig_len ────────────────► 网线上真实长度(比如 1514 字节) snaplen = 65535 时 incl_len ────────────────► 文件里存了 1514 字节(没截断,两者相等) snaplen = 96 时 orig_len ────────────────► 1514 incl_len ──────────────► 96(只留了头部,载荷被砍掉)

判断数据完整性、计算"这条流一共传了多少字节",靠的就是这两个字段的区别。


六、包数据里面:套娃式分层(链路 → 网络 → 传输 → 应用)

包数据的起点由network决定(下文按最常见的以太网讲)。每层都在自己头部里声明"我的载荷是什么",解析就是一个不断推进偏移、递减剩余长度的循环。

6.0 每一层的"数据部分",就是下一层的"整体"。

包数据 = 以太网帧 └─ [以太网头 14B] + IP 数据报 └─ [IPv4 头 20B] + TCP 段 └─ [TCP 头 20B] + 应用数据 18B

“头多大”“下一层是谁”,都是每层自己告诉你的:

层头多大?谁说的下一层是谁?谁说的数据部分从哪开始
记录层(容器)固定 16 字节不适用(它不是协议)上一条记录结束处 + 16
链路层 · 以太网固定 14 字节(有 VLAN 就是 18)偏移 12 的Type:0x0800= IPv4帧内偏移 14
网络层 · IPv4IHL × 4(典型 20)偏移 9 的Protocol:6= TCPIP 头内偏移IHL × 4
传输层 · TCPDataOffset × 4(典型 20)没有这个字段 → 靠端口号认应用协议(80 = HTTP)TCP 头内偏移DataOffset × 4
应用层没有头——剩下的全是它

最上面(应用层)没有"下一层是谁"的字段——TCP / UDP 只有端口号,所以"这是不是 HTTP"往往是猜出来的,这也是 Wireshark 会"猜协议"的原因。

读下面的字段表之前先记住:每张表里的偏移,都是相对本层起点的,不是相对文件、也不是相对整个包。IP 头那张表的"偏移 0",指的是 IP 头的第一个字节。

6.1 数据链路层:以太网 II(14 字节头)

偏移长度字段
06目的 MAC
66源 MAC
122类型 (EtherType)
14—载荷
末尾 4 字节4FCS 帧校验(libpcap 通常不保存)

常见 EtherType:

值协议
0x0800IPv4
0x0806ARP
0x86DDIPv6
0x8100VLAN(802.1Q)
0x88A8QinQ(802.1ad)
0x8847MPLS
0x8864PPPoE 会话
0x88CCLLDP

VLAN 会改变偏移:0x8100后面是 2 字节 TCI + 2 字节"真正的 EtherType",于是头从 14 变成 18 字节,还能叠两层(QinQ)。正确写法是循环读 EtherType,直到不是 VLAN 为止,而不是写死 14。

6.2 网络层:IPv4(20 ~ 60 字节头)

IPv4 头紧跟在 14 字节以太网头之后,所以它的起点 = 以太网头起点 + 14。

完整字段表(IPv4 头 20 ~ 60 字节,下表偏移都相对 IP 头起点):

偏移长度字段说明
01版本 + 首部长度高 4 位 = 版本(4);低 4 位 =IHL,头长 =IHL × 4(最小 5 → 20 字节)
11DSCP + ECN服务质量与拥塞标记,一般忽略
22总长度头 + 载荷的总字节数;可能大于实际捕获到的字节(被 snaplen 截断时)
42标识同一份原始数据切出来的所有分片共用这个编号
62标志 + 分片偏移高 3 位:R / DF / MF;低 13 位是分片偏移,单位 8 字节
81TTL每过一跳减 1,减到 0 就被丢弃(防止包永远打转)
91协议载荷类型 → 决定下一层是谁
102头校验和只校验 IP 头,不含数据
124源 IP
164目的 IP
20IHL×4 − 20选项少见(记录路由、时间戳等);有选项时头会长于 20 字节

协议字段 → 下一层:

值下一层值下一层
1ICMP47GRE(隧道)
2IGMP50ESP(IPsec 加密)
4IP-in-IP(隧道)51AH(IPsec 认证)
6TCP58ICMPv6
17UDP89OSPF(路由协议)
41IPv6 封装132SCTP

偏移怎么推进:传输层起点 = IP 头起点 + 首部长度。注意不能写死 20——首部长度写在 IP 头第 1 个字节的低 4 位里,要用第 1 个字节 & 0x0F再乘以 4 算出来。

进这一层前的防御:这一条记录至少要有34字节——

34 = 以太网头 14 + IP 头最小 20 不足 34 字节 → 连一个完整 IPv4 头都放不下 → 不解析

分片是这里最大的陷阱:分片偏移 ≠ 0的包(后续分片)里装的是纯数据片段,没有 TCP / UDP 头;MF=1的首片虽有传输层头,但载荷不完整。想正确解析就得先做重组,否则只能跳过。

分片:IPv4 会把一份大数据切成多片,每片单独成一个包发出:

  • MF = 1:后面还有分片;MF = 0:这是最后一片
  • DF = 1:不许分片(分片会被丢弃)
  • 分片偏移 ≠ 0:这不是第一片,里面没有 TCP / UDP 头,只有一段裸数据
  • 想还原完整报文,得按"标识 + 分片偏移"把同一组的片拼起来(重组)

日常上网(网页、ping、下载)里基本见不到分片,等真遇到再处理。

6.3 网络层:IPv6(固定 40 字节头)

怎么认出来:以太网类型是0x86DD(而不是0x0800),说明这一层装的不是 IPv4。

固定 40 字节头:

偏移长度字段说明
04版本(4b) + 流量类别(8b) + 流标签(20b)版本恒为 6
42载荷长度只算 40 字节固定头之后的部分
61下一头部载荷类型,但不一定是 TCP / UDP
71跳数限制等价于 IPv4 的 TTL
816源地址128 位
2416目的地址128 位

与 IPv4 的三个关键差别:

  1. 头是定长 40 字节,没有 IHL,不用算头长
  2. 地址是16 字节(128 位),不是 4 字节
  3. "下一层是谁"写在一连串扩展头里,要走一遍链才能找到 TCP / UDP:
值扩展头值扩展头
0逐跳选项51AH
43路由59无下一头部
44分片60目的选项
50ESP6 / 17 / 58TCP / UDP / ICMPv6

所以 IPv6 的解析本质是个循环:读下一头部 → 是扩展头就按它自己的长度往下跳 → 直到遇到 6 / 17 / 58 为止。

6.4 传输层:TCP(典型 20 字节头)

这一层的任务:读出端口号认出应用;TCP 还多几个字段值得看。

TCP 头紧跟在 IP 头之后,起点 = IP 头起点 + 首部长度。

偏移长度字段用途
0 / 22 / 2源端口 / 目的端口认应用:53 = DNS、80 = HTTP、443 = HTTPS
44序号本报文第一个数据字节的编号;把同一条连接的数据按顺序拼起来要靠它
84确认号期望下次收到对方哪个序号,ACK 置位时有效
121数据偏移(高 4 位) + 保留(3) + NS(1)头长 =高 4 位 × 4(最小 5 → 20 字节)
131标志位(8 个经典标志)见下表
142窗口大小对方还能接收多少字节(流量控制)
162校验和覆盖 TCP 头 + 数据 + 伪首部
182紧急指针只有 URG 置位时才有意义
20数据偏移×4 − 20选项MSS、窗口扩大、SACK、时间戳等;常见 12 字节 → 头变成 32 字节

标志位(偏移13)怎么读:把偏移 12 那 2 字节的低 8 位取出来(用& 0x00FF把高 8 位掩掉),一位一个标志:

bit值标志含义
00x01FIN断开连接
10x02SYN请求建立连接
20x04RST强制复位
30x08PSH尽快交给应用层
40x10ACK确认号有效
50x20URG紧急指针有效
60x40ECE显式拥塞通知(ECN)回显
70x80CWR拥塞窗口已减小

所以你看到的SYN, ACK,就是这两位同时为 1——TCP 三次握手的第二步。

6.5 传输层:UDP(固定 8 字节)

固定8 字节,字段一览:

偏移长度字段说明
0 / 22 / 2源端口 / 目的端口认应用:53 = DNS、67 / 68 = DHCP
42长度UDP 头 + 数据的总字节数(最小 8,即空数据)
62校验和可选,IPv4 里允许填 0 表示"没算"

关键结论:UDP 头后面直接就是应用数据——应用层报文的起点 = UDP 头起点 + 8。

小坑:长度字段在实际抓包里不一定可靠(有的实现不填、IPv6 巨型帧里甚至是 0),稳妥做法是用IP 总长 − IP 头长 − 8推算应用数据长度。

6.6 应用层

传输层载荷就是应用层报文本身。pcap只保存原始字节流,不会标记这一段是什么应用协议,识别和解析完全靠我们自己写的解析器。

识别依据:传输层端口号(约定俗成,不是绝对可靠)

常用应用协议速览

应用协议默认传输层端口说明
HTTPTCP80 / 8080TCP头后的载荷就是HTTP报文(GET/POST等请求)
HTTPS(TLS)TCP443TCP头后是TLS加密报文,无法直接读出明文
DNSUDP53UDP头后直接是DNS报文(12B固定DNS头 + 域名查询/应答)
DNSTCP53TCP头后载荷,最前面多2字节长度字段,之后才是DNS报文
触发场景:UDP应答>512B(TC=1截断)、主从服务器区域传输
DHCPUDP67(服务器) / 68(客户端)UDP载荷为DHCP报文,局域网分配IP用
SSHTCP22远程登录,加密TCP载荷
SMTPTCP25邮件发送协议

七.解析通用逻辑(抓包代码流程)

  1. 解析以太网帧 → 拿到IP报文
  2. 解析IP头,判断协议号:TCP(6) / UDP(17)
  3. 解析TCP/UDP头,读取源端口、目的端口
  4. 根据端口判断应用类型,取出传输层载荷交给对应应用层解析函数
    • UDP:应用起点 = 以太网14 + IP头长 + 8
    • TCP:应用起点 = 以太网14 + IP头长 + (TCP数据偏移 × 4)
  5. 对载荷按照该应用协议的格式解析

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询