[ovs-dev] [PATCH ovn v2 2/2] northd: Use ct_(snat/dnat)_in_czone action for distributed routers.

Mark Michelson mmichels at redhat.com
Fri Nov 19 16:15:45 UTC 2021


On 11/18/21 18:42, Numan Siddique wrote:
> On Thu, Nov 18, 2021 at 4:49 PM Mark Michelson <mmichels at redhat.com> wrote:
>>
>> Hi Numan,
>>
>> I had one question in the code and then one small finding in the
>> documentation. See below:
>>
>> On 11/18/21 13:13, numans at ovn.org wrote:
>>> From: Numan Siddique <numans at ovn.org>
>>>
>>> Make of use of these new actions for the distributed routers
>>> for NAT.  These new actions ensure that both sNAT and dNAT
>>> happens in the same zone.  This approach solves a couple of
>>> problems:
>>>
>>>    - The datapath flows generated for external traffic which requires
>>>      dNAT (N -> S) or sNAT (S -> N) are completely HWOL'able.
>>>
>>>    - Since there is only one zone, it would avoid multiple recirculations
>>>      (improving the performance).
>>>
>>> If the packet needs to be both sNATted and dNATted (for hairpin traffic
>>> with source and destination on the same chassis), then sNAT is done
>>> in a separate zone.  To detect this scenario, this patch adds a few
>>> extra logical flows.  For each dnat_and_snat entry prior to this patch
>>> ovn-northd was generating 9 logical flows and with this patch it now
>>> generates 12 logical flows.
>>>
>>> Similar approach can be taken for gateway routers.
>>>
>>> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1984953
>>> Signed-off-by: Numan Siddique <numans at ovn.org>
>>> ---
>>>
>>> v1 -> v2
>>> ------
>>>     * Rebased and resolved conflicts.
>>>
>>>    include/ovn/logical-fields.h |   1 +
>>>    lib/logical-fields.c         |   4 +
>>>    northd/northd.c              | 147 +++++++--
>>>    northd/ovn-northd.8.xml      | 201 +++++++++---
>>>    tests/ovn-northd.at          | 575 +++++++++++++++++++----------------
>>>    tests/ovn.at                 |   2 +-
>>>    tests/system-ovn.at          |  64 ++--
>>>    7 files changed, 607 insertions(+), 387 deletions(-)
>>>
>>> diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
>>> index c9675f81c..2118f7933 100644
>>> --- a/include/ovn/logical-fields.h
>>> +++ b/include/ovn/logical-fields.h
>>> @@ -70,6 +70,7 @@ enum mff_log_flags_bits {
>>>        MLF_LOOKUP_FDB_BIT = 8,
>>>        MLF_SKIP_SNAT_FOR_LB_BIT = 9,
>>>        MLF_LOCALPORT_BIT = 10,
>>> +    MLF_USE_SNAT_ZONE = 11,
>>>    };
>>>
>>>    /* MFF_LOG_FLAGS_REG flag assignments */
>>> diff --git a/lib/logical-fields.c b/lib/logical-fields.c
>>> index 7b3d431e0..352a48c89 100644
>>> --- a/lib/logical-fields.c
>>> +++ b/lib/logical-fields.c
>>> @@ -125,6 +125,10 @@ ovn_init_symtab(struct shash *symtab)
>>>                 MLF_SKIP_SNAT_FOR_LB_BIT);
>>>        expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL,
>>>                                 flags_str);
>>> +    snprintf(flags_str, sizeof flags_str, "flags[%d]",
>>> +             MLF_USE_SNAT_ZONE);
>>> +    expr_symtab_add_subfield(symtab, "flags.use_snat_zone", NULL,
>>> +                             flags_str);
>>>
>>>        /* Connection tracking state. */
>>>        expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
>>> diff --git a/northd/northd.c b/northd/northd.c
>>> index 0ff61deec..e4d051a94 100644
>>> --- a/northd/northd.c
>>> +++ b/northd/northd.c
>>> @@ -159,11 +159,14 @@ enum ovn_stage {
>>>        PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,     18, "lr_in_arp_request")  \
>>>                                                                          \
>>>        /* Logical router egress stages. */                               \
>>> -    PIPELINE_STAGE(ROUTER, OUT, UNDNAT,      0, "lr_out_undnat")        \
>>> -    PIPELINE_STAGE(ROUTER, OUT, POST_UNDNAT, 1, "lr_out_post_undnat")   \
>>> -    PIPELINE_STAGE(ROUTER, OUT, SNAT,        2, "lr_out_snat")          \
>>> -    PIPELINE_STAGE(ROUTER, OUT, EGR_LOOP,    3, "lr_out_egr_loop")      \
>>> -    PIPELINE_STAGE(ROUTER, OUT, DELIVERY,    4, "lr_out_delivery")
>>> +    PIPELINE_STAGE(ROUTER, OUT, CHECK_DNAT_LOCAL,   0,                       \
>>> +                   "lr_out_chk_dnat_local")                                  \
>>> +    PIPELINE_STAGE(ROUTER, OUT, UNDNAT,             1, "lr_out_undnat")      \
>>> +    PIPELINE_STAGE(ROUTER, OUT, POST_UNDNAT,        2, "lr_out_post_undnat") \
>>> +    PIPELINE_STAGE(ROUTER, OUT, SNAT,               3, "lr_out_snat")        \
>>> +    PIPELINE_STAGE(ROUTER, OUT, POST_SNAT,          4, "lr_out_post_snat")   \
>>> +    PIPELINE_STAGE(ROUTER, OUT, EGR_LOOP,           5, "lr_out_egr_loop")    \
>>> +    PIPELINE_STAGE(ROUTER, OUT, DELIVERY,           6, "lr_out_delivery")
>>>
>>>    #define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME)   \
>>>        S_##DP_TYPE##_##PIPELINE##_##STAGE                          \
>>> @@ -210,6 +213,7 @@ enum ovn_stage {
>>>    #define REGBIT_PKT_LARGER        "reg9[1]"
>>>    #define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]"
>>>    #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
>>> +#define REGBIT_DST_NAT_IP_LOCAL "reg9[4]"
>>>
>>>    /* Register to store the eth address associated to a router port for packets
>>>     * received in S_ROUTER_IN_ADMISSION.
>>> @@ -9568,9 +9572,10 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
>>>                                        undnat_match_p, est_actions,
>>>                                        &lb->nlb->header_);
>>>            } else {
>>> -            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
>>> -                                    undnat_match_p, "ct_dnat;",
>>> -                                    &lb->nlb->header_);
>>> +            ovn_lflow_add_with_hint(
>>> +                lflows, od, S_ROUTER_OUT_UNDNAT, 120, undnat_match_p,
>>> +                od->is_gw_router ? "ct_dnat;" : "ct_dnat_in_czone;",
>>> +                &lb->nlb->header_);
>>>            }
>>>            free(undnat_match_p);
>>>    next:
>>> @@ -9865,7 +9870,7 @@ lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
>>>            uint16_t priority;
>>>
>>>            /* Priority of logical flows corresponding to exempted_ext_ips is
>>> -         * +1 of the corresponding regulr NAT rule.
>>> +         * +2 of the corresponding regular NAT rule.
>>>             * For example, if we have following NAT rule and we associate
>>>             * exempted external ips to it:
>>>             * "ovn-nbctl lr-nat-add router dnat_and_snat 10.15.24.139 50.0.0.11"
>>> @@ -9873,17 +9878,17 @@ lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
>>>             * And now we associate exempted external ip address set to it.
>>>             * Now corresponding to above rule we will have following logical
>>>             * flows:
>>> -         * lr_out_snat...priority=162, match=(..ip4.dst == $exempt_range),
>>> +         * lr_out_snat...priority=163, match=(..ip4.dst == $exempt_range),
>>>             *                             action=(next;)
>>>             * lr_out_snat...priority=161, match=(..), action=(ct_snat(....);)
>>>             *
>>>             */
>>>            if (is_src) {
>>>                /* S_ROUTER_IN_DNAT uses priority 100 */
>>> -            priority = 100 + 1;
>>> +            priority = 100 + 2;
>>>            } else {
>>>                /* S_ROUTER_OUT_SNAT uses priority (mask + 1 + 128 + 1) */
>>> -            priority = count_1bits(ntohl(mask)) + 2;
>>> +            priority = count_1bits(ntohl(mask)) + 3;
>>
>> I'm having trouble following why these priorities were raised. I looked
>> at the changes to ovn-northd.8.xml to see if they were mentioned, but I
>> couldn't find anything there that referenced the change. What am I
>> missing here? :)
> 
> Hi Mark,
> 
> Thanks for the review.
> 
> Let me try to explain with an example.
> 
> Suppose a logical router -  LR has below dnat_and_snat entry
> 
> ovn-nbctl lr-nat-add LR dnat_and_snat  172.16.1.2 50.0.0.11
> 
> 
> And It also has the below configured
> 
> ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\"
> ovn-nbctl --is-exempted lr-nat-update-ext-ip LR dnat_and_snat
> 172.16.1.2 disallowed_range
> 
> Before this patch,  below logical flows were programmed in LR_OUT_SNAT stage
> 
> table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "LR-S1" && is_chassis_resident("cr-DR-S1")),
> action=(ct_snat_in_czone(172.16.1.1);)
> table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "LR-S1" && is_chassis_resident("cr-DR-S1") &&
> ip4.dst == $disallowed_range), action=(next;)
> 
> The priority-162 flow basically skips the SNAT if the destination ip
> belongs to the address set 'disallowed_range'
> 
> Now this patch changes the priority-162 to 163 flow and adds another
> flow with the priority - 162.
> 
> table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")),
> action=(ct_snat_in_czone(172.16.1.1);)
> table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") &&
> reg9[4] == 1), action=(reg9[4] = 0; ct_snat(172.16.1.1);)
> table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") &&
> ip4.dst == $disallowed_range), action=(next;)
> 
> 
> If you notice, the priority-162 flow is almost the same as the
> prio-161 flow with the extra match - reg9[4] == 1.
> 
> 
> I'll update the documentation accordingly.
> 
> This patch could also do something like below
> 
> table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") &&
> reg9[4] == 0), action=(ct_snat_in_czone(172.16.1.1);)
> table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") &&
> reg9[4] == 1), action=(reg9[4] = 0; ct_snat(172.16.1.1);)
> table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src ==
> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") &&
> ip4.dst == $disallowed_range), action=(next;)
> 
> Let me know which one you'd prefer ?  I guess both would work.

I like the way you've done it here better than your alternate proposal. 
Thank you for the explanation.

> 
> Thanks
> Numan
> 
>>
> 
>>>
>>>                if (!od->is_gw_router) {
>>>                    priority += 128;
>>> @@ -12268,9 +12273,9 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>            /* Traffic received on l3dgw_port is subject to NAT. */
>>>            ds_clear(match);
>>>            ds_clear(actions);
>>> -        ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
>>> -                      is_v6 ? "6" : "4", nat->external_ip,
>>> -                      od->l3dgw_ports[0]->json_key);
>>> +        ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
>>> +                      "flags.loopback == 0", is_v6 ? "6" : "4",
>>> +                      nat->external_ip, od->l3dgw_ports[0]->json_key);
>>>            if (!distributed && od->n_l3dgw_ports) {
>>>                /* Flows for NAT rules that are centralized are only
>>>                * programmed on the gateway chassis. */
>>> @@ -12282,12 +12287,31 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                ds_put_format(actions, "ip%s.dst=%s; next;",
>>>                              is_v6 ? "6" : "4", nat->logical_ip);
>>>            } else {
>>> -            ds_put_cstr(actions, "ct_snat;");
>>> +            ds_put_cstr(actions, "ct_snat_in_czone;");
>>>            }
>>>
>>>            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
>>>                                    100, ds_cstr(match), ds_cstr(actions),
>>>                                    &nat->header_);
>>> +
>>> +        if (!stateless) {
>>> +            ds_clear(match);
>>> +            ds_clear(actions);
>>> +            ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
>>> +                          "flags.loopback == 1 && flags.use_snat_zone == 1",
>>> +                          is_v6 ? "6" : "4", nat->external_ip,
>>> +                          od->l3dgw_ports[0]->json_key);
>>> +            if (!distributed && od->n_l3dgw_ports) {
>>> +                /* Flows for NAT rules that are centralized are only
>>> +                * programmed on the gateway chassis. */
>>> +                ds_put_format(match, " && is_chassis_resident(%s)",
>>> +                            od->l3dgw_ports[0]->cr_port->json_key);
>>> +            }
>>> +            ds_put_cstr(actions, "ct_snat;");
>>> +            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
>>> +                                    100, ds_cstr(match), ds_cstr(actions),
>>> +                                    &nat->header_);
>>> +        }
>>>        }
>>>    }
>>>
>>> @@ -12364,7 +12388,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                    ds_put_format(actions, "ip%s.dst=%s; next;",
>>>                                  is_v6 ? "6" : "4", nat->logical_ip);
>>>                } else {
>>> -                ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
>>> +                ds_put_format(actions, "ct_dnat_in_czone(%s", nat->logical_ip);
>>>                    if (nat->external_port_range[0]) {
>>>                        ds_put_format(actions, ",%s", nat->external_port_range);
>>>                    }
>>> @@ -12417,7 +12441,8 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>            ds_put_format(actions, "ip%s.src=%s; next;",
>>>                          is_v6 ? "6" : "4", nat->external_ip);
>>>        } else {
>>> -        ds_put_format(actions, "ct_dnat;");
>>> +        ds_put_format(actions,
>>> +                      od->is_gw_router ? "ct_dnat;" : "ct_dnat_in_czone;");
>>>        }
>>>
>>>        ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
>>> @@ -12425,6 +12450,36 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                                &nat->header_);
>>>    }
>>>
>>> +static void
>>> +build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
>>> +                                const struct nbrec_nat *nat, struct ds *match,
>>> +                                struct ds *actions, bool distributed,
>>> +                                bool is_v6)
>>> +{
>>> +    /* Note that this only applies for NAT on a distributed router.
>>> +     */
>>> +    if (!od->n_l3dgw_ports) {
>>> +        return;
>>> +    }
>>> +
>>> +    ds_clear(match);
>>> +    ds_put_format(match, "ip && ip%s.dst == %s && ",
>>> +                  is_v6 ? "6" : "4", nat->external_ip);
>>> +    if (distributed) {
>>> +        ds_put_format(match, "is_chassis_resident(\"%s\")", nat->logical_port);
>>> +    } else {
>>> +        ds_put_format(match, "is_chassis_resident(%s)",
>>> +                      od->l3dgw_ports[0]->cr_port->json_key);
>>> +    }
>>> +
>>> +    ds_clear(actions);
>>> +    ds_put_cstr(actions, REGBIT_DST_NAT_IP_LOCAL" = 1; next;");
>>> +
>>> +    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_CHECK_DNAT_LOCAL,
>>> +                            50, ds_cstr(match), ds_cstr(actions),
>>> +                            &nat->header_);
>>> +}
>>> +
>>>    static void
>>>    build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                                const struct nbrec_nat *nat, struct ds *match,
>>> @@ -12478,16 +12533,19 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>            ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
>>>                          is_v6 ? "6" : "4", nat->logical_ip,
>>>                          od->l3dgw_ports[0]->json_key);
>>> -        if (!distributed && od->n_l3dgw_ports) {
>>> -            /* Flows for NAT rules that are centralized are only
>>> -            * programmed on the gateway chassis. */
>>> -            priority += 128;
>>> -            ds_put_format(match, " && is_chassis_resident(%s)",
>>> -                          od->l3dgw_ports[0]->cr_port->json_key);
>>> -        } else if (distributed) {
>>> -            priority += 128;
>>> -            ds_put_format(match, " && is_chassis_resident(\"%s\")",
>>> -                          nat->logical_port);
>>> +        if (od->n_l3dgw_ports) {
>>> +            if (distributed) {
>>> +                ovs_assert(nat->logical_port);
>>> +                priority += 128;
>>> +                ds_put_format(match, " && is_chassis_resident(\"%s\")",
>>> +                              nat->logical_port);
>>> +            } else {
>>> +                /* Flows for NAT rules that are centralized are only
>>> +                * programmed on the gateway chassis. */
>>> +                priority += 128;
>>> +                ds_put_format(match, " && is_chassis_resident(%s)",
>>> +                              od->l3dgw_ports[0]->cr_port->json_key);
>>> +            }
>>>            }
>>>            ds_clear(actions);
>>>
>>> @@ -12505,7 +12563,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                ds_put_format(actions, "ip%s.src=%s; next;",
>>>                              is_v6 ? "6" : "4", nat->external_ip);
>>>            } else {
>>> -            ds_put_format(actions, "ct_snat(%s",
>>> +            ds_put_format(actions, "ct_snat_in_czone(%s",
>>>                            nat->external_ip);
>>>                if (nat->external_port_range[0]) {
>>>                    ds_put_format(actions, ",%s", nat->external_port_range);
>>> @@ -12519,6 +12577,24 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
>>>                                    priority, ds_cstr(match),
>>>                                    ds_cstr(actions), &nat->header_);
>>> +
>>> +        if (!stateless) {
>>> +            ds_put_cstr(match, " && "REGBIT_DST_NAT_IP_LOCAL" == 1");
>>> +            ds_clear(actions);
>>> +            if (distributed) {
>>> +                ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
>>> +                              ETH_ADDR_ARGS(mac));
>>> +            }
>>> +            ds_put_format(actions,  REGBIT_DST_NAT_IP_LOCAL" = 0; ct_snat(%s",
>>> +                          nat->external_ip);
>>> +            if (nat->external_port_range[0]) {
>>> +                ds_put_format(actions, ",%s", nat->external_port_range);
>>> +            }
>>> +            ds_put_format(actions, ");");
>>> +            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
>>> +                                    priority + 1, ds_cstr(match),
>>> +                                    ds_cstr(actions), &nat->header_);
>>> +        }
>>>        }
>>>    }
>>>
>>> @@ -12749,10 +12825,13 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>>>        /* Packets are allowed by default. */
>>>        ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
>>> +    ovn_lflow_add(lflows, od, S_ROUTER_OUT_CHECK_DNAT_LOCAL, 0, "1",
>>> +                  REGBIT_DST_NAT_IP_LOCAL" = 0; next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 0, "1", "next;");
>>> +    ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, 0, "1", "next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
>>>        ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
>>>
>>> @@ -12765,8 +12844,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>>>         * not committed, it would produce ongoing datapath flows with the ct.new
>>>         * flag set. Some NICs are unable to offload these flows.
>>>         */
>>> -    if ((od->is_gw_router || od->n_l3dgw_ports) &&
>>> -        (od->nbr->n_nat || od->has_lb_vip)) {
>>> +    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
>>>            ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 50,
>>>                          "ip", "flags.loopback = 1; ct_dnat;");
>>>            ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 50,
>>> @@ -12839,6 +12917,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>>>                }
>>>            }
>>>
>>> +        /* S_ROUTER_OUT_DNAT_LOCAL */
>>> +        build_lrouter_out_is_dnat_local(lflows, od, nat, match, actions,
>>> +                                        distributed, is_v6);
>>> +
>>>            /* S_ROUTER_OUT_UNDNAT */
>>>            build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed,
>>>                                          mac, is_v6);
>>> @@ -12912,7 +12994,8 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>>>                              "clone { ct_clear; "
>>>                              "inport = outport; outport = \"\"; "
>>>                              "eth.dst <-> eth.src; "
>>> -                          "flags = 0; flags.loopback = 1; ");
>>> +                          "flags = 0; flags.loopback = 1; "
>>> +                          "flags.use_snat_zone = "REGBIT_DST_NAT_IP_LOCAL"; ");
>>>                for (int j = 0; j < MFF_N_LOG_REGS; j++) {
>>>                    ds_put_format(actions, "reg%d = 0; ", j);
>>>                }
>>> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
>>> index 21d83718c..fc9d51ea7 100644
>>> --- a/northd/ovn-northd.8.xml
>>> +++ b/northd/ovn-northd.8.xml
>>> @@ -2879,23 +2879,65 @@ icmp6 {
>>>            <p>
>>>              For each configuration in the OVN Northbound database, that asks
>>>              to change the source IP address of a packet from <var>A</var> to
>>> -          <var>B</var>, a priority-100 flow matches <code>ip &&
>>> -          ip4.dst == <var>B</var> && inport == <var>GW</var></code> or
>>> -          <code>ip &&
>>> -          ip6.dst == <var>B</var> && inport == <var>GW</var></code>
>>> -          where <var>GW</var> is the logical router gateway port, with an
>>> -          action <code>ct_snat;</code>. If the NAT rule is of type
>>> -          dnat_and_snat and has <code>stateless=true</code> in the
>>> -          options, then the action would be <code>ip4/6.dst=
>>> -          (<var>B</var>)</code>.
>>> +          <var>B</var>, two priority-100 flows are added.
>>>            </p>
>>>
>>>            <p>
>>>              If the NAT rule cannot be handled in a distributed manner, then
>>> -          the priority-100 flow above is only programmed on the
>>> +          the below priority-100 flows are only programmed on the
>>>              gateway chassis.
>>>            </p>
>>>
>>> +        <ul>
>>> +          <li>
>>> +            <p>
>>> +              The first flow matches <code>ip &&
>>> +              ip4.dst == <var>B</var> && inport == <var>GW</var>
>>> +              && flags.loopback == 0</code> or
>>> +              <code>ip &&
>>> +              ip6.dst == <var>B</var> && inport == <var>GW</var>
>>> +              && flags.loopback == 0</code>
>>> +              where <var>GW</var> is the logical router gateway port, with an
>>> +              action <code>ct_snat_in_czone;</code> to unSNAT in the common
>>> +              zone.  If the NAT rule is of type dnat_and_snat and has
>>> +              <code>stateless=true</code> in the options, then the action
>>> +              would be <code>ip4/6.dst=(<var>B</var>)</code>.
>>> +            </p>
>>> +
>>> +            <p>
>>> +              If the NAT entry is of type <code>snat</code>, then there is an
>>> +              additional match <code>is_chassis_resident(<var>cr-GW</var>)
>>> +              </code> where <var>cr-GW</var> is the chassis resident port of
>>> +              <var>GW</var>.
>>> +            </p>
>>> +          </li>
>>> +
>>> +          <li>
>>> +            <p>
>>> +              The second flow matches <code>ip &&
>>> +              ip4.dst == <var>B</var> && inport == <var>GW</var>
>>> +              && flags.loopback == 1 &&
>>> +              flags.use_snat_zone == 1</code> or
>>> +              <code>ip &&
>>> +              ip6.dst == <var>B</var> && inport == <var>GW</var>
>>> +              && flags.loopback == 0 &&
>>> +              flags.use_snat_zone == 1</code>
>>> +              where <var>GW</var> is the logical router gateway port, with an
>>> +              action <code>ct_snat;</code> to unSNAT in the snat zone. If the
>>> +              NAT rule is of type dnat_and_snat and has
>>> +              <code>stateless=true</code> in the options, then the action
>>> +              would be <code>ip4/6.dst=(<var>B</var>)</code>.
>>> +            </p>
>>> +
>>> +            <p>
>>> +              If the NAT entry is of type <code>snat</code>, then there is an
>>> +              additional match <code>is_chassis_resident(<var>cr-GW</var>)
>>> +              </code> where <var>cr-GW</var> is the chassis resident port of
>>> +              <var>GW</var>.
>>> +            </p>
>>> +          </li>
>>> +        </ul>
>>> +
>>>            <p>
>>>              A priority-0 logical flow with match <code>1</code> has actions
>>>              <code>next;</code>.
>>> @@ -4031,7 +4073,43 @@ nd_ns {
>>>          </li>
>>>        </ul>
>>>
>>> -    <h3>Egress Table 0: UNDNAT</h3>
>>> +    <h3>Egress Table 0: Check DNAT local </h3>
>>> +
>>> +    <p>
>>> +      This table checks if the packet needs to be DNATed in the router ingress
>>> +      table <code>lr_out_dnat</code> after it is SNATed  and looped back
>>
>> s/lr_out_dnat/lr_in_dnat/
>>
>>> +      to the ingress pipeline.  This check is done only for routers configured
>>> +      with distributed gateway ports and NAT entries.  This check is done
>>> +      so that SNAT and DNAT is done in different zones instead of a common
>>> +      zone.
>>> +    </p>
>>> +
>>> +    <ul>
>>> +      <li>
>>> +        <p>
>>> +          For each NAT rule in the OVN Northbound database on a
>>> +          distributed router, a priority-50 logical flow with match
>>> +          <code>ip4.dst == <var>E</var> &&
>>> +          is_chassis_resident(<var>P</var>)</code>, where <var>E</var> is the
>>> +          external IP address specified in the NAT rule, <var>GW</var>
>>> +          is the logical router distributed gateway port. For dnat_and_snat
>>> +          NAT rule, <var>P</var> is the logical port specified in the NAT rule.
>>> +          If <ref column="logical_port"
>>> +          table="NAT" db="OVN_Northbound"/> column of
>>> +          <ref table="NAT" db="OVN_Northbound"/> table is NOT set, then
>>> +          <var>P</var> is the <code>chassisredirect port</code> of
>>> +          <var>GW</var> with the actions:
>>> +          <code>REGBIT_DST_NAT_IP_LOCAL = 1; next; </code>
>>> +        </p>
>>> +      </li>
>>> +
>>> +      <li>
>>> +        A priority-0 logical flow with match <code>1</code> has actions
>>> +        <code>REGBIT_DST_NAT_IP_LOCAL = 0; next;</code>.
>>> +      </li>
>>> +    </ul>
>>> +
>>> +    <h3>Egress Table 1: UNDNAT</h3>
>>>
>>>        <p>
>>>          This is for already established connections' reverse traffic.
>>> @@ -4040,6 +4118,23 @@ nd_ns {
>>>          is unDNATed here.
>>>        </p>
>>>
>>> +    <ul>
>>> +      <li>
>>> +        A priority-0 logical flow with match <code>1</code> has actions
>>> +        <code>next;</code>.
>>> +      </li>
>>> +    </ul>
>>> +
>>> +    <h3>Egress Table 1: UNDNAT on Gateway Routers</h3>
>>> +
>>> +    <ul>
>>> +      <li>
>>> +        For all IP packets, a priority-50 flow with an action
>>> +        <code>flags.loopback = 1; ct_dnat;</code>.
>>> +      </li>
>>> +    </ul>
>>> +
>>> +    <h3>Egress Table 1: UNDNAT on Distributed Routers</h3>
>>>        <ul>
>>>          <li>
>>>            <p>
>>> @@ -4050,9 +4145,9 @@ nd_ns {
>>>              gateway chassis that matches
>>>              <code>ip && ip4.src == <var>B</var> &&
>>>              outport == <var>GW</var></code>, where <var>GW</var> is the logical
>>> -          router gateway port with an action <code>ct_dnat;</code>. If the
>>> -          backend IPv4 address <var>B</var> is also configured with L4 port
>>> -          <var>PORT</var> of protocol <var>P</var>, then the
>>> +          router gateway port with an action <code>ct_dnat_in_czone;</code>.
>>> +          If the backend IPv4 address <var>B</var> is also configured with
>>> +          L4 port <var>PORT</var> of protocol <var>P</var>, then the
>>>              match also includes <code>P.src</code> == <var>PORT</var>.  These
>>>              flows are not added for load balancers with IPv6 <var>VIPs</var>.
>>>            </p>
>>> @@ -4072,7 +4167,7 @@ nd_ns {
>>>              matches <code>ip && ip4.src == <var>B</var>
>>>              && outport == <var>GW</var></code>, where <var>GW</var>
>>>              is the logical router gateway port, with an action
>>> -          <code>ct_dnat;</code>. If the NAT rule is of type
>>> +          <code>ct_dnat_in_czone;</code>. If the NAT rule is of type
>>>              dnat_and_snat and has <code>stateless=true</code> in the
>>>              options, then the action would be <code>ip4/6.src=
>>>              (<var>B</var>)</code>.
>>> @@ -4081,7 +4176,7 @@ nd_ns {
>>>            <p>
>>>              If the NAT rule cannot be handled in a distributed manner, then
>>>              the priority-100 flow above is only programmed on the
>>> -          gateway chassis.
>>> +          gateway chassis with the action <code>ct_dnat_in_czone</code>.
>>>            </p>
>>>
>>>            <p>
>>> @@ -4094,26 +4189,17 @@ nd_ns {
>>>            </p>
>>>          </li>
>>>
>>> -      <li>
>>> -        For all IP packets, a priority-50 flow with an action
>>> -        <code>flags.loopback = 1; ct_dnat;</code>.
>>> -      </li>
>>> -
>>> -      <li>
>>> -        A priority-0 logical flow with match <code>1</code> has actions
>>> -        <code>next;</code>.
>>> -      </li>
>>>        </ul>
>>>
>>> -    <h3>Egress Table 1: Post UNDNAT</h3>
>>> +    <h3>Egress Table 2: Post UNDNAT</h3>
>>>
>>>        <p>
>>>          <ul>
>>>            <li>
>>>              A priority-50 logical flow is added that commits any untracked flows
>>> -          from the previous table <code>lr_out_undnat</code>. This flow
>>> -          matches on <code>ct.new && ip</code> with action
>>> -          <code>ct_commit { } ; next; </code>.
>>> +          from the previous table <code>lr_out_undnat</code> for Gateway
>>> +          routers.  This flow matches on <code>ct.new && ip</code>
>>> +          with action <code>ct_commit { } ; next; </code>.
>>>            </li>
>>>
>>>            <li>
>>> @@ -4124,7 +4210,7 @@ nd_ns {
>>>          </ul>
>>>        </p>
>>>
>>> -    <h3>Egress Table 2: SNAT</h3>
>>> +    <h3>Egress Table 3: SNAT</h3>
>>>
>>>        <p>
>>>          Packets that are configured to be SNATed get their source IP address
>>> @@ -4140,7 +4226,7 @@ nd_ns {
>>>          </li>
>>>        </ul>
>>>
>>> -    <p>Egress Table 2: SNAT on Gateway Routers</p>
>>> +    <p>Egress Table 3: SNAT on Gateway Routers</p>
>>>
>>>        <ul>
>>>          <li>
>>> @@ -4239,7 +4325,7 @@ nd_ns {
>>>          </li>
>>>        </ul>
>>>
>>> -    <p>Egress Table 2: SNAT on Distributed Routers</p>
>>> +    <p>Egress Table 3: SNAT on Distributed Routers</p>
>>>
>>>        <ul>
>>>          <li>
>>> @@ -4247,28 +4333,46 @@ nd_ns {
>>>              For each configuration in the OVN Northbound database, that asks
>>>              to change the source IP address of a packet from an IP address of
>>>              <var>A</var> or to change the source IP address of a packet that
>>> -          belongs to network <var>A</var> to <var>B</var>, a flow matches
>>> -          <code>ip && ip4.src == <var>A</var> &&
>>> -          outport == <var>GW</var></code>, where <var>GW</var> is the
>>> -          logical router gateway port, with an action
>>> -          <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
>>> -          is calculated based on the mask of <var>A</var>, with matches
>>> -          having larger masks getting higher priorities. If the NAT rule
>>> -          is of type dnat_and_snat and has <code>stateless=true</code>
>>> -          in the options, then the action would be <code>ip4/6.src=
>>> -          (<var>B</var>)</code>.
>>> +          belongs to network <var>A</var> to <var>B</var>, two flows are
>>> +          added.  The priority of these flows are calculated based on the
>>> +          mask of <var>A</var>, with matches having larger masks getting
>>> +          higher priorities.
>>>            </p>
>>>
>>>            <p>
>>>              If the NAT rule cannot be handled in a distributed manner, then
>>> -          the flow above is only programmed on the
>>> -          gateway chassis increasing flow priority by 128 in
>>> -          order to be run first
>>> +          the below flows are only programmed on the gateway chassis increasing
>>> +          flow priority by 128 in order to be run first.
>>>            </p>
>>>
>>> +        <ul>
>>> +          <li>
>>> +            The first flow is added with the match
>>> +            <code>ip && ip4.src == <var>A</var> &&
>>> +            outport == <var>GW</var></code>, where <var>GW</var> is the
>>> +            logical router gateway port, with an action
>>> +            <code>ct_snat_in_czone(<var>B</var>);</code> to SNATed in the
>>> +            common zone.  If the NAT rule is of type dnat_and_snat and has
>>> +            <code>stateless=true</code> in the options, then the action
>>> +            would be <code>ip4/6.src=(<var>B</var>)</code>.
>>> +          </li>
>>> +
>>> +          <li>
>>> +            The second flow is added with the match
>>> +            <code>ip && ip4.src == <var>A</var> &&
>>> +            outport == <var>GW</var> &&
>>> +            REGBIT_DST_NAT_IP_LOCAL == 0</code>, where <var>GW</var> is the
>>> +            logical router gateway port, with an action
>>> +            <code>ct_snat(<var>B</var>);</code> to SNAT in the snat zone.
>>> +            If the NAT rule is of type dnat_and_snat and has
>>> +            <code>stateless=true</code> in the options, then the action would
>>> +            be <code>ip4/6.src=(<var>B</var>)</code>.
>>> +          </li>
>>> +        </ul>
>>> +
>>>            <p>
>>>              If the NAT rule can be handled in a distributed manner, then
>>> -          there is an additional action
>>> +          there is an additional action (for both the flows)
>>>              <code>eth.src = <var>EA</var>;</code>, where <var>EA</var>
>>>              is the ethernet address associated with the IP address
>>>              <var>A</var> in the NAT rule.  This allows upstream MAC
>>> @@ -4299,7 +4403,7 @@ nd_ns {
>>>          </li>
>>>        </ul>
>>>
>>> -    <h3>Egress Table 3: Egress Loopback</h3>
>>> +    <h3>Egress Table 4: Egress Loopback</h3>
>>>
>>>        <p>
>>>          For distributed logical routers where one of the logical router
>>> @@ -4344,6 +4448,7 @@ clone {
>>>        outport = "";
>>>        flags = 0;
>>>        flags.loopback = 1;
>>> +    flags.use_snat_zone = REGBIT_DST_NAT_IP_LOCAL;
>>>        reg0 = 0;
>>>        reg1 = 0;
>>>        ...
>>> @@ -4368,7 +4473,7 @@ clone {
>>>          </li>
>>>        </ul>
>>>
>>> -    <h3>Egress Table 4: Delivery</h3>
>>> +    <h3>Egress Table 5: Delivery</h3>
>>>
>>>        <p>
>>>          Packets that reach this table are ready for delivery.  It contains:
>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>> index 85b47a18f..70ec5e2e3 100644
>>> --- a/tests/ovn-northd.at
>>> +++ b/tests/ovn-northd.at
>>> @@ -877,25 +877,25 @@ check_flow_match_sets() {
>>>    echo
>>>    echo "IPv4: stateful"
>>>    ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
>>> -check_flow_match_sets 2 2 3 0 0 0 0
>>> +check_flow_match_sets 3 4 2 0 0 0 0
>>>    ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
>>>
>>>    echo
>>>    echo "IPv4: stateless"
>>>    ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
>>> -check_flow_match_sets 2 0 1 2 2 0 0
>>> +check_flow_match_sets 2 0 0 2 2 0 0
>>>    ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
>>>
>>>    echo
>>>    echo "IPv6: stateful"
>>>    ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
>>> -check_flow_match_sets 2 2 3 0 0 0 0
>>> +check_flow_match_sets 3 4 2 0 0 0 0
>>>    ovn-nbctl lr-nat-del R1 dnat_and_snat  fd01::1
>>>
>>>    echo
>>>    echo "IPv6: stateless"
>>>    ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
>>> -check_flow_match_sets 2 0 1 0 0 2 2
>>> +check_flow_match_sets 2 0 0 0 0 2 2
>>>
>>>    AT_CLEANUP
>>>    ])
>>> @@ -924,9 +924,9 @@ echo "CR-LRP UUID is: " $uuid
>>>    ovn-nbctl --portrange lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11 1-3000
>>>
>>>    AT_CAPTURE_FILE([sbflows])
>>> -OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows && test 2 = `grep -c lr_in_unsnat sbflows`])
>>> +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows && test 3 = `grep -c lr_in_unsnat sbflows`])
>>>    AT_CHECK([grep -c 'ct_snat.*3000' sbflows && grep -c 'ct_dnat.*3000' sbflows],
>>> -  [0], [1
>>> +  [0], [2
>>>    1
>>>    ])
>>>
>>> @@ -934,9 +934,9 @@ ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
>>>    ovn-nbctl --wait=sb --portrange lr-nat-add R1 snat  172.16.1.1 50.0.0.11 1-3000
>>>
>>>    AT_CAPTURE_FILE([sbflows2])
>>> -OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows2 && test 2 = `grep -c lr_in_unsnat sbflows`])
>>> +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows2 && test 3 = `grep -c lr_in_unsnat sbflows`])
>>>    AT_CHECK([grep -c 'ct_snat.*3000' sbflows2 && grep -c 'ct_dnat.*3000' sbflows2],
>>> -  [1], [1
>>> +  [1], [2
>>>    0
>>>    ])
>>>
>>> @@ -944,7 +944,7 @@ ovn-nbctl lr-nat-del R1 snat  172.16.1.1
>>>    ovn-nbctl --wait=sb --portrange --stateless lr-nat-add R1 dnat_and_snat  172.16.1.2 50.0.0.12 1-3000
>>>
>>>    AT_CAPTURE_FILE([sbflows3])
>>> -OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows3 && test 3 = `grep -c lr_in_unsnat sbflows3`])
>>> +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows3 && test 4 = `grep -c lr_in_unsnat sbflows3`])
>>>    AT_CHECK([grep 'ct_[s]dnat.*172\.16\.1\.2.*3000' sbflows3], [1])
>>>
>>>    ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
>>> @@ -1008,17 +1008,20 @@ AT_CAPTURE_FILE([drflows])
>>>    ovn-sbctl dump-flows CR > crflows
>>>    AT_CAPTURE_FILE([crflows])
>>>
>>> -AT_CHECK([
>>> -  grep -c lr_out_snat drflows
>>> -  grep -c lr_out_snat crflows
>>> -  grep lr_out_snat drflows | grep "ip4.src == 50.0.0.11" | grep -c "ip4.dst == $allowed_range"
>>> -  grep lr_out_snat crflows | grep "ip4.src == 50.0.0.11" | grep -c "ip4.dst == $allowed_range"], [0], [dnl
>>> -3
>>> -3
>>> -1
>>> -1
>>> +AT_CHECK([grep -e "lr_out_snat" drflows | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat_in_czone(172.16.1.1);)
>>> +  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.1);)
>>> +])
>>> +
>>> +AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=33   , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.1);)
>>>    ])
>>>
>>> +
>>>    # SNAT with DISALLOWED_IPs
>>>    check ovn-nbctl lr-nat-del DR snat  50.0.0.11
>>>    check ovn-nbctl lr-nat-del CR snat  50.0.0.11
>>> @@ -1036,19 +1039,19 @@ AT_CAPTURE_FILE([drflows2])
>>>    ovn-sbctl dump-flows CR > crflows2
>>>    AT_CAPTURE_FILE([crflows2])
>>>
>>> -AT_CHECK([
>>> -  grep -c lr_out_snat drflows2
>>> -  grep -c lr_out_snat crflows2
>>> -  grep lr_out_snat drflows2 | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep -c "priority=162"
>>> -  grep lr_out_snat drflows2 | grep "ip4.src == 50.0.0.11" | grep -c "priority=161"
>>> -  grep lr_out_snat crflows2 | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep -c "priority=34"
>>> -  grep lr_out_snat crflows2 | grep "ip4.src == 50.0.0.11" | grep -c "priority=33"], [0], [dnl
>>> -4
>>> -4
>>> -1
>>> -1
>>> -1
>>> -1
>>> +AT_CHECK([grep -e "lr_out_snat" drflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat_in_czone(172.16.1.1);)
>>> +  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.1);)
>>> +  table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
>>> +])
>>> +
>>> +AT_CHECK([grep -e "lr_out_snat" crflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=33   , match=(ip && ip4.src == 50.0.0.11), action=(ct_snat(172.16.1.1);)
>>> +  table=??(lr_out_snat        ), priority=35   , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $disallowed_range), action=(next;)
>>>    ])
>>>
>>>    # Stateful FIP with ALLOWED_IPs
>>> @@ -1059,25 +1062,24 @@ check ovn-nbctl lr-nat-add DR dnat_and_snat  172.16.1.2 50.0.0.11
>>>    check ovn-nbctl lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
>>>
>>>    check ovn-nbctl lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 allowed_range
>>> -check ovn-nbctl lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range
>>> +check ovn-nbctl --wait=sb lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range
>>>
>>> -ovn-nbctl show DR
>>> -ovn-sbctl dump-flows DR
>>> -ovn-nbctl show CR
>>> -ovn-sbctl dump-flows CR
>>> -
>>> -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
>>> -wc -l`])
>>> -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
>>> -wc -l`])
>>> +ovn-sbctl dump-flows DR > drflows3
>>> +AT_CAPTURE_FILE([drflows2])
>>> +ovn-sbctl dump-flows CR > crflows3
>>> +AT_CAPTURE_FILE([crflows2])
>>>
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
>>> +AT_CHECK([grep -e "lr_out_snat" drflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat_in_czone(172.16.1.2);)
>>> +  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.2);)
>>>    ])
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
>>> +
>>> +AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=33   , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.2);)
>>>    ])
>>>
>>>    # Stateful FIP with DISALLOWED_IPs
>>> @@ -1088,26 +1090,26 @@ ovn-nbctl lr-nat-add DR dnat_and_snat  172.16.1.2 50.0.0.11
>>>    ovn-nbctl lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
>>>
>>>    ovn-nbctl --is-exempted lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 disallowed_range
>>> -ovn-nbctl --is-exempted lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 disallowed_range
>>> +check ovn-nbctl --wait=sb --is-exempted lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 disallowed_range
>>>
>>> -ovn-nbctl show DR
>>> -ovn-sbctl dump-flows DR
>>> -ovn-nbctl show CR
>>> -ovn-sbctl dump-flows CR
>>> -
>>> -OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
>>> -wc -l`])
>>> -OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
>>> -wc -l`])
>>> +ovn-sbctl dump-flows DR > drflows4
>>> +AT_CAPTURE_FILE([drflows2])
>>> +ovn-sbctl dump-flows CR > crflows4
>>> +AT_CAPTURE_FILE([crflows2])
>>>
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=162" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
>>> +AT_CHECK([grep -e "lr_out_snat" drflows4 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat_in_czone(172.16.1.2);)
>>> +  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.2);)
>>> +  table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=34" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
>>> +AT_CHECK([grep -e "lr_out_snat" crflows4 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=33   , match=(ip && ip4.src == 50.0.0.11), action=(ct_snat(172.16.1.2);)
>>> +  table=??(lr_out_snat        ), priority=35   , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $disallowed_range), action=(next;)
>>>    ])
>>>
>>>    # Stateless FIP with DISALLOWED_IPs
>>> @@ -1120,24 +1122,21 @@ ovn-nbctl --stateless lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
>>>    ovn-nbctl lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 allowed_range
>>>    ovn-nbctl lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range
>>>
>>> -ovn-nbctl show DR
>>> -ovn-sbctl dump-flows DR
>>> -
>>> -ovn-nbctl show CR
>>> -ovn-sbctl dump-flows CR
>>> -
>>> -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
>>> -wc -l`])
>>> -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
>>> -wc -l`])
>>> +ovn-sbctl dump-flows DR > drflows5
>>> +AT_CAPTURE_FILE([drflows2])
>>> +ovn-sbctl dump-flows CR > crflows5
>>> +AT_CAPTURE_FILE([crflows2])
>>>
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
>>> +AT_CHECK([grep -e "lr_out_snat" drflows5 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ip4.src=172.16.1.2; next;)
>>>    ])
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
>>> +
>>> +AT_CHECK([grep -e "lr_out_snat" crflows5 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=33   , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ip4.src=172.16.1.2; next;)
>>>    ])
>>>
>>>    # Stateful FIP with DISALLOWED_IPs
>>> @@ -1150,23 +1149,25 @@ ovn-nbctl --stateless lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
>>>    ovn-nbctl --is-exempted lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 disallowed_range
>>>    ovn-nbctl --is-exempted lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 disallowed_range
>>>
>>> -ovn-nbctl show DR
>>> -ovn-sbctl dump-flows DR
>>> -ovn-nbctl show CR
>>> -ovn-sbctl dump-flows CR
>>> +ovn-nbctl --wait=sb sync
>>>
>>> -OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
>>> -wc -l`])
>>> -OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
>>> -wc -l`])
>>> +ovn-sbctl dump-flows DR > drflows6
>>> +AT_CAPTURE_FILE([drflows2])
>>> +ovn-sbctl dump-flows CR > crflows6
>>> +AT_CAPTURE_FILE([crflows2])
>>>
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=162" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
>>> +AT_CHECK([grep -e "lr_out_snat" drflows6 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ip4.src=172.16.1.2; next;)
>>> +  table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
>>>    ])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=34" | wc -l], [0], [1
>>> -])
>>> -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
>>> +
>>> +AT_CHECK([grep -e "lr_out_snat" crflows6 | sed 's/table=../table=??/' | sort], [0], [dnl
>>> +  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=??(lr_out_snat        ), priority=33   , match=(ip && ip4.src == 50.0.0.11), action=(ip4.src=172.16.1.2; next;)
>>> +  table=??(lr_out_snat        ), priority=35   , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $disallowed_range), action=(next;)
>>>    ])
>>>
>>>    AT_CLEANUP
>>> @@ -3475,14 +3476,14 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(ct_lb(backends=10.0.0.40:8080);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>>    check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4"
>>> @@ -3511,21 +3512,21 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=100  , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);)
>>> -  table=2 (lr_out_snat        ), priority=100  , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=100  , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);)
>>> +  table=? (lr_out_snat        ), priority=100  , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>>    check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
>>> @@ -3557,22 +3558,22 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>>    check ovn-nbctl --wait=sb remove logical_router lr0 options chassis
>>> @@ -3584,9 +3585,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
>>>      table=4 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>>    check ovn-nbctl set logical_router lr0 options:chassis=ch1
>>> @@ -3617,23 +3618,23 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>>    check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080
>>> @@ -3661,18 +3662,18 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 10.0.0.20 && tcp && reg9[[16..31]] == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=120  , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>>    AT_CLEANUP
>>> @@ -4176,6 +4177,8 @@ check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1
>>>    check ovn-nbctl lr-nat-add lr0 dnat 42.42.42.42 192.168.0.2
>>>    check ovn-nbctl --wait=sb sync
>>>
>>> +ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64'
>>> +
>>>    AT_CHECK([ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64' | grep "output(\"sw0-port1\")"], [0], [ignore])
>>>
>>>    dnl If we remove the DNAT entry we will be unable to trace to the DNAT address
>>> @@ -4761,17 +4764,17 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>>    # Create few dnat_and_snat entries
>>> @@ -4797,17 +4800,21 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>>    ovn-sbctl chassis-add gw1 geneve 127.0.0.1
>>> @@ -4828,9 +4835,12 @@ AT_CAPTURE_FILE([lr0flows])
>>>
>>>    AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
>>>      table=4 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
>>> -  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> -  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> -  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.30 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10 && inport == "lr0-public" && flags.loopback == 0 && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10 && inport == "lr0-public" && flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && flags.loopback == 0 && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.30 && inport == "lr0-public" && flags.loopback == 0 && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.30 && inport == "lr0-public" && flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>>    ])
>>>
>>>    AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
>>> @@ -4839,26 +4849,34 @@ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
>>>
>>>    AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
>>> -  table=6 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat(10.0.0.3);)
>>> +  table=6 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone(10.0.0.3);)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>> +  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.10 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
>>> +  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.20 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
>>> +  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.30 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);)
>>> -  table=2 (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);)
>>> +  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
>>>    ])
>>>
>>>    # Associate load balancer to lr0
>>> @@ -4879,9 +4897,12 @@ AT_CAPTURE_FILE([lr0flows])
>>>
>>>    AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
>>>      table=4 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
>>> -  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> -  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> -  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.30 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10 && inport == "lr0-public" && flags.loopback == 0 && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10 && inport == "lr0-public" && flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && flags.loopback == 0 && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.30 && inport == "lr0-public" && flags.loopback == 0 && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone;)
>>> +  table=4 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.168.0.30 && inport == "lr0-public" && flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
>>>    ])
>>>
>>>    AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
>>> @@ -4894,7 +4915,7 @@ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
>>>
>>>    AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
>>> -  table=6 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat(10.0.0.3);)
>>> +  table=6 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone(10.0.0.3);)
>>>      table=6 (lr_in_dnat         ), priority=110  , match=(ct.est && ip4 && reg0 == 172.168.0.200 && ct_label.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
>>>      table=6 (lr_in_dnat         ), priority=110  , match=(ct.new && ip4 && reg0 == 172.168.0.200 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.80,10.0.0.81);)
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.est && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_label.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
>>> @@ -4905,27 +4926,35 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.50:6062,10.0.0.60:6062);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> -  table=0 (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.4 && tcp.src == 8080)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> -  table=0 (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.50 && tcp.src == 82) || (ip4.src == 10.0.0.60 && tcp.src == 82)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> -  table=0 (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.50 && udp.src == 6062) || (ip4.src == 10.0.0.60 && udp.src == 6062)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> -  table=0 (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.80) || (ip4.src == 10.0.0.81)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>> +  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.10 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
>>> +  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.20 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
>>> +  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.30 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.4 && tcp.src == 8080)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.50 && tcp.src == 82) || (ip4.src == 10.0.0.60 && tcp.src == 82)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.50 && udp.src == 6062) || (ip4.src == 10.0.0.60 && udp.src == 6062)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src == 10.0.0.80) || (ip4.src == 10.0.0.81)) && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);)
>>> -  table=2 (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);)
>>> +  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
>>>    ])
>>>
>>>    # Make the logical router as Gateway router
>>> @@ -4965,22 +4994,26 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(ct_lb(backends=10.0.0.50:6062,10.0.0.60:6062);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>>    ])
>>>
>>>    # Set lb force snat logical router.
>>> @@ -5020,24 +5053,28 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.50:6062,10.0.0.60:6062);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>>    ])
>>>
>>>    # Add a LB VIP same as router ip.
>>> @@ -5081,24 +5118,28 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.50:6062,10.0.0.60:6062);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>>    ])
>>>
>>>    # Add IPv6 router port and LB.
>>> @@ -5155,26 +5196,30 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == def0::2 && tcp && reg9[[16..31]] == 8000), action=(flags.force_snat_for_lb = 1; ct_lb(backends=[[aef0::2]]:80,[[aef0::3]]:80);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-public"), action=(ct_snat(def0::10);)
>>> -  table=2 (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw0"), action=(ct_snat(aef0::1);)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> -  table=2 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-public"), action=(ct_snat(def0::10);)
>>> +  table=? (lr_out_snat        ), priority=110  , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw0"), action=(ct_snat(aef0::1);)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=25   , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
>>> +  table=? (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
>>>    ])
>>>
>>>    check ovn-nbctl lrp-del lr0-sw0
>>> @@ -5209,19 +5254,23 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>>      table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.50:6062,10.0.0.60:6062);)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_undnat" lr0flows | sort], [0], [dnl
>>> -  table=0 (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> -  table=0 (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>> +AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_post_undnat" lr0flows | sort], [0], [dnl
>>> -  table=1 (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> -  table=1 (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>> +AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_post_undnat ), priority=50   , match=(ip && ct.new), action=(ct_commit { } ; next; )
>>>    ])
>>>
>>> -AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>>> -  table=2 (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> -  table=2 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>> +AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
>>> +  table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
>>> +  table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>>>    ])
>>>
>>>    AT_CLEANUP
>>> diff --git a/tests/ovn.at b/tests/ovn.at
>>> index 0d606b42f..ae5744407 100644
>>> --- a/tests/ovn.at
>>> +++ b/tests/ovn.at
>>> @@ -21604,7 +21604,7 @@ AT_CAPTURE_FILE([sbflows])
>>>    AT_CHECK([for regex in ct_snat ct_dnat ip4.dst= ip4.src=; do
>>>      grep -c "$regex" sbflows;
>>>    done], [0], [0
>>> -1
>>> +0
>>>    2
>>>    2
>>>    ])
>>> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
>>> index c9f5771c9..7f6cb32dc 100644
>>> --- a/tests/system-ovn.at
>>> +++ b/tests/system-ovn.at
>>> @@ -2224,7 +2224,7 @@ ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.16
>>>
>>>    ovn-nbctl list load_balancer
>>>    ovn-sbctl dump-flows R2
>>> -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=42 | \
>>> +OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=43 | \
>>>    grep 'nat(src=20.0.0.2)'])
>>>
>>>    dnl Test load-balancing that includes L4 ports in NAT.
>>> @@ -2262,7 +2262,7 @@ ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.16
>>>
>>>    ovn-nbctl list load_balancer
>>>    ovn-sbctl dump-flows R2
>>> -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=42 | \
>>> +OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=43 | \
>>>    grep 'nat(src=20.0.0.2)'])
>>>
>>>    rm -f wget*.log
>>> @@ -3711,17 +3711,24 @@ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>>    icmpv6,orig=(src=fd20::2,dst=fd20::3,id=<cleared>,type=128,code=0),reply=(src=fd11::2,dst=fd20::2,id=<cleared>,type=129,code=0),zone=<cleared>
>>>    ])
>>>
>>> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> +
>>>    # South-North SNAT: 'foo2' pings 'alice1'. But 'alice1' receives traffic
>>> -# from 172.16.1.4
>>> +# from fd20::4
>>>    NS_CHECK_EXEC([foo2], [ping6 -q -c 3 -i 0.3 -w 2 fd20::2 | FORMAT_PING], \
>>>    [0], [dnl
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that SNAT indeed happened via 'dump-conntrack' command.
>>> +ovs-appctl dpctl/dump-conntrack | grep icmpv6
>>>    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd11::3) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmpv6,orig=(src=fd11::3,dst=fd20::2,id=<cleared>,type=128,code=0),reply=(src=fd20::2,dst=fd11::3,id=<cleared>,type=129,code=0),zone=<cleared>
>>> +])
>>> +
>>> +# We verify that SNAT indeed happened via 'dump-conntrack' command.
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::4) | \
>>> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +icmpv6,orig=(src=fd11::3,dst=fd20::2,id=<cleared>,type=128,code=0),reply=(src=fd20::2,dst=fd20::4,id=<cleared>,type=129,code=0),zone=<cleared>
>>>    ])
>>>
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> @@ -3861,11 +3868,9 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that the connection is tracked but not NATted. This is due to the
>>> -# unDNAT table in the egress router pipeline
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
>>> +# We verify that the connection is not tracked.
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(192.168.2.2) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmp,orig=(src=192.168.1.2,dst=192.168.2.2,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=192.168.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    ])
>>>
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> @@ -3875,11 +3880,9 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that the connection is tracked but not NATted. This is due to the
>>> -# unDNAT table in the egress router pipeline
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
>>> +# We verify that the connection is not tracked.
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(192.168.2.2) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmp,orig=(src=192.168.1.3,dst=192.168.2.2,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=192.168.1.3,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    ])
>>>
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> @@ -3889,14 +3892,13 @@ NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 192.168.1.3 | FORMAT_PING], \
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that the connection is tracked but not NATted. This is due to the
>>> -# unDNAT table in the egress router pipeline
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
>>> +# We verify that the connection is not tracked.
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(192.168.2.2) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmp,orig=(src=192.168.2.2,dst=192.168.1.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.3,dst=192.168.2.2,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    ])
>>>
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> +
>>>    # East-West NAT: 'foo1' pings 'bar1' using 172.16.1.4.
>>>    NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
>>>    [0], [dnl
>>> @@ -3905,11 +3907,10 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
>>>
>>>    # Check conntrack entries.  First SNAT of 'foo1' address happens.
>>>    # Then DNAT of 'bar1' address happens (listed first below).
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(172.16.1.4) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>>    icmp,orig=(src=172.16.1.3,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.3,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    icmp,orig=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.3,id=<cleared>,type=0,code=0),zone=<cleared>
>>> -icmp,orig=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=192.168.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    ])
>>>
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> @@ -3922,7 +3923,7 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
>>>
>>>    # Check conntrack entries.  First SNAT of 'foo2' address happens.
>>>    # Then DNAT of 'bar1' address happens (listed first below).
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(172.16.1.1) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>>    icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    icmp,orig=(src=192.168.1.3,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
>>> @@ -4055,13 +4056,6 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that the connection is tracked but not NATted. This is due to the
>>> -# unDNAT table in the egress router pipeline
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd12::2) | \
>>> -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmpv6,orig=(src=fd11::2,dst=fd12::2,id=<cleared>,type=128,code=0),reply=(src=fd12::2,dst=fd11::2,id=<cleared>,type=129,code=0),zone=<cleared>
>>> -])
>>> -
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>    # East-West No NAT: 'foo2' pings 'bar1' using fd12::2.
>>>    NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
>>> @@ -4069,13 +4063,6 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that the connection is tracked but not NATted. This is due to the
>>> -# unDNAT table in the egress router pipeline
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd12::2) | \
>>> -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmpv6,orig=(src=fd11::3,dst=fd12::2,id=<cleared>,type=128,code=0),reply=(src=fd12::2,dst=fd11::3,id=<cleared>,type=129,code=0),zone=<cleared>
>>> -])
>>> -
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>    # East-West No NAT: 'bar1' pings 'foo2' using fd11::3.
>>>    NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 fd11::3 | FORMAT_PING], \
>>> @@ -4083,13 +4070,6 @@ NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 fd11::3 | FORMAT_PING], \
>>>    3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>>    ])
>>>
>>> -# We verify that the connection is tracked but not NATted. This is due to the
>>> -# unDNAT table in the egress router pipeline
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd12::2) | \
>>> -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmpv6,orig=(src=fd12::2,dst=fd11::3,id=<cleared>,type=128,code=0),reply=(src=fd11::3,dst=fd12::2,id=<cleared>,type=129,code=0),zone=<cleared>
>>> -])
>>> -
>>>    AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>    # East-West NAT: 'foo1' pings 'bar1' using fd20::4.
>>>    NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 fd20::4 | FORMAT_PING], \
>>> @@ -4101,7 +4081,6 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 fd20::4 | FORMAT_PING], \
>>>    # Then DNAT of 'bar1' address happens (listed first below).
>>>    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::4) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmpv6,orig=(src=fd11::2,dst=fd20::4,id=<cleared>,type=128,code=0),reply=(src=fd20::4,dst=fd11::2,id=<cleared>,type=129,code=0),zone=<cleared>
>>>    icmpv6,orig=(src=fd11::2,dst=fd20::4,id=<cleared>,type=128,code=0),reply=(src=fd20::4,dst=fd20::3,id=<cleared>,type=129,code=0),zone=<cleared>
>>>    icmpv6,orig=(src=fd20::3,dst=fd20::4,id=<cleared>,type=128,code=0),reply=(src=fd12::2,dst=fd20::3,id=<cleared>,type=129,code=0),zone=<cleared>
>>>    ])
>>> @@ -6037,7 +6016,6 @@ NS_CHECK_EXEC([sw01-x], [ping -q -c 3 -i 0.3 -w 2 172.16.1.100 | FORMAT_PING], \
>>>    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.100) | \
>>>    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>>    icmp,orig=(src=192.168.1.2,dst=172.16.1.100,id=<cleared>,type=8,code=0),reply=(src=172.16.1.100,dst=172.16.1.20,id=<cleared>,type=0,code=0),zone=<cleared>
>>> -icmp,orig=(src=192.168.1.2,dst=172.16.1.100,id=<cleared>,type=8,code=0),reply=(src=172.16.1.100,dst=192.168.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
>>>    ])
>>>
>>>    OVS_APP_EXIT_AND_WAIT([ovn-controller])
>>>
>>
>> _______________________________________________
>> dev mailing list
>> dev at openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
> 



More information about the dev mailing list