之前网桥与子接口相关的vlan信息都由net_port_vlans结构保存。一个接口配置的所有vlan id都按照tag/untag属性保存在不同的bitmap中。
struct net_port_vlans {
u16 pvid;
unsigned long vlan_bitmap[BR_VLAN_BITMAP_LEN];
unsigned long untagged_bitmap[BR_VLAN_BITMAP_LEN];
u16 num_vlans;
};
VLAN GROUP加入之后,去掉了以上的net_port_vlan结构体,网桥及其子接口的结构体都新增了一个net_bridge_vlan_group成员,用来保存所有配置的vlan表项(net_bridge_vlan)信息,vlan单个表项结构体名字上虽带有bridge,但是不只网桥,子接口也是使用net_bridge_vlan表示自己的单个vlan表项。
struct net_bridge_vlan_group {
struct rhashtable vlan_hash;
struct rhashtable tunnel_hash;
struct list_head vlan_list;
u16 num_vlans;
u16 pvid;
};
新的VLAN GROUP的定义,去掉了net_port_vlan中的bitmap结构,改为链表保存vlan表项信息。其中flags变量中的bit2位用来区分vlan是tag还是untag:
#define BRIDGE_VLAN_INFO_UNTAGGED (1<<2) /* VLAN egresses untagged */
既然网桥与子接口共用net_bridge_vlan结构表示vlan表项,就涉及到如何区分二者。其定义中有两个联合体用来标识此vlan为网桥或者子接口vlan时,具有的不同成员。当为网桥的vlan表项时,第一个联合体中br指针指向网桥(net_bridge),port指针为空;否则为子接口vlan表项时,port指针指向子接口,br指针为空。
如果flags设置了BRIDGE_VLAN_INFO_MASTER标志,第一个联合体中br指针指向网桥(net_bridge);如果flags没有MASTER标志,br无效,port指针指向网桥子接口的数据结构。当此vlan做为一个子接口的vlan表项,brvlan指向此接口所属网桥的VLAN GROUP中具有相同vlan id的vlan表项。
struct net_bridge_vlan {
struct rhash_head vnode;
u16 vid;
u16 flags;
union {
struct net_bridge *br;
struct net_bridge_port *port;
};
union {
refcount_t refcnt;
struct net_bridge_vlan *brvlan;
};
}
网桥VLAN的添加
VLAN的添加分为2种,一个是为网桥添加VLAN(br_vlan_add),另一种是为网桥子接口添加VLAN(nbp_vlan_add)。为网桥自身添加vlan时,flags中设置BRIDGE_VLAN_INFO_MASTER标志,如果此vlan也可用于vlan过滤功能需要设置BRIDGE_VLAN_INFO_BRENTRY标志。以上两个标志表示此vlan为网桥vlan组中的vlan并且要用于vlan过滤。使用bridge命令为网桥添加master的vlan时,内核默认此vlan做过滤使用(自动加上BRIDGE_VLAN_INFO_BRENTRY标志)。
ip link add br_test type bridge
ip link set eth0 master br_test
bridge vlan add dev br_test vid 10 self
bridge vlan add dev br_test vid 20 master
函数br_vlan_should_use完成以上的判断。之后将可用作过滤的vlan所对应的子接口的mac地址与vlan id添加到FDB转发表中。 另外VLAN GROUP中的vlan个数(num_vlans),只会在判断添加的vlan为可用时增加。所谓可用的vlan,即可用于二层转发数据包。
static inline bool br_vlan_should_use(const struct net_bridge_vlan *v)
{
if (br_vlan_is_master(v)) {
return (br_vlan_is_brentry(v) ? true : false);
}
return true;
}
static int __vlan_add(struct net_bridge_vlan *v, u16 flags)
{
if (br_vlan_should_use(v)) {
br_fdb_insert(br, p, dev->dev_addr, v->vid);
vg->num_vlans++;
}
}
在数据包接收过程中,检查数据包携带的vlan id,如果在group中能找到此id,并且group中的vlan可用于过滤功能,允许此数据包通过。
static bool __allowed_ingress(const struct net_bridge *br, struct net_bridge_vlan_group *vg, struct sk_buff *skb, u16 *vid)
{
v = br_vlan_find(vg, *vid);
if (!v || !br_vlan_should_use(v))
goto drop;
}
子接口VLAN添加
为子接口添加vlan时,首先要得到一个相同id的主VLAN,在函数br_vlan_get_master中如果没有找到主VLAN,随即创建一个,由于子接口VLAN要引用此主VLAN,在返回主vlan前,增加其引用计数。注意此时的主VLAN并不用作过滤功能!!
如果设置了BRIDGE_VLAN_INFO_MASTER表示此vlan要同时在网桥上做vlan过滤,需要额外增加一个网桥主vlan,设置BRIDGE_VLAN_INFO_BRENTRY标志做过滤。
static int __vlan_add(struct net_bridge_vlan *v, u16 flags)
{
if (p) {
/* need to work on the master vlan too */
if (flags & BRIDGE_VLAN_INFO_MASTER) {
err = br_vlan_add(br, v->vid, flags | BRIDGE_VLAN_INFO_BRENTRY, &changed);
}
masterv = br_vlan_get_master(br, v->vid);
v->brvlan = masterv;
}
}
纯粹主VLAN添加
纯粹的不用作过滤的主vlan,目前看还不能通过ip命令直接创建。其是在创建子接口vlan时,通过指定BRIDGE_VLAN_INFO_MASTER标志附带创建出来的。不做vlan过滤,也没看到起有其它功能。仅可见所有子接口的stats字段(struct br_vlan_stats)都是指向master的stats字段,可汇总所有子接口的vlan统计信息。
static int __vlan_add(struct net_bridge_vlan *v, u16 flags)
{
masterv = br_vlan_get_master(br, v->vid);
v->brvlan = masterv;
v->stats = masterv->stats;
}
内核版本
Linux-4.15