第一部分:VLAN 概念
说起IEEE 802.1q,都知道是VLAN,说起VLAN,基本上也没有盲区,网络基础。
对于Linux的VLAN配置却存在大量的疑问。这些疑问之所以存在我觉得有两点原因:
1.对VLAN的本质还是没有理解。
不管你的Cisco/H3C命令敲得再熟练,如果看不懂Linux的vconfig,那么也将无法掩饰你对概念理解的浅显;
2.对Linux实现虚拟网络设备风格不熟悉
可能你已经十分理解802.1q了,也许还看过了IEEE的文档,然而却对Linux的Bridge,tap,bond等虚拟设备不是很理解,那么也将无法顺利配置VLAN。
可参考如下文章,进一步了解:
《linux bridge 原理解析》
对于VLAN概念的理解,有几点要强调:
- 1.VLAN分离了广播域;
- 2.单独的一个VLAN模拟了一个常规的交换以太网,因此VLAN将一个物理交换机分割成了一个或多个逻辑交换机;
- 3.不同VLAN之间通信需要三层参与;
- 4.当多台交换机级联时,VLAN通过VID来识别,该ID插入到标准的以太帧中,被称作tag;
- 5.大多数的tag都不是端到端的,一般在上行路上第一个VLAN交换机打tag,下行链路的最后一个VLAN交换机去除tag;
- 6.只有在一个数据帧不打tag就不能区分属于哪个VLAN时才会打上tag,能去掉时尽早要去掉tag;
- 7.最终,IEEE 802.1q解决了VLAN的tag问题。
关键看最后3点,也就是3,4,5。这是VLAN最难理解的部分。
为了使得叙述上以及配置上更加的方便,Cisco以及其他的厂商定义了很多的细节,而这些细节在IEEE 802.1q标准中并没有被定义,这些细节包括但不局限于以下几点:
1.每一个VLAN交换机端口需要绑定一个VLAN id;
2.每一个VLAN交换机端口处于下面三类中的一类:access,trunk,hybrid。
2.1.access端口:从此类端口收到的数据帧是不打tag的,从此类端口发出的数据帧是不打tag的;
2.2.trunck端口:从此类端口收到的数据帧打着tag,从此类端口发出的数据帧需要打tag(不考虑缺省VLAN的情况);
2.3.hybrid端口:略
trunk端口的存在是因为不得已,因为有属于多个VLAN的数据帧要通过单一的物理链路,不打tag是无法区分各自属于哪个VLAN的,于是就有了IEEE 802.1q这个标准,定义了一个tag插入到以太帧中,为了使这个理论性的东西被使用起来,厂商便定义了一系列的概念性的东西,比如和tag相关的链路就是trunk链路之类。
第二部分:Linux上的VLAN
理解Linux Bridge的都知道,Linux本身就可以实现多个Bridge设备,因为Linux的Bridge是软的,所以一个Linux Box可以配置多个逻辑意义的Bridge,而多个Bridge设备之间必须通过第三层进行通信,加之第三层正是以太网的边界,因此一个Linux Box也就可以模拟多个以太网了,不同的Bridge设备就可以代表不同的VLAN。
Linux上的VLAN和Cisco/H3C上的VLAN不同,后者的VLAN是现有了LAN,再有V,也就是说是先有一个大的LAN,再划分为不同的VLAN,而Linux则正好相反,由于Linux的Bridge设备是被创建出来的逻辑设备,因此Linux需要先创建VLAN,再创建一个Bridge关联到该VLAN,创建VLAN很简单:
ifconfig eth0 0.0.0.0 up
vconfig eth0 10 # 创建 vlan 10 在 eth0 接口
ifconfig eth0.10 up # 打开 vlan10
当使用vconfig创建了eth0.10之后,它就是一个“真实意义”的虚拟网卡设备了,类似br0,tap0,bond0之类的,在这个虚拟网卡之下绑定的是一个真实网卡eth0,也就是数据从eth0这块真实网卡发出;
eth0.10中的 “.10” 表示它可以承载VLAN 10的数据帧,并且在通过eth0发出之前要打上tag。那么打tag这件事自然而然就是通过 eth0.10 这个虚拟设备的hard_xmit来完成的,在这个hard_xmit中,打上相应的tag后,再调用eth0的hard_xmit将数据真正发出,如下图所示:
因此一个真实的物理网卡比如ethx,它可以承载多个VLAN的数据帧,因此它就是trunk端口了,如下所示:
Linux的VLAN工具vconfig采用ethx.y的方式以ethx为trunk端口加入VLAN id为y的VLAN中。
类比Cisco/H3C,我们已经创建了trunk,总结一下:使用vconfig创建一个ethx.y的虚拟设备,就创建了一个trunk,其中ethx就是trunk口,而y代表该trunk口连接的trunk链路可以承载的VLAN数据帧的id,我们创建ethx.a,ethx.b,ethx.c,ethx.d,就说明ethx可以承载VLAN a,VLAN b,VLAN c,VLAN d的数据帧。
如何创建access 和 trunk 端口
由于Linux的Bridge是虚拟的,逻辑意义的,因此可以先创建了VLAN之后,再根据这个VLAN动态的创建Bridge,而不是“为每一个端口配置VLAN id”,我们需要做的很简单:
# (1) 创建VLAN:
ifconfig eth0 0.0.0.0 up
vconfig eth0 10
ifconfig eth0.10 up
# (2) 为该VLAN创建Bridge:
brctl addbr brvlan10
brctl addif brvlan10 eth0.10
# (3) 为该VLAN添加网卡:
ifconfig eth1 0.0.0.0 up
brctl addif brvlan10 eth1
ifconfig eth2 0.0.0.0 up
brctl addif brvlan10 eth2
从此,eth1和eth2就是VLAN 10的access端口了,而eth0则是一个trunk端口;
级联VLAN的时候要用到,如果不需要级联VLAN,而仅仅需要扩展VLAN 10,那么你大可将eth1连接在一个二层常规交换机或者hub上。
可以再创建一个VLAN,同样通过eth0来级联上游VLAN交换机:
ifconfig eth0 0.0.0.0 up
vconfig eth0 20
ifconfig eth0.20 up
brctl addbr brvlan20
brctl addif brvlan20 eth0.20
ifconfig eth3 0.0.0.0 up
brctl addif brvlan20 eth3
ifconfig eth4 0.0.0.0 up
brctl addif brvlan20 eth4
ifconfig eth5 0.0.0.0 up
brctl addif brvlan20 eth5
这下基本就搞定了Linux上VLAN的配置。
VLAN 间通信
VLAN之间的通信,那就是使用路由,为此很多人把支持VLAN的三层交换机和路由器等同起来。
既然使用路由就需要一个IP地址作为网关,那么如何能寻址到这个IP地址,我们要把这个IP配置在哪里呢?可以肯定的是,必须配置在当前VLAN的某处,于是我们有多个地方可以配置这个IP:
- 1.同属于一个VLAN的路由器接口上,且该路由器有到达目的VLAN的路由(该路由器接口为trunk口)。
- 2.同属于一个VLAN的ethx.y似的虚拟接口上,且该Linux Box拥有到指定VLAN a的路由(最显然的,拥有ethx’.a虚拟接口)。
- 3.同属于一个VLAN的Bridge设备上(Linux的Bridge默认带有一个本地接口,可以配置IP地址),且该Linux Box拥有到指定VLAN a的路由(最显然的,拥有ethx’.a虚拟接口或者目标VLAN的Bridge设备)。
其中的1和2实际上没有什么差别,本质上就是找一个能配置IP地址的地方,大多数情况下使用2,但是如果出现同一个VLAN在同一个Linux Box配置了两个trunk端口,那么就要使用Bridge的地址了,比如下面的配置:
brctl addbr brvlan10
brctl addif brvlan10 eth0.10
brctl addif brvlan10 eth1.10
ifconfig brvlan10 up
此时有两个ethx.y型的虚拟接口,为了不使路由冲突,只能配置一个IP,那么此IP地址就只能配置在brvlan10上了。不管配置在Bridge上还是配置在ethx.y上,都是要走IP路由的,只要MAC地址指向了本地的任意的一个接口,在netif_receive_skb调用handle_bridge的时候都会将数据帧导向本地的IP路由来处理。Linux作为一个软件,其并没有原生实现硬件cache转发,因此对于Linux而言,所谓的三层交换其实就是路由。
我们看一下一个被打上tag的数据帧什么时候脱去这个tag,在定义上,它是从access端口发出时脱去的,然而在语义上,只要能保证access端口发出的数据帧不带有tag即可,因此对于何时脱去tag并没有什么严格的要求。在Linux的VLAN实现上,packet_type的func作为一个第三层的处理函数来单独处理802.1q数据帧,802.1q此时和IP协议处于一个同等的位置,VLAN的func函数vlan_skb_recv正如IP的处理函数ip_rcv一样。在Linux实现的VLAN中,只有当一个端口收到了一个数据帧,并且该数据帧是发往本地的时候,才会到达第三层的packet_type的func处理,否则只会被第二层处理,也就是Bridge逻辑处理,Linux的原生Bridge实现并不能处理802.1q数据帧,甚至都不能识别它。整个trunk口收发数据帧,IEEE 802.1q帧处理,以及VLAN间通信的示意图如下:
到此为止,Linux的VLAN要点基本已经说完了,有了这些理解,我想设计一个单臂Linux Box就不是什么难事了,单臂设备最大的优势就是节省物理设备,同时还能实现隔离。这个配置不复杂,如果不想用VLAN实现的话也可以用ip addr add dev …增加虚拟IP的方式来实现,然而用VLAN实现的好处在于可以和既有的三层交换机进行联动,也可以直接插在支持标准的IEEE 802.1q的设备的trunk口上。
机制搭台,策略唱戏。既然VLAN的实现机制已经了然于胸了,那么它的缺点估计你也看到了,如何去克服呢?PVLAN说实在的是一个VLAN的替代方案。解决了VLAN间的IP网段隔离问题,我们在Linux上如何实现它呢?这倒也不难,无非就是在LAN上添加一些访问控制策略罢了,完全可以用纯软件的方式来实现,甚至都可以用ebtables/arptables/iptables来实现一个PVLAN。如果说VLAN是一个硬实现的VLAN的话,那么PVLAN纯粹是一个软实现的VLAN,甚至都不需要划分什么VLAN,大家都处于一个IP网段,只需要配置好访问控制策略即可,使得同一IP子网的Host只能和默认网关通信,而之间不能通信,所以说,即使你不知道“隔离VLAN”,“团体VLAN”之类的术语,实际上你已经实现了一个PVLAN了。
第三部分:几点总结
1.你需要首先规划出你的网络拓扑而不是先去研究VLAN在Linux上如何配置以及如何实现;
2.你需要深入理解VLAN设计的初衷,该配置哪些东西;
3.你需要知道对于VLAN哪些概念是核心,哪些概念并不是必须的。
4.不管基于什么平台配置VLAN,只有两点是必须的:a.哪些端口属于哪个VLAN;b.哪个端口是级联端口,属于多个VLAN。
5.其它的都不用去死记硬背,都是浮云…
第四部分: 应用实例汇总
- 注意:
默认情况下,所有物理接口都是VLAN 1的子接口
brctl addbr vlan_default_1
brctl addif vlan_default_1
以下更改接口VLAN的操作,都需要先把接口从vlan 1中删除
brctl delif vlan_default_1
1)创建VLAN接口
VLAN接口的创建实现为创建一个网桥设备
brctl addbr vlan_access_100
或者
ip link add vlan_access_100 type bridge
添加VLAN子接口:
brctl addif vlan_access_100 eth0
brctl addif vlan_access_100 eth1
2)设置物理接口ACCESS VLAN
在物理接口配置access vlan时,将物理口加入对应vlan id的网桥vlan_access_(id),如果网桥不存在,首先创建网桥。
brctl addbr vlan_access_100
将eth0设置为access接口
brctl addif vlan_access_100 eth0
将eth1设置为access接口
brctl addif vlan_access_100 eth1
说明:在步骤1)和 2)中都可创建access vlan,添加子接口,作用相同。
3)设置物理口TRUNK VLAN
比如将eth1更改为trunk接口,允许通过的VLAN ID为 100,101,102。
由于在 2)中配置了eth1的access模式vlan 100,首先删除它
brctl delif vlan_access_100 eth1
创建100,101,102三个vlan接口
ip link add link eth1 name eth1.100 type vlan id 100
ip link add link eth1 name eth1.101 type vlan id 101
ip link add link eth1 name eth1.102 type vlan id 102
由于在 2)中创建了access vlan 100,与此处trunk中被允许的vlan 100冲突,将其删除
brctl delif vlan_access_100 eth0
brctl delbr vlan_access_100
创建新的bridge,将access vlan 100与trunk vlan 100合并,将之前删除的eth0 access 100添加回来
brctl addbr vlan_trunk_100
brctl addif vlan_trunk_100 eth0
brctl addif vlan_trunk_100 eth1.100
注意,如果之后要更改eth1为access接口,需要还原eth0的access vlan 100的配置。
4)设置物理接口TRUNK的PVLAN
配置eth1接口的pvlan为110
ip link add link eth1 name eth1.110 type vlan id 110
brctl addbr vlan_access_pvlan_110
brctl addif vlan_access_pvlan_110 eth1
假设eth2接口配置了access vlan 110
brctl addif vlan_access_pvlan_110 eth2
如果数据包的VLAN id等于trunk接口的pvlan,在由trunk发出时,去掉其vlan字段。
5)创建Hybrid接口
配置eth2为hybrid模式接口,允许通过的VLAN ID为 200,201,202。
创建200,201,202三个vlan接口(带有此三个vlan tag的数据包可接收)
ip link add link eth2 name eth2.200 type vlan id 200
ip link add link eth2 name eth2.201 type vlan id 201
ip link add link eth2 name eth2.202 type vlan id 202
配置eth2接口的pvid 205,接收到不带vlan的数据包,依据vlan 205转发
brctl addbr vlan_access_205
brctl addif vlan_access_205 eth3 // 假定eth3配置了access vlan 205
brctl addif vlan_access_205 eth2
与trunk模式不同,hybrid模式可指定发送时是否带有vlan tag,比如vlan 200/201带tag发送、vlan 202不带tag。
brctl addbr vlan_hybrid_tag_200
brctl addif vlan_hybrid_tag_200 eth2.200
brctl addif vlan_hybrid_tag_200 eth4 // 假定eth4配置了access vlan 200
brctl addbr vlan_hybrid_tag_201
brctl addif vlan_hybrid_tag_201 eth2.201
brctl addif vlan_hybrid_tag_201 eth5 // 假定eth5配置了access vlan 201
brctl addbr vlan_hybrid_untag_202
brctl addif vlan_hybrid_untag_202 eth2
brctl addif vlan_hybrid_untag_202 eth6 // 假定eth6配置了access vlan 202
6)创建supervlan与subvlan
创建supervlan,supervlan不能包含物理口
brctl addbr vlan_supervlan_200
ifconfig vlan_supervlan_200 192.168.1.1
创建子vlan,添加子接口;将子vlan添加到supervlan中
brctl addbr vlan_subvlan_201
brctl addif vlan_subvlan_201 eth10
brctl addif vlan_subvlan_201 eth11
brctl addif vlan_supervlan_200 vlan_subvlan_201
7)创建 QinQ
创建service vlan接口eth0.300,vlan id等于300。协议proto选择802.1ad时TPID为0x88a8,proto选择802.1Q时,TPID为0x8100。
ip link add link eth0 eth0.300 type vlan proto 802.1ad id 300
或者
ip link add link eth0 eth0.300 type vlan proto 802.1Q id 300
添加customer vlan 301.
ip link add link eth0.300 eth0.300.301 type vlan proto 802.1Q id 301
第五部分:vlan filtering 过滤功能
网桥的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
配置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
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
————————————————
版权声明:本文为CSDN博主「dog250」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dog250/article/details/7354590
版权声明:本文为CSDN博主「redwingz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_20184565/article/details/80626694
版权声明:本文为CSDN博主「redwingz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_20184565/article/details/80704355