从 ICMP 差错报文到 sk_buff 字段
Table of Contents
最近在复习计算机网络关于 ICMP 协议的知识点时,看到一条经典的铁律:
“为了防止广播风暴,针对目的地址是广播或多播的 IP 数据包,接收方产生差错时,不会发送 ICMP 差错报文。”
这条规则本身很好理解,但我产生了一个疑问。
问题的起因
ICMP 差错报文是由 网络层(Layer 3) 发出的。但是按照 OSI 或 TCP/IP 的分层封装原则,数据包从物理层向上传递时,数据链路层(Layer 2) 的帧头(包含 MAC 地址)会被剥离,只把剩下的净荷(IP 包)交给网络层。
那么问题来了: “既然 MAC 头都已经去掉了,接收站点的网络层是怎么知道这段数据在链路层是广播发过来的?如果不知道,它又是依据什么来决定‘不发 ICMP’的?”
听起来像是网络层“越级”去偷看了链路层的信息。
分析与解答
经过查阅资料,发现了之前的认知盲点:
分层间的数据传递没有那么简单。
实际上,操作系统内核在各层之间传递数据时,传的不只是**“数据本身”** ,还有一个包含各种**“元数据(Metadata)”**的结构体。
即使 MAC 头被物理剥离了,其源地址这个关键属性,也会被打成一个标签 ,贴在数据包上,一直传给网络层。
Linux 内核的 sk_buff
以 Linux 内核为例,网络数据包在内核中通常封装在一个叫做 sk_buff(socket buffer)的结构体对象中。
把这个结构体想象成一个文件夹:
- 文件内容:真正的 IP 数据包(去掉 MAC 头后)。
- 文件夹封面(属性):记录了大量元数据,包括数据包的来源、目的地、协议类型、接收接口等信息。
当一个广播帧到达网卡时,内核里发生了这样的“接力”:
-
网卡驱动层(Layer 2)接收: 驱动程序读取数据帧,此时它能看到完整的 MAC 帧头,发现目的 MAC 是
FF:FF:FF:FF:FF:FF(广播)。 -
打标签(关键步骤): 驱动程序在把数据包向上传递之前,做了两件事:
-
移动指针:把指向数据开头的指针向后移,跳过 MAC 头(相当于逻辑上去除 MAC 头)。
-
填写元数据:在
sk_buff结构体的pkt_type字段里写上标记PACKET_BROADCAST。
-
-
网络层(Layer 3)处理: IP 协议栈收到
sk_buff。虽然指针指向的是 IP 头,但它可以直接读取结构体上的pkt_type字段。 -
伪代码复现:
// IP层处理逻辑示意 void ip_send_icmp_error(struct sk_buff *skb) { // ...其他检查... // 【重点】检查链路层属性 // 检查随包传上来的“元数据标签” if (skb->pkt_type == PACKET_BROADCAST || skb->pkt_type == PACKET_MULTICAST) { // 发现这是一个链路层广播包,根据规定,不回发ICMP,直接丢弃 return; } // 只有通过所有检查,才发送ICMP send_icmp(skb); }