Linux VLAN框架及其在Intel网卡I350的实现

时间:2022-10-06 23:40:16

一、 科普:什么是VLAN

常提到的VLAN是依据IEEE802.1Q标准定义的,其最核心的概念是所谓的TAG。我们经常提到的TAG,本质上是VLAN字段的VID字段。下面贴出VLAN的字段含义:

Linux VLAN框架及其在Intel网卡I350的实现

从定义可以看出,VLAN是一种特殊的以太网包定义,这就意味着VLAN是一种二层的概念。本质上VLAN数据包就是以太网类型为0x8100的以太网数据包,VLAN的提出是为了在一个实体广播域内划分出更多的广播域,实现子网的隔离。如果不同的VLAN子域之间需要互相通信,就需要三层交换的介入。

二、 Linux的VLAN支持

Linux内核中有一个“802.1Q VLAN Support”的网络选项,可以模块或者built-in到内核镜像提供对VLAN的内核支持。内核态的实现是无法跟用户交互的,在Linux系统使用VLAN功能还需要有vconifg这个用户态工具

 vconfig --help


Create and remove virtual ethernet devices


add IFACE VLAN_ID
rem VLAN_NAME
set_flag IFACE 0|1 VLAN_QOS
set_egress_mapVLAN_NAME SKB_PRIO VLAN_QOS
set_ingress_mapVLAN_NAME SKB_PRIO VLAN_QOS
set_name_type NAME_TYPE


比较常用到的命令是add:在某个以太网口添加换一个VLAN 端口,rem删除一个已经添加的VLAN端口。

关于如何用vconfig实现类似交换机的VLAN交换功能,网络上有很多博文做了描述,这里就不交代了。

三、Intel网卡I350对Linux VLAN的支持

内核主线的VLAN处理与网卡的接口部分变动很大,下面分别以2.6.28和3.8.0两个内核版本做分别介绍

首先说一下I350对VLAN的支持:I350的网卡驱动接收和发送部分的skb普通的skb数据包,skb->data里面是没有任何VLAN的信息的。I350硬件处理VLAN的接收和发送。

接收时I350会对接收到的VLAN数据包做去tag的处理,然后把处理后的数据包上传到linux网络协议栈。

发送时Linux协议栈发送来的数据包也是untagged,发送的时候网卡硬件会做相应的TAG处理。

硬件是如何做到接收和发送的处理的呢,当然软件不可能什么工作都不做,就让网卡完成VLAN的处理。 I350的网卡有个核心的概念就是:发送/接收描述符

接收描述符回写时定义

Linux VLAN框架及其在Intel网卡I350的实现

Linux VLAN框架及其在Intel网卡I350的实现

网卡在接收到VLAN数据包时,会根据系统的配置,决定是否接收该VLAN数据包到协议栈,如果符合接收的条件,会把数据包的VLAN信息记录到描述符

发送描述符

Linux VLAN框架及其在Intel网卡I350的实现

Linux VLAN框架及其在Intel网卡I350的实现

发送时,网卡驱动会把协议栈记录的相应的VLAN信息填写到发送描述符指导网卡的硬件做出正确的处理。

下面看一下不同版本的内核是如何与网卡驱动配合实现对VLAN的支持的。

首先看一下用于处理特殊接收信息的服务代码igb_process_skb_fields的片段

f ((dev->features & NETIF_F_HW_VLAN_RX) &&
igb_test_staterr(rx_desc, E1000_RXD_STAT_VP)) {
u16 vid = 0;
if (igb_test_staterr(rx_desc, E1000_RXDEXT_STATERR_LB) &&
test_bit(IGB_RING_FLAG_RX_LB_VLAN_BSWAP, &rx_ring->flags))
vid = be16_to_cpu(rx_desc->wb.upper.vlan);
else
vid = le16_to_cpu(rx_desc->wb.upper.vlan);
#ifdef HAVE_VLAN_RX_REGISTER
IGB_CB(skb)->vid = vid;
} else {
IGB_CB(skb)->vid = 0;
#else
__vlan_hwaccel_put_tag(skb, vid);
#endif
发接收描述符获取了VLAN的VID信息之后如何记录以来与宏HAVE_VLAN_RX_REGISTER

这个宏是如何定义的呢,在驱动头文件里有这样的片段

#if ( LINUX_VERSION_CODE > KERNEL_VERSION(2,4,18) )
#ifndef HAVE_VLAN_RX_REGISTER
#define HAVE_VLAN_RX_REGISTER
#endif
#endif /* > 2.4.18 */
#endif /* < 2.6.37 */
在低版本的内核下,网卡驱动把VID信息记录到自己定义的数据结构GB_CB(skb)->vid

高版本的内核,通过__vlan_hwaccel_put_tag直接记录到skb->vid域

/**
* __vlan_hwaccel_put_tag - hardware accelerated VLAN inserting
* @skb: skbuff to tag
* @vlan_tci: VLAN TCI to insert
*
* Puts the VLAN TCI in @skb->vlan_tci and lets the device do the rest
*/
static inline struct sk_buff *__vlan_hwaccel_put_tag(struct sk_buff *skb,
u16 vlan_tci)
{
skb->vlan_tci = VLAN_TAG_PRESENT | vlan_tci;
return skb;
}

高版本的内核处理时会占用VLAN的一个优先级的bit位。这个bit如何处理不好在华为的S3300交换机会出现同一个VID的数据包,VLAN域内不能转发的问题。

网卡驱动获取了相应的vid的信息后,将数据包提交到内核协议栈在高低版本也是有区别的

#ifdef HAVE_VLAN_RX_REGISTER
igb_receive_skb(q_vector, skb);
#else
napi_gro_receive(&q_vector->napi, skb);
#endif
在低版本的内核中需要驱动自己实现一个服务来兼容VLAN数据和非VLAN数据的接收,就是igb_receive_skb

#ifdef HAVE_VLAN_RX_REGISTER
/**
* igb_receive_skb - helper function to handle rx indications
* @q_vector: structure containing interrupt and ring information
* @skb: packet to send up
**/
static void igb_receive_skb(struct igb_q_vector *q_vector,
struct sk_buff *skb)
{
struct vlan_group **vlgrp = netdev_priv(skb->dev);

if (IGB_CB(skb)->vid) {
if (*vlgrp) {
vlan_gro_receive(&q_vector->napi, *vlgrp,
IGB_CB(skb)->vid, skb);
} else {
dev_kfree_skb_any(skb);
}
} else {
napi_gro_receive(&q_vector->napi, skb);
}
}

#endif /* HAVE_VLAN_RX_REGISTER */
napi_gro_receive实际是就是内核协议栈与网卡接收的接口函数netif_receive_skb

#define napi_gro_receive(_napi, _skb) netif_receive_skb(_skb)

发送方向上的处理是通过igb_xmit_frame_ring来实现

if (vlan_tx_tag_present(skb)) {
tx_flags |= IGB_TX_FLAGS_VLAN;
tx_flags |= (vlan_tx_tag_get(skb) << IGB_TX_FLAGS_VLAN_SHIFT);
}

/* record initial flags and protocol */
first->tx_flags = tx_flags;
first->protocol = protocol;

tso = igb_tso(tx_ring, first, &hdr_len);
if (tso < 0)
goto out_drop;
else if (!tso)
igb_tx_csum(tx_ring, first);
获取skb的vid信息后,通过igb_tx_sum写入到发送描述符,下面是程序片段

		/* update TX checksum flag */
first->tx_flags |= IGB_TX_FLAGS_CSUM;
}

vlan_macip_lens |= skb_network_offset(skb) << E1000_ADVTXD_MACLEN_SHIFT;
vlan_macip_lens |= first->tx_flags & IGB_TX_FLAGS_VLAN_MASK;

igb_tx_ctxtdesc(tx_ring, vlan_macip_lens, type_tucmd, mss_l4len_idx);
}