ovn vlan南北向流量问题

前言

本文将对ovn支持vlan网络的南北向流量问题进行阐述,涉及到报文通过snat,dnat_and_snat访问外部网络。

相关技术

首先介绍一下南北向流量场景,会提到的几个技术点。上一篇文章提到的内容也会用到。

gateway

gateway顾名思义就是网关的意思,即处理内部流量和外部流量的实例。ovn对于网关的实现提出了两种类型:

  • L3 Gateway Routers: 集中式router。区别于dvr,它在实现上会将所有内部流量转发到集中式的router上。借用红帽的一个图:
    GR

配置该router指定其落在哪个chassis上:

ovn-nbctl set Logical_Router 01aa12e3-d2d4-4133-9577-166629aaf991 options:chassis=5cb6a97d-defe-4ec2-9247-d8951b3ed090
  • Distributed Gateway Ports(dgw_ports): 分布式网关port。即路由器外部网关lrp(logical_router_port),它是挂在dvr上的,所以是分布式的,也就是访问网关lrp的流量在节点本地就处理了。而访问外部网络时依然会将流量转发到网关lrp驻留的节点处理。通过给该lrp配置gateway_chassis来支持高可用,标记它peer为’chassis-redirect-port’(cr-lrp)。查询高可用的节点有哪些:
()[root@ovn-tool-0 /]# ovn-nbctl lrp-get-gateway-chassis lrp-860f48c9-d948-4d86-8a49-c9b00b1fe242
lrp-860f48c9-d948-4d86-8a49-c9b00b1fe242_5cb6a97d-defe-4ec2-9247-d8951b3ed090 3
lrp-860f48c9-d948-4d86-8a49-c9b00b1fe242_b4512432-6ff5-4914-bd8e-75f602e492a7 2
lrp-860f48c9-d948-4d86-8a49-c9b00b1fe242_94f12bc5-5987-4900-9b60-e8f2d4f18460 1

逻辑流表里,常用is_chassis_resident这个字段来判断网关lrp是否驻留在本节点。

对于两者,目前我们都有对应的使用场景,而本文提到的问题主要是使用dgw_ports的情况。

nat

ovn实现nat里,最常见的是:snat,dnat,dnat_and_snat。可以配置带状态(stateful)的nat或者无状态(stateless)

  • snat: 内部流量通过snat成外部网关ip地址,访问外部网络。
  • dnat: 外部网络的资源访问内部虚机的dnat地址,外部网关进行dnat转换成虚机ip,转发给虚机。
  • dnat_and_snat: 一般用于浮动ip,也就是内部虚机既可以被外部网络的资源访问,也可以主动访问外部网络。支持分布式和集中式两种,分布式需要external_mac,即fip的mac。分布式在网卡所在节点完成浮动ip的nat转换;而集中式需要转发到网关所在节点处理。
    ()[root@ovn-ovsdb-nb-2 /]# ovn-nbctl lr-nat-list neutron-6560713f-19c1-429d-a955-01fda8599acd
    TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
    dnat_and_snat 172.90.0.100 66.66.66.101
    dnat_and_snat 172.90.0.104 66.66.66.150 fa:16:3e:a7:14:e4 60d84737-5a96-4fa6-830a-52948461f619

注意:采用分布式浮动ip的场景,需要计算节点北向网桥br-ex能够访问外网;大规模下ct对性能的影响较大,建议dnat_and_snat配置为无状态的

redirect-type

提到这个参数之前,回顾ovn实现网关的流量的转发:本地节点完成路由,将流量通过隧道转发给网关节点,由网关节点发送给外部vlan网络。这里有个问题:本来vlan的流量被overlay发送给了网关节点,这并不是完整链路的vlan实现,而且会有mtu过小的问题,。

ovn社区做了不少尝试,希望将vlan模式做得更纯粹,不需要隧道封装这样的处理,比如说采用reside-on-redirect-chassis上一篇文章提到)通过localnet port转发给网关节点,不过限制就是所有vlan网络的路由变成集中式的。

  • East - West routing is no more distributed for the VLAN tagged localnet logical switches if ‘reside-on-redirect-chassis’ option is defined
  • ‘dnat_and_snat’ NAT rules with ‘logical_mac’ and ‘logical_port’ columns defined will not work for these logical switches.

后续又推出了这两个实现OVN: Enable E-W Traffic, Vlan backed DVR, OVN: Vlan backed DVR N-S, redirect packet via localnet port。简单介绍一下两者实现原理:

  • 东西向vlan dvr:OVN: Enable E-W Traffic, Vlan backed DVR,引入参数ovn-chassis-mac-mappings,该参数配置chassis的mac地址。在计算节点上完成路由策略,将src mac(目的网关地址)替换成chassis的mac,并标记目的网络vlan来实现东西向路由。并且不再使用is_chassis_resident参数来判断外部网关是否在本主机上来限制东西向路由,在计算节点间就处理掉了。

  • 南北向vlan dvr:OVN: Vlan backed DVR N-S, redirect packet via localnet port,该方案依赖上面的ovn-chassis-mac-mappings参数,新增了参数redirect-type 用来指定是否进行隧道封装,可选参数是overlay(默认)和bridged。指定bridged类型并配合chassis mac,在流量出计算节点的时候修改src mac为chassis的mac,并打上外部网络的vlan,通过localnet port将流量牵引到网关节点处理。

而redirect-type就能决定vlan网络的真实效果,采用bridged模式就能达到纯粹的vlan转发。而社区默认的配置仍然是overlay,究其原因,我的理解是社区还未完全解决bridged模式实现上的南北向流量问题。

本文后续提到的南北向流量问题,建立的基础是:外部网关转发采用redirect-type为bridged模式。

分布式浮动ip访问外网

场景:虚机通过分布式浮动ip去访问外网。

  • floatingip mac:fa:16:3e:e3:a1:f5
    路由器外部网关(172.90.0.102)mac:fa:16:3e:d6:42:62
    外部网络子网网关(172.90.0.1)mac:f8:bc:12:4e:44:dd

预期:报文nat转换后从本节点出去,下一跳为外部网络子网网关的mac,直接发送到外部网络。
实际:虚机访问外网流量正常,不过流量先去了外部网关所在节点,再转发出去。

报文分析

源节点北向网卡抓到的icmp request报文:

fa:16:3e:e3:a1:f5 > fa:16:3e:d6:42:62, ethertype 802.1Q (0x8100), length 102: vlan 2901, p 0, ethertype IPv4, 172.90.0.100 > 110.242.68.3: ICMP echo request, id 29185, seq 160, length 64

可以看到:在本机完成了nat:dst mac修改成外部网关地址,src mac为floatingip的mac地址,snat转换成floatingip。
也就是下一跳修改成了路由网关的地址,而不是外部网络的网关地址,所以会出现报文先到网关节点再转发出去。

逻辑流表分析

1.访问外网资源,在routing阶段会将外部网络子网网关172.90.0.1配置为下一跳reg0(但是后续流表未用到):

table=10(lr_in_ip_routing), priority=1, match=(ip4.dst == 0.0.0.0/0), 
action=(ip.ttl--; reg8[0..15] = 0; reg0 = 172.90.0.1; reg1 = 172.90.0.102; eth.src = fa:16:3e:d6:42:62;
outport = "lrp-85ac07a7-d9d7-4ddf-923b-59298d76d968"; flags.loopback = 1; next;)

2.在lr_in_arp_resolve阶段,会判断路由器网关是否在本节点,不在的话将目的mac改为路由器网关mac:

table=14(lr_in_arp_resolve ), priority=50 , 
match=(outport == "lrp-85ac07a7-d9d7-4ddf-923b-59298d76d968" && !is_chassis_resident("cr-lrp-85ac07a7-d9d7-4ddf-923b-59298d76d968")),
action=(eth.dst = fa:16:3e:d6:42:62; next;)

后续不再有修改目的mac的流表。而实际上在lr_in_arp_resolve阶段,还有一条流表是get_arp获取下一跳reg0的mac,因为先匹配了上面的流表,所以该流表未起作用:

table=14(lr_in_arp_resolve), priority=0, match=(ip4), action=(get_arp(outport, reg0); next;)

当然如果虚机和网关是同一个节点,也就不会出现问题。

源码分析

为什么会产生上述流表,查看了northd代码:如果分布式外部网关是bridged模式,默认会增加一条流表判断是否在本机,不在的话修改dst mac为网关mac,也就不会去get_arp下一跳的mac了:

if (!op->derived && op->od->l3redirect_port) {
const char *redirect_type = smap_get(&op->nbrp->options,
"redirect-type");
if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
/* Packet is on a non gateway chassis and
* has an unresolved ARP on a network behind gateway
* chassis attached router port. Since, redirect type
* is "bridged", instead of calling "get_arp"
* on this node, we will redirect the packet to gateway
* chassis, by setting destination mac router port mac.*/
ds_clear(match);
ds_put_format(match, "outport == %s && "
"!is_chassis_resident(%s)", op->json_key,
op->od->l3redirect_port->json_key);
ds_clear(actions);
ds_put_format(actions, "eth.dst = %s; next;",
op->lrp_networks.ea_s);

ovn_lflow_add_with_hint(lflows, op->od,
S_ROUTER_IN_ARP_RESOLVE, 50,
ds_cstr(match), ds_cstr(actions),
&op->nbrp->header_);
}
}

bridged模式额外增加的这条流表,其目的是默认在网关节点进行未知ip的mac查询或学习,避免非网关节点(可能与外部网络不通)直接发送到外部网络。但与此同时也影响了分布式浮动ip的实现,导致其变成snat阶段是集中式的了。

解决方案

目前没有好的解决方案,我们将路由器外部网关修改成默认的overlay模式,也就少了上述流表,直接转发出去了,不需要经过网关节点。
2022-09-19更新:
相关bug
社区正在提交patch试图解决这个问题。简单看了一下该方案,如果判断是bridge模式,则提前进行get_arp,避免dst mac被默认修改。

同路由器下访问浮动ip

场景:两虚机关联同一个路由器,和网关均在不同节点,其中一台虚机有集中式浮动ip。

  • vm1: 66.66.66.101,fip-172.90.0.100
    vm2: 66.66.66.45
    外部网关: 172.90.0.102/fa:16:3e:d6:42:62
    源节点chassis-mac: 9a:63:53:54:e5:4f

预期:vm2能正常访问vm1的浮动ip
实际:流量不通

报文分析

1.分析源节点发出去的请求报文,在北向抓request报文:

9a:63:53:54:e5:4f > fa:16:3e:d6:42:62, ethertype 802.1Q (0x8100), length 102: vlan 2901, p 0, ethertype IPv4, 66.66.66.45 > 172.90.0.100: ICMP echo request, id 65282, seq 599, length 64

vm2的request报文源ip是自身;而目的mac是外部网关mac,也就是说未进行snat发送给了外部网关。

2.分析网关节点处理后的报文,在南向抓request报文:

96:45:cc:7c:02:45 > fa:16:3e:c1:0b:f7, ethertype 802.1Q (0x8100), length 102: vlan 1147, p 0, ethertype IPv4, 66.66.66.45 > 66.66.66.101: ICMP echo request, id 65282, seq 969, length 64

这个报文是经过dnat后,目的ip/mac对应目的虚机,配置了虚机网络vlan。也就是说最终到达目标虚机的报文,源ip未做snat。
那么可以分析得出,目标虚机vm1收到报文后会将其当作同二层的报文给处理,直接回复给源虚机vm2。

3.继续抓目标节点的reply报文:

fa:16:3e:c1:0b:f7 > fa:16:3e:85:e1:85, ethertype 802.1Q (0x8100), length 102: vlan 1147, p 0, ethertype IPv4, 66.66.66.101 > 66.66.66.45: ICMP echo reply, id 65282, seq 1320, length 64

可以看到reply给了源虚机ip,证实了我们的分析。而后续源虚机vm2收到未知的reply报文,不会做处理。

逻辑流表分析

1.报文在网关节点处理时,在lr_in_dnat阶段,完成dnat转换,也就是目的地址修改为子网网段66.66.66.101:

table=6 (lr_in_dnat), priority=100  , 
match=(ip && ip4.dst == 172.90.0.100 && inport == "lrp-85ac07a7-d9d7-4ddf-923b-59298d76d968" && is_chassis_resident("cr-lrp-85ac07a7-d9d7-4ddf-923b-59298d76d968")),
action=(ct_dnat(66.66.66.101);)

2.在lr_in_ip_routing阶段,如果访问子网的话,会将outport设置为子网网关:

table=10(lr_in_ip_routing), priority=49 , 
match=(ip4.dst == 66.66.66.0/24),
action=(ip.ttl--; reg8[0..15] = 0; reg0 = ip4.dst; reg1 = 66.66.66.1; eth.src = fa:16:3e:d8:52:22;
outport = "lrp-e13835a9-34b5-45a0-9b48-3f931f0de86c"; flags.loopback = 1; next;)

3.在lr_in_arp_resolve阶段,配置目的地址为vm1的mac:

table=14(lr_in_arp_resolve), priority=100  ,
match=(outport == "lrpe13835a9-34b5-45a0-9b48-3f931f0de86c" && reg0 == 66.66.66.101), action=(eth.dst = fa:16:3e:c1:0b:f7; next;)

4.后续在egress的lr_out_snat阶段进行snat的流表如下:

table=1 (lr_out_snat), priority=153 , 
match=(ip && ip4.src == 66.66.66.0/24 && outport == "lrp-85ac07a7-d9d7-4ddf-923b-59298d76d968" && is_chassis_resident("cr-lrp-85ac07a7-d9d7-4ddf-923b-5929 8d76d968")),
action=(ct_snat(172.90.0.102))

也就是说要进行snat的话,必须满足两个条件:

  • a.匹配outport是外部网关lrp(可以理解为需要访问外网时才进行snat)
  • b.必须在外部网关所在节点执行

然而上述报文在完成dnat和路由后,显然不满足条件a了,所以源ip地址就不会进行snat转换成外部网关地址。

这里引申一下:同样条件,如果是访问分布式的浮动ip会怎么样?答案是与集中式浮动ip情况类似,流量也不通。
原因就是不满足条件b,源节点发出去的报文未进行snat转换发送给了浮动ip,即dst mac是浮动ip的mac:

9a:63:53:54:e5:4f > fa:16:3e:e3:a1:f5, ethertype 802.1Q (0x8100), length 102: vlan 2901, p 0, ethertype IPv4, 66.66.66.45 > 172.90.0.100: ICMP echo request, id 28673, seq 54240, length 64

同样的,同一个路由器跨网段虚机访问对方浮动ip依然有问题,原因与上述情况类似,有兴趣可以trace流表。
总结一下:如果同一路由下的虚机在东西向是网络可达的,且源虚机和外部网关不在一个节点上,源虚机访问目标虚机的浮动ip是不通的。

已向社区提了issue:issue#86

解决方案

解决方案也是,修改为overlay模式。

切换网关lrp的高可用节点

我们在验证多次切换lrp的高可用节点优先级顺序时,发现了流量不通的问题。
场景:有三个节点node-1,node-2,node-3作为网关节点。
操作:lrp的高可用节点一开始是node-1优先级最高,然后手动修改优先级将node-2作为最高。
预期:内部虚机访问外部流量正常。
实际:切换前正常,切换后流量不通,重启ovn-controller正常。

对比了切换前node-1和切换后node-2的北向网卡报文,发现了不同之处:

  • 切换前,从node-1发出的报文,src mac是外部网关的地址
  • 切换后,从node-2发出的报文,src mac是节点的chassis mac

也就是说,正常来讲,切换到node-2后,访问外部的报文src mac应该是外部网关mac而不是chassis mac。

问题分析

先来说一下为什么正常情况下发出的报文src mac是外部网关的mac:按前面提到的chassis mac的使用原理,vlan报文出网关不是默认都会修改成chassis-mac吗?其实这样会有一个问题存在,那就是外部网关的mac被替换后,交换机就可能学不到网关mac了。

所以社区提了一个方案:
OVN: Do not replace router port mac on gateway chassis。该patch的目的就是防止网关节点的src mac被替换成chassis mac。
在方法consider_port_binding处理locanet port的流程中,会去完成 “网关mac是否replace为chassis-mac”的操作:

如果网关的cr-lrp在本节点,则不会修改mac为chassis-mac:

所以正常流量从网关节点发出去的时候src mac是外部网关的mac。

但与此同时,该实现会有另外一个问题:我们手动操作改变了cr-lrp的网关节点优先级,但cr-lrp的变动可能不会触发localnet port的流程,也就是说上述流程put_replace_router_port_mac_flows可能不会被触发。最终导致节点的localnet port相关流表没有变化,出去的流量依然被替换成了chassis-mac。

解决方案

1.参考external port的处理方式,在physical_handle_flows_for_lport处理external port的流表下发流程中,再处理一次localnet port:

类似的,我们也可以加一个判断,如果port_binding是chassisredirect类型的且datapath有localnet port,则需要再处理一次localnet port的流表。

已向ovn社区提了issue:issue#129

2.从当前使用来看:

  • 流量从其它节点到网关节点通过隧道连接;
  • 流量出外网的时候均是外部网关的mac。

所以网关节点的chassis-mac配置没有实际用处,因此去掉chassis-mac配置,避免其导致的一系列的流量问题。

总结

ovn社区目标是希望实现全链路的vlan,但综上所述,实际上会遇到的不少的问题。目前我们的总体方案是:默认外部网关采用overlay模式,调大链路的mtu值,并且放弃使用chassis-mac。

参考文档:
OVSCON2018
dani.foroselectronica.es
NETWORKING WITH OPEN VIRTUAL NETWORK

个人分析,欢迎指正,若转载请注明出处!