网桥的VLAN Filtering功能,顾名思义实现对入口与出口数据包依据VLAN信息的过滤。vlan filtering功能默认是关闭的,可通过如下命令开启:

echo 1 > /sys/class/net/brx/bridge/vlan_filtering
或者

ip link set brx type bridge vlan_filtering 1


初始化VLAN过滤

Linux网桥在初始化时,默认协议初始化为ETH_P_8021Q,默认接口vlan标识default_pvid为1,即vlan 1。


int br_vlan_init(struct net_bridge *br)
{
    br->vlan_proto = htons(ETH_P_8021Q);
    br->default_pvid = 1;
    return br_vlan_add(br, 1,
               BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED);
}


以下命令创建一个网桥,并显示网桥vlan配置信息,可见与br_vlan_init一致,初始化时配置了pvid等于1,并且数据包发出时不带vlan tag信息(Egress Untagged)。

root@localhost:~$ brctl addbr brx
root@localhost:~$ bridge vlan show
port    vlan ids
brx      1 PVID Egress Untagged

root@localhost:~$


配置Vlan过滤功能


可使用bridge(来自iproute2工具集)命令修改接口的vlan id、pvid以及tagged/untagged属性,如下所示。

root@localhost:~$ brctl addif brx ens160
root@localhost:~$ bridge vlan show
port    vlan ids
ens160   1 PVID Egress Untagged
brx      1 PVID Egress Untagged


root@localhost:~$ bridge vlan add dev brx vid 10 pvid self

root@localhost:~$ bridge vlan show
port    vlan ids
ens160   1 PVID Egress Untagged
brx      1 Egress Untagged
         10 PVID

root@localhost:~$

bridge工具通过netlink接口下发配置内核,br_vlan_info函数进行处理,可见nbp_vlan_add函数处理网桥子接口的vlan配置;br_vlan_add处理网桥本身的vlan配置。两者最终通过函数__vlan_add处理具体的设置。


static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p, int cmd, struct bridge_vlan_info *vinfo)
{
    if (p)
        err = nbp_vlan_add(p, vinfo->vid, vinfo->flags);
    else
        err = br_vlan_add(br, vinfo->vid, vinfo->flags);
}


来看__vlan_add函数的处理,设置vlan_bitmap中对于vid的位(在数据流程ingress中将判断此位)。如果是pvid的配置,将此vid赋值给net_port_vlans结构体成员pvid;如果配置了untagged选项,设置untagged_bitmap变量中对应vid的位(在数据流程egress中将判断此位)。


static void __vlan_add_flags(struct net_port_vlans *v, u16 vid, u16 flags)
{
    if (flags & BRIDGE_VLAN_INFO_PVID)
        __vlan_add_pvid(v, vid);

    if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
        set_bit(vid, v->untagged_bitmap);
}
static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
{
    set_bit(vid, v->vlan_bitmap);
    __vlan_add_flags(v, vid, flags);
}


入口数据包过滤


内核代码调用br_allowed_ingress进行ingress方向vlan filtering处理。ingress表示进入网桥的方向,所以过滤点有两类,一个是在外部数据包从网桥的子接口进入网桥时,在br_handle_frame_finish函数中做过滤;另一处是在本机发出的数据包直接进入网桥时,即br_dev_xmit函数中做过滤。但是两处的检查对象不同,一个是检查外部数据包的vlan与物理口的vlan规则,另一个是检查本机数据包与网桥本身的vlan设置规则。


有代码可见,两处检查传入的第二个参数的差别,一个是网桥子接口的net_port_vlans信息,一个是网桥本身的vlan信息。


int br_handle_frame_finish(struct sock *sk, struct sk_buff *skb)
{
	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
	
    if (!br_allowed_ingress(p->br, nbp_get_vlan_info(p), skb, &vid))
        goto out;
}
netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
   if (!br_allowed_ingress(br, br_get_vlan_info(br), skb, &vid))
       goto out;
}


来看函数br_allowed_ingress中的过滤,vlan过滤的具体实现分两种情况,第一如果数据包不是vlan数据包,没有vlan信息,或者是vlan数据包,但是其中的vlan id等于0,即仅表示vlan优先级的数据包。首先检查此接口是否支持pvid(不等于0),不支持丢弃此数据包;如果支持,将pvid的值赋给skb中的vlan_tci字段,此后数据包依据此pvid进行转发处理。

第二如果数据包中带有vlan信息,判断此接口vlan_bitmap中是否设置相应的vlan id位,如果设置接收此数据包,否则丢弃。


bool br_allowed_ingress(struct net_bridge *br, struct net_port_vlans *v,
            struct sk_buff *skb, u16 *vid)
{
    if (!*vid) {
        u16 pvid = br_get_pvid(v);
        if (!pvid)
            goto drop;

        *vid = pvid;
        if (likely(!tagged))
            __vlan_hwaccel_put_tag(skb, proto, pvid);
        else
            skb->vlan_tci |= pvid;

        return true;
    }
    if (test_bit(*vid, v->vlan_bitmap))
        return true;
}


出口数据包过滤


内核代码调用br_allowed_egress进行egress方向vlan filtering处理。egress表示网桥向外的方向,与ingress不同此时过滤点有三类,一是转发的数据包从网桥的子接口发出时,在br_forward函数中做过滤;二是本机产生的数据包从网桥发出时,在br_deliver函数中做过滤;另外,对于在转发forward与本地发出deliver报文时,在FDB中找不到目的MAC的情况,都需要泛洪处理,此时(单播和多播)分别在br_flood和br_multicast_flood函数中做规则过滤。


以下代码可见,规则过滤非常简单,即检查以下数据包(skb)中的vlan id是否是接口的vlan_bitmap中所允许的。


bool br_allowed_egress(struct net_bridge *br, const struct net_port_vlans *v, const struct sk_buff *skb)
{
    br_vlan_get_tag(skb, &vid);
    if (test_bit(vid, v->vlan_bitmap))
        return true;

    return false;
}


出口数据包的TAG处理


出口数据包是否要增加vlan id(tag)在函数br_handle_vlan中进行判断,如果接口的untagged_bitmap位图中含有vid比特位,说明不要加tag,清空数据包(skb)的vlan_tci字段,否则保留vlan_tci字段的值。

struct sk_buff *br_handle_vlan(struct net_bridge *br, const struct net_port_vlans *pv, struct sk_buff *skb)
{
    br_vlan_get_tag(skb, &vid);
    if (test_bit(vid, pv->untagged_bitmap))
        skb->vlan_tci = 0;
}


VLAN过滤的硬件加速


内核提供了接口函数可将vlan filtering功能卸载到网卡执行。如果网卡支持vlan_filtering,判断net_device的features字段是否置位NETIF_F_HW_VLAN_CTAG_FILTER和NETIF_F_HW_VLAN_STAG_FILTER,如果置位说明支持offload,这样在配置vlan的时候,同时将vlan下发到网卡,接口函数指针ndo_vlan_rx_add_vid,查看intel e1000e网卡驱动,对应的函数为e1000_vlan_rx_add_vid,硬件过滤开启之后,将自动过滤掉不符合vlan规则的数据包。


static int __vlan_vid_add(struct vlan_info *vlan_info, __be16 proto, u16 vid, struct vlan_vid_info **pvid_info)
{
    if (vlan_hw_filter_capable(dev, vid_info)) {
        if (netif_device_present(dev))
            err = ops->ndo_vlan_rx_add_vid(dev, proto, vid);
    }
}

./drivers/net/ethernet/intel/e1000e/netdev.c

.ndo_vlan_rx_add_vid    = e1000_vlan_rx_add_vid

但是有一点要注意,当接口处于混杂模式时,所有数据包都会上送。


内核版本

linux-3.10.0


GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐