Neutron L3实现概述

在OpenStack中,三层路由器的实现是利用了Linux的NameSpace和iptable来完成的。每当云平台上创建一个路由器,Neutron就会在网络节点创建一个与之对应的NameSpace。

可以通过命令ip netns来查看:

# ip netns
qrouter-90f0d137-0de8-4357-bd9a-dabd80d652cb
qdhcp-f58b0369-5b2d-4201-bb1a-407a81893d9f
qdhcp-df19d4e9-6b78-4dd8-8c4e-466f9e30fb85
qdhcp-dc3e56a8-faa4-406c-b22e-0de73b52019b
qdhcp-d2fbb1f5-e432-4701-bc3f-cabe90957287
qdhcp-c58f8728-2114-4249-a821-30da6af6b3fa
qdhcp-9ba6ed0f-7e17-4c66-833b-0a92fc37e77a
qdhcp-9705c672-ecd7-476b-b4f0-a364740fc515
qdhcp-5f938860-9350-48f9-b92d-fe1fc389584c
qdhcp-4f35fc11-c2e4-4938-8128-016d3287c5a0
qdhcp-29f0e9a2-9b53-4d84-baf6-188bbcd0be70
qdhcp-06dd3a27-1fc4-477f-b47a-69b028459062

其中以qrouter开头的namespace就是创建路由器时生成的。云平台的路由器功能就在这个NameSpace中实现。在NameSpace中可以看到所有挂到对应路由器上的子网的网关接口。

# ip netns exec qrouter-90f0d137-0de8-4357-bd9a-dabd80d652cb ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: qg-0111fc8a-1f@if54: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:16:3e:28:aa:b9 brd ff:ff:ff:ff:ff:ff
    inet 10.180.52.145/16 brd 10.180.255.255 scope global qg-0111fc8a-1f
       valid_lft forever preferred_lft forever
    inet 10.180.52.146/32 brd 10.180.52.146 scope global qg-0111fc8a-1f
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe28:aab9/64 scope link 
       valid_lft forever preferred_lft forever
3: qr-bb4a7359-70@if55: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:16:3e:8e:19:47 brd ff:ff:ff:ff:ff:ff
    inet 191.168.1.1/24 brd 191.168.1.255 scope global qr-bb4a7359-70
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe8e:1947/64 scope link 
       valid_lft forever preferred_lft forever
4: qr-24cd19df-96@if56: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:16:3e:8c:34:06 brd ff:ff:ff:ff:ff:ff
    inet 191.168.2.1/24 brd 191.168.2.255 scope global qr-24cd19df-96
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe8c:3406/64 scope link 
       valid_lft forever preferred_lft forever
5: qr-18ffb623-03@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:16:3e:5f:f3:47 brd ff:ff:ff:ff:ff:ff
    inet 190.168.1.1/24 brd 190.168.1.255 scope global qr-18ffb623-03
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe5f:f347/64 scope link 
       valid_lft forever preferred_lft forever
6: qr-d69819ce-3f@if58: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:16:3e:10:d7:79 brd ff:ff:ff:ff:ff:ff
    inet 190.168.2.1/24 brd 190.168.2.255 scope global qr-d69819ce-3f
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe10:d779/64 scope link 
       valid_lft forever preferred_lft forever

所以对应网络上所有发往子网网关的流量都会进入到这个namespace,在这个namespace中进行路由转发或NAT转换。
namespace中的路由:

# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.180.0.1      0.0.0.0         UG    0      0        0 qg-0111fc8a-1f
10.180.0.0      0.0.0.0         255.255.0.0     U     0      0        0 qg-0111fc8a-1f
190.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 qr-18ffb623-03
190.168.2.0     0.0.0.0         255.255.255.0   U     0      0        0 qr-d69819ce-3f
191.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 qr-bb4a7359-70
191.168.2.0     0.0.0.0         255.255.255.0   U     0      0        0 qr-24cd19df-96

namespace中的NAT规则(iptables规则):

# iptables -nL -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
neutron-l3-agent-PREROUTING  all  --  0.0.0.0/0            0.0.0.0/0           

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
neutron-l3-agent-OUTPUT  all  --  0.0.0.0/0            0.0.0.0/0           

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
neutron-l3-agent-POSTROUTING  all  --  0.0.0.0/0            0.0.0.0/0           
neutron-postrouting-bottom  all  --  0.0.0.0/0            0.0.0.0/0           

Chain neutron-l3-agent-OUTPUT (1 references)
target     prot opt source               destination         
DNAT       all  --  0.0.0.0/0            10.180.52.146        to:191.168.2.3

Chain neutron-l3-agent-POSTROUTING (1 references)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ! ctstate DNAT

Chain neutron-l3-agent-PREROUTING (1 references)
target     prot opt source               destination         
REDIRECT   tcp  --  0.0.0.0/0            169.254.169.254      tcp dpt:80 redir ports 9697
DNAT       all  --  0.0.0.0/0            10.180.52.146        to:191.168.2.3

Chain neutron-l3-agent-float-snat (1 references)
target     prot opt source               destination         
SNAT       all  --  191.168.2.3          0.0.0.0/0            to:10.180.52.146

Chain neutron-l3-agent-snat (1 references)
target     prot opt source               destination         
neutron-l3-agent-float-snat  all  --  0.0.0.0/0            0.0.0.0/0           
SNAT       all  --  0.0.0.0/0            0.0.0.0/0            to:10.180.52.145
SNAT       all  --  0.0.0.0/0            0.0.0.0/0            mark match ! 0x2/0xffff ctstate DNAT to:10.180.52.145

Chain neutron-postrouting-bottom (1 references)
target     prot opt source               destination         
neutron-l3-agent-snat  all  --  0.0.0.0/0            0.0.0.0/0            /* Perform source NAT on outgoing traffic. */

旁挂虚拟防火墙

由上文可以知道,云平台上进入到路由器的流量在底层会进入网络节点的namespace,在namespace中经过处理后进行转发,从namespace中的对应接口出去。
01.png

如果要在路由器上旁挂一个防火墙,让流量在经过路由器(也就是namespace)的时候能够受到防火墙的控制,那么就需要将进入namespace的流量引导进防火墙中,虚拟防火墙将流量处理完成后再扔回到namespace中即可。
为了实现namespace中流量路径的修改,这里使用了linux的ip rule

ip rule

linux默认情况下有3条路由策略规则,可以通过命令ip rule查看:

# ip rule
0:    from all lookup local 
32766:    from all lookup main 
32767:    from all lookup default

其中locla,main,default分别是三张路由表,这三条的规则大意是所有流量(from all)都分别查看这三张路由表。
数据流量进行路由匹配时,会按照从上到下的顺序进行匹配,local中包含了本地直连路由,所以也就可以理解直连路由的高优先级是怎么来了。如果上一张表没有匹配的路由,会依次到下一张表去查找。

namesapce中实现路由器转发用的路由都在main表中,可以通过命令ip route ls table main查看,其结果和route -n一样,只是表现的形式不一样。

# ip route ls table main
default via 10.180.0.1 dev qg-0111fc8a-1f 
10.180.0.0/16 dev qg-0111fc8a-1f  proto kernel  scope link  src 10.180.52.145 
190.168.1.0/24 dev qr-18ffb623-03  proto kernel  scope link  src 190.168.1.1 
190.168.2.0/24 dev qr-d69819ce-3f  proto kernel  scope link  src 190.168.2.1 
191.168.1.0/24 dev qr-bb4a7359-70  proto kernel  scope link  src 191.168.1.1 
191.168.2.0/24 dev qr-24cd19df-96  proto kernel  scope link  src 191.168.2.1 

在上述三条规则中的local和main之间,用户可以自定义2百多条规则,其优先级高于main。所以通过这种方法,就能让数据流量优先匹配引流用的路由。

引流策略

我们的目的是要把从子网网关接口进入到namespace中的流量转发到虚拟防火墙,所以需要将虚拟防火墙所在的子网也挂到路由器上。
02.png
然后我们配置类似这样的规则:

流量入接口为子网网关设备的流量,转发到vFW的接口IP

通过ip rule的实现就是:
先指定策略规则:

ip rule add iif qr-24cd19df-96 table vFW_Int
ip rule add iif qr-bb4a7359-70 table vFW_Int
...

其中iif的意思就是指定流量入口设备,这里我们指定为路由器上的子网接口,不包括vFW所在的子网,这个子网只是用来将vFW连接namespace用的。这些规则查找的路由表统一为vFW_int。
在路由表vFW_int中创建一条下一跳为vFW接口IP的默认路由:

ip route add 0.0.0.0/0 via <vfw IP> table vFW_int

于是规则就变为

# ip rule
0:    from all lookup local 
32764:    from all iif qr-bb4a7359-70 lookup vFW_int 
32765:    from all iif qr-24cd19df-96 lookup vFW_int 
32766:    from all lookup main 
32767:    from all lookup default 

这样进入到namespace的流量就被转发到vFW中了。vFW中的流量再送回namespace中后,由于入接口不在上述规则中,流量就会去匹配main表,回到原来的路径当中。

Floating IP(NAT)

通过上面的操作后,内部子网的虚拟机之间就能相互访问且受到防火墙的安全保护。但是如果是想要通过Floating IP访问,就会出现问题,这里涉及到iptable的NAT实现机制。
NAT的规则都在iptable的nat表中,而nat表只有每个连接的第一个数据包才会去匹配规则,之后linux的ip conntrack会记录这个连接的状态,后续如果是同一个连接的数据包过来,就会直接根据第一个数据包的匹配结果去执行,而不会再次去匹配nat表,包括PREROUTING几点和POSTROUTING阶段。
于是问题出在哪呢?就出现在第一个数据包进来,会被转发到vFW,而这个转发是不需要做NAT的,导致vFW再把这个数据包送回namespace后,就直接转发了,而不会再尝试做NAT。
这里我的解决办法是通过iptable的zone去隔离这两个数据包,所以整个拓扑就变成了下图这样。
03.png

通过ip rule指定路由器上的内部子接口进来的流量转发到vFW的internal子网的接口上,将路由器上的网关接口进来的流量转发到vFW的ext子网的接口上。vFW也区分是内部转发的流量还是要出外网的流量,分别从两个子接口扔出去。这两,两个zone之间的ip conntrack连接是不可见的,也就能正常做NAT转发了。
设定iptables zone的方法是在iptables的raw表中添加类似如下的规则:

iptables -I PREROUTING -i qr-d69819ce-3f -j CT  --zone 1 -t raw

大意是从指定个接口进来的流量标记为zone1。