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,回环为 06
sll_addr[8] 8 字节 链路层地址(如 MAC 地址)。不足 8 字节补 0,超过则截断。对于入站包通常是源 MAC,对于出站包通常是目的 MAC 例如 00:11:22:33:44:55
sll_protocol 2 字节 上层协议类型,与以太网类型相同,网络字节序(大端) 0x0800 = IPv4
0x86DD = IPv6
0x0806 = ARP
0x8100 = VLAN

字节序注意sll_pkttypesll_hatypesll_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 或更高版本。tcpdumpSLL2 的支持在行为上有一个细节:v2 版本总是会输出链路层信息(如接口名和方向),而 v1 版本只在使用了 -e 选项时才会输出。
  • Wireshark: 需要 3.6.5 或更高版本,其核心的 sll 解析器在 2020年5月 才加入对 SLL2 的解析支持。
  • 其他工具:一些较新的安全工具(如 Suricata)已在其代码库中加入对 SLL2 的解码支持,但旧版本可能无法识别。