Linux网络抓包之LINUX_SLL
Linux网络抓包之LINUX_SLL
问题背景
最近遇到了个问题,在一个高版本的Linux系统上抓的包,在低版本系统(版本跨度比较大)的 Wireshark 下打开报错:
The file “board.pcap” contains record data that Wireshark doesn’t support.
pcap: network type 276 unknown or unsupported
网络类型 276 对应的是 Linux “cooked” capture encapsulation v2 (SLL2) 格式。出现这个错误是因为当前使用的 Wireshark 版本较旧,尚未支持这种新的链路层类型。
tcpdump用 -y 选项强制指定链路层类型,sudo tcpdump -i any -y LINUX_SLL 来使用旧版封装。
LINUX_SLL 是什么?
LINUX_SLL(Linux cooked capture)是 Linux 系统在使用 tcpdump -i any 或类似“任意接口”抓包时,由 libpcap 生成的一种伪链路层头部。它对应的 pcap 链路层类型为 DLT_LINUX_SLL = 113,在 Wireshark 中显示为 “Linux cooked capture”。
它的核心作用是:将来自不同物理接口(以太网、Wi-Fi、回环等)的数据包统一封装成一个固定格式,以便在用户态进行分析。由于它并非真实的链路层帧,因此被称为“cooked”(烹饪过的、加工过的)。
产生机制
当执行 tcpdump -i any 时,libpcap 会打开一个 AF_PACKET 类型的 SOCK_DGRAM 套接字,并绑定到所有接口。内核在每次收到或发出数据包时,会通过 recvfrom() 返回一个 sockaddr_ll 结构,其中包含:
- 接口索引
- 数据包类型(发往本机、广播、多播等)
- 链路层地址类型(如以太网)
- 链路层地址长度和地址
- 上层协议类型
libpcap 将这些信息提取出来,构造成一个 **16 字节的 sll_header**,然后紧跟在原始网络层数据包(如 IP 头)之前,写入 pcap 文件。因此,pcap 文件中的每个数据包都以这个伪头开始。
头部结构定义及字段详解
#define SLL_HDR_LEN 16 /* 总长度 */
#define SLL_ADDRLEN 8 /* 地址字段长度 */
struct sll_header {
uint16_t sll_pkttype; /* 数据包类型 */
uint16_t sll_hatype; /* 链路层地址类型 */
uint16_t sll_halen; /* 链路层地址长度 */
uint8_t sll_addr[8]; /* 链路层地址 */
uint16_t sll_protocol; /* 上层协议类型 */
};
整个头部固定 16 字节,之后就是网络层数据(例如 IPv4 头)。
字段详解:
| 字段 | 长度 | 说明 | 常见值 |
|---|---|---|---|
sll_pkttype |
2 字节 | 数据包类型,表示包的方向和目的地 | 0 = 发往本机 (HOST)1 = 广播 (BROADCAST)2 = 多播 (MULTICAST)3 = 发往其他主机 (OTHERHOST)4 = 本机发出 (OUTGOING) |
sll_hatype |
2 字节 | 链路层地址类型(ARPHRD_*) | 1 = 以太网 (ETHER)772 = 回环 (LOOPBACK)512 = PPP 等 |
sll_halen |
2 字节 | 链路层地址的有效长度 | 以太网为 6,回环为 0 或 6 |
sll_addr[8] |
8 字节 | 链路层地址(如 MAC 地址)。不足 8 字节补 0,超过则截断。对于入站包通常是源 MAC,对于出站包通常是目的 MAC。 | 例如 00:11:22:33:44:55 |
sll_protocol |
2 字节 | 上层协议类型,与以太网类型相同,网络字节序(大端) | 0x0800 = IPv40x86DD = IPv60x0806 = ARP0x8100 = VLAN |
字节序注意:
sll_pkttype、sll_hatype、sll_halen在 pcap 文件中通常按捕获主机的字节序存储(小端居多),而sll_protocol保持网络字节序。Wireshark 会根据 pcap 文件的全局字节序标识正确解析。
与以太网头的对比
| 特性 | 以太网头 | LINUX_SLL 头 |
|---|---|---|
| 长度 | 14 字节 | 16 字节 |
| 地址字段 | 目的 MAC (6) + 源 MAC (6) | 只有一个地址字段 (8),含义随包方向变化 |
| 类型字段 | 2 字节 EtherType | 2 字节协议类型,位置在末尾 |
| 额外信息 | 无 | 包类型、链路层地址类型、地址长度 |
| 真实性 | 真实链路层帧 | 伪头部,由内核和 libpcap 构造 |
| 接口信息 | 无 | 无接口索引(这是 SLL 的最大局限) |
由于 -i any 可能同时捕获多个接口,SLL 头无法告诉分析者数据包来自哪个具体接口(如 eth0 还是 wlan0),这在多网卡环境中很不方便。这一缺陷后来由 LINUX_SLL2 弥补。
🚀 LINUX_SLL2 (v2):功能增强
LINUX_SLL2 是为解决 v1 的核心缺陷(无法直接获取接口索引)而设计的升级版。是对经典 LINUX_SLL 封装格式的一次重要改良。其核心改进是增加了接口索引字段,这使得在多网卡环境下进行流量分析变得更加清晰和准确。
它的头部扩展为 20 字节,结构如下:
#define SLL2_HDR_LEN 20 /* 总头长度 */
struct sll2_header {
uint16_t sll2_protocol; /* 协议类型 (2字节) */
uint16_t sll2_reserved_mbz; /* 保留字段,必须为0 (2字节) */
uint32_t sll2_if_index; /* 接口索引 (4字节) */
uint16_t sll2_hatype; /* 链路层地址类型 (2字节) */
uint8_t sll2_pkttype; /* 数据包类型 (1字节) */
uint8_t sll2_halen; /* 链路层地址长度 (1字节) */
uint8_t sll2_addr[8]; /* 链路层地址 (8字节) */
};
🔍 核心差异对比
SLL2 相较于 SLL 的关键改进,主要体现在三个方面:
- **新增接口索引字段 (
sll2_if_index)**:这是最核心的改进。该字段允许抓包工具明确显示数据包来自哪个网络接口(如eth0,wlan0),这对于多网卡环境下的故障排查至关重要。SLL缺乏此字段,因此无法直接获取接口名称。 - 字段尺寸优化:
pkttype(数据包类型)和halen(地址长度)字段从SLL的 2 字节缩减为 1 字节,与内核中sockaddr_ll结构体的定义保持一致,减少了头部开销。 - 字段顺序重排:
SLL2将协议类型字段(protocol)从末尾移至开头,其他字段的顺序也做了调整,这使整个头部结构更紧凑,也更接近sockaddr_ll的原生布局。
📊 头部字段差异
| 字段 (SLL / SLL2) | 长度 (SLL / SLL2) | 说明 |
|---|---|---|
sll_pkttype / sll2_pkttype |
2字节 / 1字节 | 数据包类型。常见值:0=发往本机,1=广播,2=多播,3=发往其他主机。 |
sll_hatype / sll2_hatype |
2字节 / 2字节 | 链路层地址类型(如 ARPHRD_ETHER 表示以太网)。 |
sll_halen / sll2_halen |
2字节 / 1字节 | 链路层地址的有效长度(如 MAC 地址为 6)。 |
sll_addr / sll2_addr |
8字节 / 8字节 | 链路层地址(如 MAC 地址)。若实际地址不足8字节,则用0填充;若超过,则截断。 |
sll_protocol / sll2_protocol |
2字节 / 2字节 | 上层协议类型(如 0x0800 表示 IPv4)。 |
sll2_if_index |
- / 4字节 | SLL2 新增。网络接口索引,用于关联 if_indextoname() 获取接口名称。 |
🛠️ 工具支持与版本要求
LINUX_SLL2 的普及是一个渐进的过程,你需要确保工具链的版本足够新。
- libpcap: 需要 1.9.1 或更高版本。
- tcpdump: 需要 4.9.3 或更高版本。
tcpdump对SLL2的支持在行为上有一个细节:v2 版本总是会输出链路层信息(如接口名和方向),而 v1 版本只在使用了-e选项时才会输出。 - Wireshark: 需要 3.6.5 或更高版本,其核心的
sll解析器在 2020年5月 才加入对SLL2的解析支持。 - 其他工具:一些较新的安全工具(如 Suricata)已在其代码库中加入对
SLL2的解码支持,但旧版本可能无法识别。





