Contents

  Openstack允许不同租户创建自己的网络,并通过租户自己的Router连接到外网。网络是允许重叠的,即不同租户都可以创建了192.168.1.0/24这个网段,都有一个192.168.1.1的IP,这样就需要在底层实现对不同租户网络的隔离。

  Neutron(openstack中的网络管理模块)实现隔离的方式不止一种(根据不同的plugin而不同),我只讨论我所配置的Openstack(应该也是最觉的配置方式之一)的情况,即OVS_Neutron_Plugin,即选用Open vSwith作为虚拟网桥的方式,对处于不同物理节点上的ovs bridge,采用GRE隧道的方式连接(另外一种是Vlan)。

  根据查看Neutron相关部分代码和实验验证,得知Neutron是通过Vlan的方式来作网络隔离的,这一点也不意外。下表列出了某计算节点上br-int的端口配置情况,可以看到不同的网络分配了不同的tag值,这里的tag就是Vlan ID,所有从这些port进入的数据都会被加上相应的Vlan ID在交换机里传输和处理,不同的Vlan的端口是不会接收到其它Vlan的数据的,这样就在一个物理节点为解决了隔离问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
root@compute1:~# ovs-vsctl show
a9505710-5f04-4e25-88a5-03b04a367393
Bridge br-int
Port "qvo29608ccb-bb"
tag: 2
Interface "qvo29608ccb-bb"
Port br-int
Interface br-int
type: internal
Port "qvocaad8131-1c"
tag: 1
Interface "qvocaad8131-1c"
Port "qvoae9adf7e-04"
tag: 3
Interface "qvoae9adf7e-04"
Port patch-tun
Interface patch-tun
type: patch
options: {peer=patch-int}
Port "qvo222d72f1-80"
tag: 1
Interface "qvo222d72f1-80"
Port "qvo68d15feb-77"
tag: 3
Interface "qvo68d15feb-77"
ovs_version: "2.0.0"

  为什么说只是在一个物理节点上解决了隔离问题,因为Neutron为同一个网络在不同的物理节点上(比如控制节点和计算节点)上分配的Vlan ID是不一样的,这样做的原因我想一是为了部署的灵活,更重要的是为了充分地利用Vlan资源。因为Vlan ID最大只能到4096,在复杂的云网络中是比较稀缺的资源,不同的物理节点上自由分配Vlan ID可以使资源的利用最大化,特别是在系统的物理节点非常多的情况下。

  那么既然不同物理节点的Vlan分配不一致,但是不同物理节点上的虚拟网络必须要保持一致,这就需要一个翻译的过程。前面已经提到,不同节点间的虚拟网络是通过GRE隧道连接的,这个翻译正是在数据进出隧道的时候完成的。我们可以看Neutron在某计算节点的br-tun桥中为此而下属OpenFlow流规则。

1
2
3
4
5
6
7
root@compute1:~# ovs-ofctl dump-flows br-tun |grep "vlan"
cookie=0x0, duration=1302.92s, table=2, n_packets=4, n_bytes=300, idle_age=1285, priority=1,tun_id=0x1 actions=mod_vlan_vid:3,resubmit(,10)
cookie=0x0, duration=1304.725s, table=2, n_packets=3, n_bytes=210, idle_age=1290, priority=1,tun_id=0x3 actions=mod_vlan_vid:1,resubmit(,10)
cookie=0x0, duration=1303.878s, table=2, n_packets=6, n_bytes=468, idle_age=1288, priority=1,tun_id=0x4 actions=mod_vlan_vid:2,resubmit(,10)
cookie=0x0, duration=1303.035s, table=21, n_packets=0, n_bytes=0, idle_age=1303, hard_age=1302, priority=1,dl_vlan=3 actions=strip_vlan,set_tunnel:0x1,output:2
cookie=0x0, duration=1303.994s, table=21, n_packets=0, n_bytes=0, idle_age=1303, hard_age=1302, priority=1,dl_vlan=2 actions=strip_vlan,set_tunnel:0x4,output:2
cookie=0x0, duration=1304.784s, table=21, n_packets=0, n_bytes=0, idle_age=1304, hard_age=1302, priority=1,dl_vlan=1 actions=strip_vlan,set_tunnel:0x3,output:2

  可以看到此Openstack下有三个租户网络,以粗体部分为例,解释一下这些流规则的语义:第4条规则,当有vlan id = 3的数据准备进入隧道离开本节点到达对岸节点的时候,将会去掉原有的Vlan ID,而加上tun id =1;第1条规则,则是相反的操作,当有tun id=1的数据从隧道出来的时候,将会为其加上vlan id=3,再由下一级流表进行后续处理,这样就在一个节点上完成了vlan id=3 <—> tun id=1的翻译(转换)。

  我们再来看此隧道对面的节点(为控制节点)的隧道又是如何配置的呢?

1
2
3
4
5
6
7
8
9
root@controller:~# ovs-ofctl dump-flows br-tun |grep vlan
cookie=0x0, duration=1398.155s, table=2, n_packets=0, n_bytes=0, idle_age=1398, priority=1,tun_id=0x1 actions=mod_vlan_vid:1,resubmit(,10)
cookie=0x0, duration=1397.476s, table=2, n_packets=0, n_bytes=0, idle_age=1397, priority=1,tun_id=0x5 actions=mod_vlan_vid:2,resubmit(,10)
cookie=0x0, duration=1396.634s, table=2, n_packets=0, n_bytes=0, idle_age=1396, priority=1,tun_id=0x3 actions=mod_vlan_vid:4,resubmit(,10)
cookie=0x0, duration=1397.033s, table=2, n_packets=0, n_bytes=0, idle_age=1397, priority=1,tun_id=0x4 actions=mod_vlan_vid:3,resubmit(,10)
cookie=0x0, duration=1396.687s, table=21, n_packets=4, n_bytes=300, idle_age=1385, priority=1,dl_vlan=4 actions=strip_vlan,set_tunnel:0x3,output:2
cookie=0x0, duration=1397.139s, table=21, n_packets=6, n_bytes=468, idle_age=1383, priority=1,dl_vlan=3 actions=strip_vlan,set_tunnel:0x4,output:2
cookie=0x0, duration=1397.537s, table=21, n_packets=0, n_bytes=0, idle_age=1397, priority=1,dl_vlan=2 actions=strip_vlan,set_tunnel:0x5,output:2
cookie=0x0, duration=1398.218s, table=21, n_packets=4, n_bytes=300, idle_age=1380, priority=1,dl_vlan=1 actions=strip_vlan,set_tunnel:0x1,output:2

  可以看到在控制节点上,将tun id 1 翻译成了vlan id =1,证明在不同的物理节点上为不同的网络分配Vlan确实是不同的。

  下图是Neutron实现整个隔离过程的总结,两台虚拟机属于同一虚拟网络,但它们在各自的节点上分配的Vlan Id却不同,对过各自的Tunnel交换机对Vlan <—> GRE的转换,实现一致性。

  接下来将分析一旦OVS网桥接上SDN控制器将是什么情况。从某种意义上,我们也可以说Neutron其实就是一个SDN的控制器,因为通过它可以进行端口的创建和删除,流规则的配置等操作,但是可惜的是它不是通过开放的OpenFlow和OF-Config来实现这些操作的,而是通过OVS特有的接口OVSDB的方式来实现的。对研究OpenFlow的人来说,常常需要将Neutron所创建的这些OVS Bridge连接上OpenFlow的控制器。这里选用的是Floodlight,它是Big Switch公司控制器的开源版,当OVS Bridge连上Openflow控制器之后,它就由一个OpenFlow hybrid的混合交换机变成一个Openflow交换机,它的行为就遵从OpenFlow协议的规范,在每个交换机里的流规则不再是一条简单的Normal动作通配一切,而是由控制器对每一条流的源/目的等信息进行解析再根据策略实时下发转发规则,这对网络流量的行为监控和管理都十分有利。但在下发任何规则之前,Floodlight将会将接入的交换机原有的规则全部清空(也许也可以保留或者部分保留,待研究,但这不是根本问题)。

  清空规则对tunnel的影响是最大的,如前所示Neutron为了保证网络的隔离和不同物理节点上虚拟网络的连续性,在br-tun的bridge上配置了一系列的转换规则,清除这些规则意味着转换关系的丢失。如果只是转换关系的丢失,我们完全可以通过控制器再次添加相同的OpenFlow流来弥补,但关键的问题还不在于此。前面提到Neutron为每一个租户网络分配了不同的Vlan,属于某个租户网络的Port都统一打上了对应的Vlan tag,以使每一个出现在网络中的数据帧都有对应的vlan标记。但是这种为交换机access port加上vlan tag就可以为数据包自动加上相应vlan标记的方式是传统网络设备的特性,并不是OpenFlow规范所支持的内容,也就是说,前面提到过的OVS Bridge上的tag(如下),在OVS作为OpenFlow交换机的时候是无意义的(除非使用非OpenFlow规范内的Normal动作)。  

1
2
3
4
Bridge br-int
  Port "qvo68d15feb-77"
tag: 3
Interface "qvo68d15feb-77"

  也就是说,一旦将OVS Bridge连上控制器,由虚拟主机进入交换机中的数据包将不再被加上Vlan tag,网络隔离被破坏。事实上我们也可以从wireshark抓取的GRE封装包来印证,这是未连接控制器的数据时,由Vlan3的虚拟机发出的数据包。可以看到只有Gre隧道的key=0x1,而封装的以太帧中也没有Vlan信息(进入br-int时应是有的,但是进入tunnel的时候被strip_vlan动作去掉了,参见前文),符合前面的分析。

  而一旦连接上控制器,数据包没有Vlan信息(进入br-int就没有),Gre key =0(没有相应的规则添加tun id了),亦即不再有任何标识来隔离不同网络。

  更进一步地,为了证明我们的分析,现在只将br-tun连接控制器,这时我们可以看到数据包变成了如下情况:

  因为控制器清除了br-tun上的转换信息,所以隧道数据gre key = 0,没有被设置,但是br-int并没有连接控制器,它依然是一个openflow hybrid的交换机,里面只有一条规则,即对所有数据包执行Normal动作,OpenFlow对Normal没有规范,完全由厂商自行设计实现,代表传统转发行为,所以它端口的vlan tag依然有效,GRE封装的以太帧类型是802.1Q(即Vlan),其ID=3。

  至此,问题的根源已经明了,那就是ovs端口上的tag标签只是一种传统交换机的特性,不是OpenFlow规范里的内容。虽然OVS是我们最常用的虚拟OpenFlow交换机,但是它却远不只是一个OpenFlow交换机,它还支持许多传统交换机的功能,比如设置Mirror,它也有自己特性,比如OVSDB接口。而端口的Vlan tag刚好是一个OpenFlow之外的特性。

  事实上我们查看OpenFLow的协议文档(1.3.2版本)关于端口配置的部分(7.2.1节:Port Structures):

1
2
3
4
5
6
enum ofp_port_config {
OFPPC_PORT_DOWN = 1 << 0, /* Port is administratively down. */
OFPPC_NO_RECV = 1 << 2, /* Drop all packets received by port. */
OFPPC_NO_FWD = 1 << 5, /* Drop packets forwarded to port. */
OFPPC_NO_PACKET_IN = 1 << 6 /* Do not send packet-in msgs for port. */
};

  较早的1.0版本的OpenFlow中对Port的描述为

1
2
3
4
5
6
7
8
9
10
11
12
13
/*Flags to indicate behavior of the physical port. These flags are
* used in ofp_phy_port to describe the current configuration. They are
* used in the ofp_port_mod message to configure the port's behavior.
*/
enum ofp_port_config {
OFPPC_PORT_DOWN = 1 << 0, /* Port is administratively down. */
OFPPC_NO_STP = 1 << 1, /* Disable 802.1D spanning tree on port. */
OFPPC_NO_RECV = 1 << 2, /* Drop all packets except 802.1D spanning tree packets. */
OFPPC_NO_RECV_STP = 1 << 3, /* Drop received 802.1D STP packets. */
OFPPC_NO_FLOOD = 1 << 4, /* Do not include this port when flooding. */
OFPPC_NO_FWD = 1 << 5, /* Drop packets forwarded to port. */
OFPPC_NO_PACKET_IN = 1 << 6 /* Do not send packet-in msgs for port. */
};

都没有关于Vlan的对应设置,也印证了OFport没有Vlan tag功能的结论。

  这里需要强调的是,并没有说OpenFlow不支持VLAN,大家都知道OpenFlow的Match里是有Vlan ID和Vlan codepoint等项匹配的。这里所说的只是OpenFlow定义的交换机端口里没有关于Vlan的配置项,而Match和Action里则都有相应的内容。

  其实OVS_Neutron_plugin本来就是为OVS设计而不是为控制器设计的,强行为其加上控制器会破坏其功能也就不中为怪了。就我已知Neutron有过为Floodlight设计的Plugin,但是功能较为简单,代码更新状况不是很好,可能也与早期的OF功能比较简单有关系;另外还有ryu控制器的Plugin,但是我没有特别调查过不是特别了解。

  如果一定要在控制器环境下使用Vlan来进行隔离,暂时想到两种思路,一是使数据进入OVS之前就为其加上Vlan tag,比如通过Linux Bridge,这应该是一种较为简单有效的方式,但是需要修改Neutron;另外一种就是在控制器上修改,使每个Port的动作都附加上额外的Mod_vlan(Strip Vlan)动作,但是这需要控制器对云环境有感知能力,即需要知道Port对应的网络才行。

Contents