剖析Linux内核虚拟设备《VLAN数据包接收流程》

2023-02-20 00:00:00 函数 字段 设备 接收 数据包

先来看一下vlan数据包的帧格式,整个vlan信息大小为4个字节,分别为2个字节的标签协议标识(Tag Protocol Identifier),和2个字节标签控制信息(Tag Control Infomation)。

其中后者TCI又有三个子字段组成:3个bit的优先级(PRI)、一个bit的标准格式指示器(Canonical Format Indicator)和12个bit的vlan id:

以下代码是位于net/core/dev.c文件中vlan数据包的主要接收处理函数。驱动层接收上来的数据包已经设置好了skb的protocol字段(为8021Q或者8021AD)。依次调用skb_vlan_untag与vlan_do_receive函数进行处理。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)

{

another_round:

if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||

skb->protocol == cpu_to_be16(ETH_P_8021AD)) {

skb = skb_vlan_untag(skb);

}

if (skb_vlan_tag_present(skb)) {

if (vlan_do_receive(&skb))

goto another_round;

}

}


一、剥离VLAN标签

对于函数skb_vlan_untag来说,主要功能是从数据包中提取vlan信息,保存到skb结构中,然后从数据包中取出vlan相关字段,另外,获取数据包的真实协议类型更新到skb的protocol字段(三层协议类型),替换了之前的ETH_P_8021Q或者ETH_P_8021AD。

skb_reorder_vlan_header函数负责将vlan信息从数据包中剥离出来。

static inline void vlan_set_encap_proto(struct sk_buff *skb, struct vlan_hdr *vhdr)

{

proto = vhdr->h_vlan_encapsulated_proto;

if (ntohs(proto) >= ETH_P_802_3_MIN)

skb->protocol = proto;

}

static struct sk_buff *skb_reorder_vlan_header(struct sk_buff *skb)

{

memmove(skb->data - ETH_HLEN, skb->data - skb->mac_len - VLAN_HLEN, 2 * ETH_ALEN);

}

struct sk_buff *skb_vlan_untag(struct sk_buff *skb)

{

vhdr = (struct vlan_hdr *)skb->data;

vlan_tci = ntohs(vhdr->h_vlan_TCI);

__vlan_hwaccel_put_tag(skb, skb->protocol, vlan_tci);

vlan_set_encap_proto(skb, vhdr);

skb = skb_reorder_vlan_header(skb);

}


二、VLAN设备接收

系统中必须要有与数据包中vlan id相同的vlan设备,否则结束处理。找到此vlan设备后将其赋值给skb的dev,此时就完成了接收设备从物理网卡到vlan设备的转换,skb中的vlan_tci也就没有用处了可以清理。VLAN设备可能具有与其所在物理设备不同的MAC地址,在此情况下物理设备驱动程序会赋值PACKET_OTHERHOST到skb的pkt_type,需要进一步判断数据包目的MAC是否为vlan的MAC地址,如果是,修改pkt_type为PACKET_HOST,表示为发往本机的数据包。


如果关闭VLAN_FLAG_REORDER_HDR选项,vlan_do_receive函数会重新把vlan信息插入到skb的payload中。

bool vlan_do_receive(struct sk_buff **skbp)

{

vlan_dev = vlan_find_dev(skb->dev, vlan_proto, vlan_id);

if (!vlan_dev) return false;

skb->dev = vlan_dev;

if (skb->pkt_type == PACKET_OTHERHOST) {

if (ether_addr_equal(eth_hdr(skb)->h_dest, vlan_dev->dev_addr))

skb->pkt_type = PACKET_HOST;

}

skb->vlan_tci = 0;

}

对于IP数据包,skb的protocol换成了0x0806,ip_rcv函数就可以正常接收了。

相关文章