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 ens160root@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 showport 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处理
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