DragonFlow SNAT + DNAT + Provider 混合部署

Table of Contents

  • DragonFlow SNAT DNAT Provider 混合部署
    • SNAT provider 广播风暴
      • 网络拓扑
      • 问题分析
      • 解决方法
    • DNAT 和 SNAT 冲突
      • 网络拓扑
      • 问题分析
      • 解决方法
      • 模拟报文

Created by gh-md-toc

DragonFlow SNAT + DNAT + Provider 混合部署

在 openstack pike dragonflow 集群中,单独部署 SNAT,DNAT 和 Provider 网络,都可以正常工作。然而同时启用 snat,dnat 和 provider,我们发现了两个虚机不通的情况。经过分析,确定原因是 dragonflow 流表的问题,修改相关代码后,所有网络都可以正常工作。

Commit:
https://github.com/wujieqian/dragonflow/commit/aa3c98cdf466bf4ea9e3a9bdceb5c2f4140011ef

SNAT + Provider 广播风暴

在 snat 和 provider 两个 app 同时启用下,snat 虚机无法与外网通信,并且发现 provider 网络的 ovs 转发表被破坏。

网络拓扑

dragon flow 配置文件,编辑 /etc/neutron/dragonflow.ini

SNAT 配置

[df]
pub_sub_driver = redis_db_pubsub_driver
enable_selective_topology_distribution = False
external_host_ip = 172.24.4.200
integration_bridge = br-int

Provider 配置

[df_provider_networks_app]
bridge_mappings = public:br-ex
 vm1: 10.0.0.12
 snat: 172.24.4.200
       |
 ------------    provider   -------------
|           1|<----------->|1   br-ex    |
|   br-int   |     snat    |             |
|           5|<----------->|2 172.24.4.1 |
 ------------               -------------

问题分析

vm1 与 br-ex 通信时,以 ICMP 为例,vm1 内部 IP 10.0.0.12 会经过 br-int 上 snat 动作,转换
为 172.24.4.200 外部 IP,然后从 snat patch port 5 转发出去。

接下来,br-ex 会收到 icmp 报文,送给宿主机协议栈。由于宿主机没有 172.24.4.200 的 mac 地址记录,协议栈会发送 arp request, 请求 172.24.4.200 的物理地址。通过抓包,我们可以在
br-ex 上看到如下报文:

[root@host-10-15-255-16 ~]# tcpdump -ni br-ex -e
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br-ex, link-type EN10MB (Ethernet), capture size 65535 bytes
10:42:24.466733 91:92:ac:18:04:c8 > 12:5d:01:40:2a:4f, ethertype IPv4 (0x0800), length 98: 172.24.4.200 > 172.24.4.1: ICMP echo request, id 33538, seq 0, length 64
10:42:24.466777 12:5d:01:40:2a:4f > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 172.24.4.200 tell 172.24.4.1, length 28
10:42:24.466891 91:92:ac:18:04:c8 > 12:5d:01:40:2a:4f, ethertype ARP (0x0806), length 42: Reply 172.24.4.200 is-at 91:92:ac:18:04:c8, length 28
10:42:24.466895 12:5d:01:40:2a:4f > 91:92:ac:18:04:c8, ethertype IPv4 (0x0800), length 98: 172.24.4.1 > 172.24.4.200: ICMP echo reply, id 33538, seq 0, length 64

br-ex 是一个 Normal 网桥,因此 arp request 包会被 flood 出去。 br-ex 两个出口 port 1 和 port 2 分别同 br-int 的 port 1 和 port 5连接,因此 br-int 的 port 1 和 port 5 都会收到广播报文。
对于 snat patch port,由于目的 mac 地址并不是 snat 定义的地址,会直接丢弃。
而对于 provider patch port,流表不会直接丢弃报文,也无法从 provider port 转发回去,因此广播报文最终会进入到 table=115,由于没有匹配到任何特定的出口,最终会选择优先级最低的 snat 端口转发出去,而正是这个报文,引发了网络问题。

 cookie=0x0, duration=1629749.533s, table=115, n_packets=4, n_bytes=168, idle_age=2048, hard_age=65534, priority=1 actions=output:5

从 br-int port 5 回到 br-ex port 2 的报文,会被 br-ex 进行 mac 地址学习,不巧该报文的源 mac 地址是 br-ex 本身,因此 br-ex 这个本地地址会错误的学习成了 port 2,然后继续洪泛。

# ovs-appctl fdb/show br-ex
 port  VLAN  MAC                Age
 2     0  12:5d:01:40:2a:4f    0

解决方法

分析到这里,我们明白这其实是两个交换机环路问题。如果是物理交换机,有 STP 协议来解决,而对于 br-int 这样纯 openflow 转发表交换机,我们只能自己添加规则解决环路。
显然,解决环路,只要在中途丢弃报文就可以了。这里有两个可以断开环路的点 1.封住入口 2. 封住出口
上文在 br-ex 上引入的两条 flow, 既是方法1,让 ARP 报文仅从 snat-patch(端口2) 进入 br-int,
对于 snat IP, snat-patch 能够正确处理 arp 报文,不会引发 flood。但对于其他IP,如 provider IP 虚机,是需要从 br-ex-patch(端口1) 进入 br-int,因此考虑方法2, 在 br-int 上的 snat 出口丢弃
广播包。这样方法就很简单了,在 table=115 添加 drop 流表:

ovs-ofctl add-flow br-int table=115,priority=10,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00,action=drop

DNAT 和 SNAT 冲突

在解决问题1后,SNAT 和 Provider 终于可以正常工作了。无论对端是在 br-ex 上还是 br-int 上,
vm1 都可以与之通信。但是当我们继续启动一台 DNAT 虚机,原本可以正常通信的 vm1 现在无法 ping 通
br-int 上的 provider 虚机了。最终分析原因,是 DNAT 新添加的流表破坏了原本的转发流程。

网络拓扑

 vm1: 10.0.0.12
 snat: 172.24.4.200
 mac: 91:92:ac:18:04:c8
               |
         ------------    provider   -------------
        |           1|<----------->|1   br-ex    |
        |   br-int   |     snat    |             |
        |           5|<----------->|2 172.24.4.1 |
         ------------               -------------
          |         |
 vm2:172.24.4.8     dnat 172.24.4.14
                    vm3: 10.0.0.6

问题分析

ovs 网络没通,有几个位置需要检查:

  1. 首先抓包看 ping echo 和 reply 发送接收情况。事实上 echo 报文成功发送到了 vm2,并且
    vm2 也正确返回了 reply。
# tcpdump -ni tap9c7d035e-be -e
tcpdump: WARNING: tap9c7d035e-be: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap9c7d035e-be, link-type EN10MB (Ethernet), capture size 65535 bytes
14:04:49.091435 12:5d:01:40:2a:4f > fa:16:3e:d4:b7:7b, ethertype IPv4 (0x0800), length 98: 172.24.4.200 > 172.24.4.8: ICMP echo request, id 35842, seq 0, length 64
14:04:49.093390 fa:16:3e:d4:b7:7b > 91:92:ac:18:04:c8, ethertype IPv4 (0x0800), length 98: 172.24.4.8 > 172.24.4.200: ICMP echo reply, id 35842, seq 0, length 64
  1. 由于 dnat enable 之后出现问题,所以对比流表区别。table=55 中 针对广播报文规则动作中在正常和异常环境中均有计数器更新,说明这一条规则是转发的关键路径。

并且在异常环境中,action 里多加了一项 load:0x9->NXM_NX_REG7[],resubmit(,75)

 cookie=0x0, duration=1638901.089s, table=55, n_packets=8, n_bytes=336, idle_age=7294, hard_age=65534, priority=100,metadata=0x2,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=load:0x9->NXM_NX_REG7[],resubmit(,75),load:0x4->NXM_NX_REG7[],resubmit(,75),load:0x7->NXM_NX_REG7[],resubmit(,75),load:0->NXM_NX_REG7[],resubmit(,75)

在这里 n_packets 计数器也有改变,证明报文确实匹配到了这里。按照 dragonflow 定义,table 55 是 L2_LOOKUP_TABLE,也就是根据目的 mac 地址,设置 reg7 计数器,在后面的匹配表里找到对应的出口转出交换机。
看到这里,有两个疑问 1. dl_dst 表示这一条 rule 是用来匹配 mac 多播的,为什么发向 snat 的 reply 报文会走到这里?

  1. 正常情况下,reply 报文应该继续走哪一个 action?

在正常流程下,snat 虚机的 mac 地址为 91:92:ac:18:04:c8,计算掩码后为 01:00:00:00:00:00,
是一个多播 mac 地址,因此匹配到了多播规则。对于为什么 snat 的 mac 地址使用了多播格式,猜测 df 的开发者是有意为之。直接让 dst mac = snat 的报文走多播的路径,也就是让该报文从每个 provider port 发送一份,其中自然包括了 provider patch port。
snat 虚机的外部 IP 上是一个 provider 网段 IP 地址,DF SNAT 设计者希望 br-int 把报文从 provider port 转发出去,之后再从 snat port 回 br-int 走 snat 匹配。正常流程下的流表路径也印证了这一点:

table=55
  cookie=0x0, duration=1638901.089s, table=55, n_packets=8, n_bytes=336, idle_age=7294, hard_age=65534, priority=100,metadata=0x2,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=load:0x4->NXM_NX_REG7[],resubmit(,75),load:0x7->NXM_NX_REG7[],resubmit(,75),load:0->NXM_NX_REG7[],resubmit(,75)

-> table=75 
  # 没有对 reg7 的要求,其实也就是 reg7=0x0,既默认值
  cookie=0x0, duration=1642589.397s, table=75, n_packets=45, n_bytes=3794, idle_age=2738, hard_age=65534, priority=50,metadata=0x2 actions=resubmit(,80)

-> table=80
 cookie=0x0, duration=1642589.397s, table=80, n_packets=25, n_bytes=1834, idle_age=2738, hard_age=65534, priority=100,metadata=0x2 actions=output:1

该流程也可以通过 ovs-appctl 注册模拟报文来观察:

# ovs-appctl ofproto/trace br-int  icmp,dl_dst=91:92:ac:18:04:c8,dl_src=fa:16:3e:d4:b7:7b,nw_src=172.24.4.8,nw_dst=172.24.4.200,icmp_type=0,in_port=10  -generate 

然而,在异常情况下,我们并没有看到 reg7=0x0 在 table=75, table=80的计数器更新,说明在 table=55 的连续 resubmit 中,pipeline 没有正确执行。

由于正常和异常的 action 区别只有 load:0x9->NXM_NX_REG7[],resubmit(,75) 这一个新加的动作,那么现在的问题就变成了 resubmit 究竟做了什么?

使用 ovs-appctl 注册模拟报文,发现 flow 匹配到 table 55后,的确触发了多个 resubmit 动作,4个 resubmit 分别是 reg7=9,reg7=4, reg7=7, reg7=0,以深度优先执行4个 resubmit,最终会走到我们期望的 reg7=0 这个分支。
模拟报文说明 flow 表设计的逻辑是说得通的,但是经过 debug,逻辑顺序和实际情况竟然并不一致。在 ovs 中我们添加 debug 信息,观察输出结果,发现只有 reg7=9 最终会修改报文信息,包括 metadata 和 mac 地址。而后面的resubmit 用的报文并不是原始副本,而是reg7=9 修改过的版本,导致正常流程被破坏。

看到这里,DF 设计者显然被 openflow resubmit 字面迷惑了,以为 resubmit 使用的独立副本,不会影响 pipeline 后续的动作。然而 ovs 对于 resubmit 动作的实现里,报文是使用了同一个结构体实例。

openvswitch/ofproto/ofproto-dpif-xlate.c

static void
xlate_recursively(struct xlate_ctx *ctx, struct rule_dpif *rule)
{
    struct rule_dpif *old_rule = ctx->rule;
    ovs_be64 old_cookie = ctx->rule_cookie;
    const struct rule_actions *actions;

    if (ctx->xin->resubmit_stats) {
        rule_dpif_credit_stats(rule, ctx->xin->resubmit_stats);
    }

    ctx->resubmits++;
    ctx->recurse++;
    ctx->rule = rule;
    ctx->rule_cookie = rule_dpif_get_flow_cookie(rule);
    actions = rule_dpif_get_actions(rule);
    do_xlate_actions(actions->ofpacts, actions->ofpacts_len, ctx);
    ctx->rule_cookie = old_cookie;
    ctx->rule = old_rule;
    ctx->recurse--;
}

解决方法

既然 openvswitch openflow 实现里,resubmit 始终使用同一份内存空间, 我们需要引其他规则来规避这个问题。这里有两个方案,1. 更改 snat 的 mac 前缀,改为单播地址。2. 为 91:92 地址添加独立的转发规则。

为以后方便和 upstream merge, 我们选择方案2实现。

模拟报文

这里给出模拟报文的输出片段,可以观察 table 55 的4次 resubmit 动作。

#ovs-appctl ofproto/trace br-int  icmp,dl_dst=91:92:ac:18:04:c8,dl_src=fa:16:3e:d4:b7:7b,nw_src=172.24.4.8,nw_dst=172.24.4.200,icmp_type=0,in_port=10  -generate ;id=0x1; ovs-appctl ofproto/trace br-int icmp,dl_dst=91:92:ac:18:04:c8,dl_src=fa:16:3e:d4:b7:7b,nw_src=172.24.4.8,nw_dst=172.24.4.200,icmp_type=0,ct_state="new|trk",recirc_id=0x$id -generate

Rule: table=20 cookie=0 priority=1
OpenFlow actions=goto_table:55

    Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
    Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
    Resubmitted  odp: drop
    Resubmitted megaflow: recirc_id=0x35f,ip,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_frag=no
    Rule: table=55 cookie=0 priority=100,metadata=0x2,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00
    OpenFlow actions=set_field:0x9->reg7,resubmit(,75),set_field:0x4->reg7,resubmit(,75),set_field:0x7->reg7,resubmit(,75),set_field:0->reg7,resubmit(,75)

        1. `resubmit 1, reg7=0x9`
        Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,reg7=0x9,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
        Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x9 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
        Resubmitted  odp: drop
        Resubmitted megaflow: recirc_id=0x35f,ip,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_frag=no
        Rule: table=75 cookie=0 priority=200,reg7=0x9
        OpenFlow actions=goto_table:76

            Resubmitted flow: unchanged
            Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x9 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
            Resubmitted  odp: drop
            Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_frag=no,icmp_type=0x0/0xfffe
            Rule: table=76 cookie=0 priority=100,ip,reg7=0x9
            OpenFlow actions=dec_ttl,set_field:fa:16:3e:58:eb:ba->eth_src,set_field:fa:16:3e:5c:bd:e0->eth_dst,set_field:10.0.0.6->ip_dst,set_field:0x6->reg7,set_field:0x1->metadata,resubmit(,55)

        2. `resubmit 2, reg7=0x4`
        Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,reg7=0x4,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
        Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x4 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
        Resubmitted  odp: drop
        Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
        Rule: table=75 cookie=0 priority=100,reg7=0x4
        OpenFlow actions=goto_table:105

            Resubmitted flow: unchanged
            Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x4 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
            Resubmitted  odp: drop
            Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
            Rule: table=105 cookie=0 priority=1
            OpenFlow actions=goto_table:115

                Resubmitted flow: unchanged
                Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x4 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
                Resubmitted  odp: drop
                Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
                Rule: table=115 cookie=0 priority=10,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00
                OpenFlow actions=drop

        3. `resubmit 3, reg7=0x7`
        Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,reg7=0x7,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
        Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x7 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
        Resubmitted  odp: drop
        Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
        Rule: table=75 cookie=0 priority=100,reg7=0x7
        OpenFlow actions=goto_table:105

            Resubmitted flow: unchanged
            Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x7 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
            Resubmitted  odp: drop
            Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
            Rule: table=105 cookie=0 priority=100,ip,reg7=0x7
            OpenFlow actions=ct(table=110,zone=OXM_OF_METADATA[0..15])

        4. `resubmit 4, reg7=0x0`
        Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
        Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
        Resubmitted  odp: ct(zone=2),recirc(0x360)
        Resubmitted megaflow: recirc_id=0x35f,icmp,reg6=0,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
        Rule: table=75 cookie=0 priority=50,metadata=0x2
        OpenFlow actions=goto_table:80

            Resubmitted flow: unchanged
            Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
            Resubmitted  odp: ct(zone=2),recirc(0x360)
            Resubmitted megaflow: recirc_id=0x35f,icmp,reg6=0,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
            Rule: table=80 cookie=0 priority=100,metadata=0x2
            OpenFlow actions=output:1

你可能感兴趣的:(DragonFlow SNAT + DNAT + Provider 混合部署)