Just For Coding

Keep learning, keep living …

基于PVLAN实现VMware vSphere环境DVS东西向防护

之前的文章<<VMware vSphere东西向网络防护>>介绍了基于不同VLAN来实现二层网络内虚拟机的东西向隔离防护技术。这种方法在每台ESXi主机上对VLAN ID的管理和分配较为烦琐,本文介绍基于PVLAN实现二层网络内东西向隔离防护的方法。由于VMware vSphere环境中只有DVS(Distributed Virtual Switch,分布式虚拟交换机)支持PVLAN,所以这种方法只能使用在DVS中。

PVLAN(Private VLAN,专用VLAN)技术是为了解决网络内需要大量隔离而VLAN ID不足的情况。它采用两层VLAN隔离技术,即上行的主VLAN(Primary VLAN)和下行的辅助VLAN(Secondary VLAN)。对上行设备而言,只可见主VLAN,而不可见辅助VLAN。辅助VLAN包含两种类型:隔离VLAN(isolated VLAN)和团体VLAN(community VLAN)。支持PVLAN的交换机的端口对应有三种类型: 隔离端口(isolated port),团体端口(community port),混杂端口(promiscuous port),它们分别对应隔离VLAN,团体VLAN和主VLAN。在隔离VLAN中,隔离端口之间不能通信,隔离端口只能和混杂端口通信。在团体VLAN中,团体端口之间以及团队端口与混杂端口之间都可以通信。

可以看到,隔离端口的特性天生就满足二层网络完全隔离的要求,为了能使他们通信,我们需要虚拟网络设备完成隔离端口间的数据包中继。

我们的测试环境由两台ESXi主机组成,通信双方虚拟机t1t2位于同一台ESXi主机,属于同一个二层网络,IP分别为:10.95.49.15010.95.49.151, 两台虚拟机的网络接口都接到分布式交换机DSwitchDPG-original端口组中。此时拓扑如图:

t1访问t2, 访问外网都能够成功:

下面我们通过设置PVLAN实现二层网络隔离。

我们在DSwitch上编辑PVLAN。添加一个专用VLAN,设置主VLAN ID为1000,辅助VLAN ID为1001,类型为隔离VLAN。如图:

接着,创建分布式端口组:DPG-isolated, VLAN类型设置为专用VLAN,专用VLAN ID选择隔离(1000,1001),如图:

再创建分布式端口组:DPG-promisc, VLAN类型选择专用VLAN,专用VLAN ID选择混杂(1000,1000),如下图所示:

接着,对DPG-promisc进行编辑,需要将安全选项混杂模式伪传输设置为接受,否则vSphere DVS将会丢弃符合特定条件的数据包,具体可以参考之前文章<<VMware vSphere虚拟网络防护>>对于这两个选项的解释, 以及这篇文章

如下图:

我们将两个虚拟机的网络接口改到端口组:DGP-isolated中,此时拓扑如图:

我们再次从t1访问t2。由于隔离端口之间不能通信,因而访问失败:

因为隔离端口能够与混杂端口通信,我们需要在混杂端口上接入虚拟网络设备,由该设备实现网络转发及防护功能,完成隔离端口之间的通信中继。PVLAN网络中增加了自己的VLAN标识,为了保证能够与原始外界的设备通信,我们需要在虚拟网络设备中将网络数据包转换为没有设置PVLAN之前的状态,因而我们需要虚拟网络设备的一个网络接口接入原有的端口组中, 整体架构如图:

下面我们看具体操作。

我们创建一台具备两个网络接口的虚拟机VNF, 第一个接口接入端口组DPG-promisc, 第二个接口接入t1,t2原来的端口组:DPG-original。因为t1,t2访问外部的流量需要经由VNF虚拟机的eth1接口发出,因而DPG-original也需要将端口组的混杂模式伪传输两个安全选项设置为接受。此时拓扑如图:

我们在VNF设备内使用OpenvSwitch实现转发逻辑。

首先,我们需要将两个网络接口添加进br0

1
2
3
ovs-vsctl add-br br0
ovs-vsctl add-port br0 eth0
ovs-vsctl add-port br0 eth1

查看ofport情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost ~]# ovs-ofctl show br0
OFPT_FEATURES_REPLY (xid=0x2): dpid:0000005056a023f3
n_tables:254, n_buffers:256
capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP
actions: output enqueue set_vlan_vid set_vlan_pcp strip_vlan mod_dl_src mod_dl_dst mod_nw_src mod_nw_dst mod_nw_tos mod_tp_src mod_tp_dst
 1(eth0): addr:00:50:56:a0:48:ee
     config:     0
     state:      0
     current:    10GB-FD COPPER
     advertised: COPPER
     supported:  1GB-FD 10GB-FD COPPER
     speed: 10000 Mbps now, 10000 Mbps max
 2(eth1): addr:00:50:56:a0:23:f3
     config:     0
     state:      0
     current:    10GB-FD COPPER
     advertised: COPPER
     supported:  1GB-FD 10GB-FD COPPER
     speed: 10000 Mbps now, 10000 Mbps max
 LOCAL(br0): addr:00:50:56:a0:23:f3
     config:     PORT_DOWN
     state:      LINK_DOWN
     speed: 0 Mbps now, 0 Mbps max
OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0

开启STP, 避免形成二层环路,造成网络中断:

1
ovs-vsctl set bridge br0 stp_enable=true

在OVS中,在如下这种流表条件下:

1
in_port=1 actions=output:1

从端口1中接收的数据包是不能由端口1发送出去的,需要使用特殊的端口IN_PORT来处理。

t1访问t2的流量需要由br0的端口eth0进入并再次由它发出,而访问外部的设备时需要由eth1端口发出,因而需要对源MAC地址进行学习来判断应该从哪个端口发出。

我们本身共使用三张流表:

  • 表0: 用于处理ARP数据包,通过ARP学习源MAC地址与PORT的对应关系,将学习到的流保存到表10中, 并将其他协议数据包交由表10和表11处理。
  • 表10: 存储学习到的流,当数据包匹配到表10中的流时,它将学习到的PORT(即应从该PORT发出该数据包)保存到REG0中。
  • 表11: 根据REG0中存储的PORT信息,修改入端口信息,保证出入端口不相同,从而可以成功发送。

添加表0的流:

1
2
3
ovs-ofctl add-flow br0 "table=0, priority=10,arp,in_port=1 actions=learn(table=10,hard_timeout=300,priority=10,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG0[0..15]),output:2,output:IN_PORT"
ovs-ofctl add-flow br0 "table=0, priority=10,arp,in_port=2 actions=learn(table=10,hard_timeout=300,priority=10,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG0[0..15]),output:1"
ovs-ofctl add-flow br0 "table=0, priority=9, actions=resubmit(,10), resubmit(,11)"

添加表11的流: 对于从eth1进入的数据包我们直接从eth0转发走:

1
ovs-ofctl add-flow br0 "table=11,priority=11,in_port=2 actions=output:1”

根据REG0中保存的出端口信息,修改入端口并由相应出端口转发:

1
2
ovs-ofctl add-flow br0 "table=11,priority=10,reg0=1,actions=set_field:2->in_port_oxm, output:1"
ovs-ofctl add-flow br0 "table=11,priority=10,reg0=2,actions=set_field:1->in_port_oxm, output:2”

没有匹配到学习到的MAC,则从两个端口都转发:

1
ovs-ofctl add-flow br0 "table=11,priority=9,in_port=1 actions=output:2,output:IN_PORT"

此时,再次从t1访问t2,访问成功:

VNF上的eth0接口上抓包可以看到数据通过了VNF进行转发:

再次访问外网,访问也成功:

VNF上的eth1接口上的抓包结果显示数据包通过了eth1接口转发:

整个DVS可以只创建一台VNF虚拟机完成所有二层网络整体流量的转发和过滤,但这样也会导致本可以在ESXi主机内完成转发的数据包需要先发送到VNF所在ESXi主机上完成过滤和转发再回到原有ESXi主机内,这给物理网络增加了额外负担。

除了这种形式外,也可以在每台ESXi主机上创建一台VNF,该VNF负责该ESXi主机上的虚拟机的流量过滤,这种情形下需要给不同的ESXi主机分配不一样的PVLAN ID,整体架构如图:

但是由于DPG-original端口组需要设置为混杂模式,处于混杂模式的端口组中的网络接口会接收到穿过虚拟交换机的所有网络流量,因而也会导致许多网络数据包在不同的ESXi主机之间进行没有必要的传输,造成网络吞吐的衰退。

之所以需要设置为混杂模式,是由于VMware vSphere的虚拟交换机(VSS/DVS)不支持Mac-Learning机制,因为vSphere平台本身就知道网络接口的MAC地址。然而在虚拟化嵌套或者我们这种由虚拟网络设备中转二层数据包的情况下,需要由一个网络接口发送大量的不同MAC地址的数据包, vSphere平台并不知道该接口会发送哪些MAC地址的数据包。VMware官方为了解决这个问题,开发了一个dvfilter-maclearn模块,具体可参考文章

dvfilter-maclearn需要安装在ESXi主机上:

1
2
3
4
5
6
7
[root@ESXi-2:~] esxcli software vib install -v /vmware-esx-dvfilter-maclearn-1.0.vib -f
Installation Result
   Message: Operation finished successfully.
   Reboot Required: false
   VIBs Installed: VMware_bootbank_vmware-esx-dvfilter-maclearn_1.00-1.00
   VIBs Removed:
   VIBs Skipped:

安装完成后,执行下述命令可以看到dvfilter-maclearn的安装情况:

1
/sbin/summarize-dvfilter

结果如图:

为了使这个dvFilter生效,需要针对相应虚拟机的网卡进行过滤设置。我们需要在相应虚拟机的VMX配置文件中添加:

1
2
ethernet#.filter4.name=dvfilter-maclearn
ethernet#.filter4.onFailure=failOpen

#为网卡的顺序号,第一个网卡为0

也可以通过VCenter进行配置,如图:

生效后再次执行/sbin/summarize-dvfilter,查看dvfilter加载情况,可以看到dvfilter-maclearn已经生效,在虚拟机相应网卡上抓包可以看到数据包有明显减少。

dvfilter-maclearn尽管可以提高部分性能还是存在一些问题,比如学习到的MAC地址永不过期等等,VMware后续又发布了一个更为完善的方案,具体可参考文章, 而在VSphere6.7中,Mac Learning已经是DVS的原生特性了,混杂模式也不需要再开启,具体参考文章, 这里不再详述。