VLAN数据包接收流程

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

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

VLAN数据包接收流程_第1张图片

以下代码是位于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函数就可以正常接收了。


内核版本

Linux-4.15


你可能感兴趣的:(内核虚拟设备)