网桥的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 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
更多推荐
所有评论(0)