On 3/29/23 16:57, Numan Siddique wrote:
> On Wed, Mar 29, 2023 at 6:15 AM Dumitru Ceara <dce...@redhat.com> wrote:
>>
>> On 3/29/23 11:20, Ales Musil wrote:
>>> There are essentially three problems with the current
>>> combination of DGP + SNAT + LB:
>>>
>>> 1) The first packet is being SNATed in common zone due
>>> to a problem with pinctrl not preserving ct_mark/ct_label.
>>> The commit would create a SNAT entry within the same with DNAT
>>> entry.
>>>
>>> 2) The unSNAT for reply always happened in common zone because of
>>> the loopback check which would be triggered only when we loop
>>> the packet through the LR. Now there are two possibilities how
>>> the reply packet would be handled:
>>>
>>> a) If the entry for SNAT in common zone did not time out yet, the
>>> packet would be passed through unSNAT in common zone which would
>>> be fine and continue on. However, the unDNAT wouldn't work due to
>>> the limitation of CT not capable of doing unSNAT/unDNAT on the same
>>> packet twice in the same zone. So the reply would arrive to
>>> the original interface, but with wrong source address.
>>>
>>> b) If the entry for SNAT timed out it would loop and do unSNAT correctly
>>> in separate zone and then also unDNAT. This is not possible anymore with
>>> a recent change 8c341b9d (northd: Drop packets destined to router owned NAT 
>>> IP for DGP).
>>> The reply would be dropped before looping after that change co the traffic
>>> would never arrive to the original interface.
>>>
>>> 3) The unDNAT was happening only if the DGP was outport meaning
>>> the reply traffic was routed out, but in the opposite case
>>> the unDNAT was happening only because of the looping which made
>>> outport=inport. That's why it worked before introduction of explicit drop.
>>>
>>> In order to fix all those issues do two changes:
>>>
>>> 1) Include inport in the unDNAT match, so that we have both
>>> routing directions covered e.g. (inport == "dgp_port" || outport == 
>>> "dpg_port").
>>>
>>> 2) Always use the separate zone for SNAT and DNAT. As the common
>>> zone was needed for HWOL make the common zone optional with
>>> configuration option called "use_common_zone". This option is
>>> by default "false" and can be specified per LR. Use of separate
>>> zones also eliminates the need for the flag propagation
>>> in "lr_out_chk_dnat_local" stage, removing the match on ct_mark/ct_label.
>>>
>>> Reported-at: https://bugzilla.redhat.com/2161281
>>> Signed-off-by: Ales Musil <amu...@redhat.com>
>>> ---
>>> v2: Fix flaky system test.
>>> v3: Rebase on top of current main.
>>> v4: Rebase on top of current main.
>>>     Move the system test to system-ovn-kmod
>>>     to unblock the failures with userspace.
>>> v5: Rebase on top of current main.
>>> v6: Rebase on top of current main.
>>>     Change the config to a global option instead.
>>
>> I know this was discussed on v5 [0][1] but I wanted to raise another point:
>>
>> The common zone was added to make deployments more "HWOL-friendly".
>> Turning it into a global option means we can never have hybrid clusters,
>> with only some of the nodes capable of offloading traffic efficiently.
>>
>> Do you foresee this becoming a problem?
> 
> Having this option per router also is not going to solve this problem right ?
> As the router would be distributed.
> 
> From RedHat OSP perspective (after talking to Daniel) ,  I think this
> would be a global option during deployment
> and it's easier for the openstack installer to set this in NB_Global
> rather than neutron setting this option for every router.
> 
> I think it should be fine to have a global option.   In the case of
> hybrid clusters you mentioned,  I suppose CMS should
> enable using common zones.

OK, sounds reasonable, I guess.

Ales, I finally got a chance to read through the patch and I have a few
comments below.  Overall the patch looks in good shape but please
address those and then I think we can accept it.

Regards,
Dumitru

> 
> Thanks
> Numan
> 
>>
>> Thanks,
>> Dumitru
>>
>> [0] https://mail.openvswitch.org/pipermail/ovs-dev/2023-March/403184.html
>> [1] https://mail.openvswitch.org/pipermail/ovs-dev/2023-March/403187.html
>>
>>> ---
>>>  northd/northd.c          | 496 ++++++++++++++++++++-------------------
>>>  northd/ovn-northd.8.xml  |  90 +------
>>>  ovn-nb.xml               |  10 +
>>>  tests/ovn-northd.at      | 213 ++++++++++++-----
>>>  tests/ovn.at             |   3 +
>>>  tests/system-ovn-kmod.at | 166 +++++++++++++
>>>  tests/system-ovn.at      | 117 ---------
>>>  7 files changed, 601 insertions(+), 494 deletions(-)
>>>
>>> diff --git a/northd/northd.c b/northd/northd.c
>>> index 7a10e4dcf..2e4e99f11 100644
>>> --- a/northd/northd.c
>>> +++ b/northd/northd.c
>>> @@ -66,6 +66,9 @@ static bool check_lsp_is_up;
>>>
>>>  static bool install_ls_lb_from_router;
>>>
>>> +/* Use common zone for SNAT and DNAT if this option is set to "true". */
>>> +static bool use_common_zone = false;
>>> +
>>>  /* MAC allocated for service monitor usage. Just one mac is allocated
>>>   * for this purpose and ovn-controller's on each chassis will make use
>>>   * of this mac when sending out the packets to monitor the services
>>> @@ -10662,6 +10665,8 @@ build_distr_lrouter_nat_flows_for_lb(struct 
>>> lrouter_nat_lb_flows_ctx *ctx,
>>>                                       enum lrouter_nat_lb_flow_type type,
>>>                                       struct ovn_datapath *od)
>>>  {
>>> +    struct ovn_port *dgp = od->l3dgw_ports[0];
>>> +
>>>      const char *undnat_action;
>>>
>>>      switch (type) {
>>> @@ -10673,9 +10678,12 @@ build_distr_lrouter_nat_flows_for_lb(struct 
>>> lrouter_nat_lb_flows_ctx *ctx,
>>>          break;
>>>      case LROUTER_NAT_LB_FLOW_NORMAL:
>>>      case LROUTER_NAT_LB_FLOW_MAX:
>>> -        undnat_action = od->is_gw_router ? "ct_dnat;" : 
>>> "ct_dnat_in_czone;";
>>> +        undnat_action = !od->is_gw_router && use_common_zone
>>> +                        ? "ct_dnat_in_czone;"
>>> +                        : "ct_dnat;";

I think it's more clear if we have a helper function that we use in all
places where we build the dnat action, e.g.:

static const char *
lrouter_get_dnat_action(const struct ovn_datapath *od)
{
    return !od->is_gw_router && use_common_zone
           ? "ct_dnat_in_czone;"
           : "ct_dnat;";
}

>>>          break;
>>>      }
>>> +
>>>      /* Store the match lengths, so we can reuse the ds buffer. */
>>>      size_t new_match_len = ctx->new_match->length;
>>>      size_t undnat_match_len = ctx->undnat_match->length;
>>> @@ -10702,10 +10710,9 @@ build_distr_lrouter_nat_flows_for_lb(struct 
>>> lrouter_nat_lb_flows_ctx *ctx,
>>>          return;
>>>      }
>>>
>>> -    ds_put_format(ctx->undnat_match,
>>> -                  ") && outport == %s && is_chassis_resident(%s)",
>>> -                  od->l3dgw_ports[0]->json_key,
>>> -                  od->l3dgw_ports[0]->cr_port->json_key);
>>> +    ds_put_format(ctx->undnat_match, ") && (inport == %s || outport == %s)"
>>> +                  " && is_chassis_resident(%s)", dgp->json_key, 
>>> dgp->json_key,
>>> +                  dgp->cr_port->json_key);
>>>      ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_UNDNAT, 120,
>>>                              ds_cstr(ctx->undnat_match), undnat_action,
>>>                              &ctx->lb->nlb->header_);
>>> @@ -11153,13 +11160,8 @@ copy_ra_to_sb(struct ovn_port *op, const char 
>>> *address_mode)
>>>  static inline bool
>>>  lrouter_nat_is_stateless(const struct nbrec_nat *nat)
>>>  {
>>> -    const char *stateless = smap_get(&nat->options, "stateless");
>>> -
>>> -    if (stateless && !strcmp(stateless, "true")) {
>>> -        return true;
>>> -    }
>>> -
>>> -    return false;
>>> +    return smap_get_bool(&nat->options, "stateless", false) &&
>>> +           !strcmp(nat->type, "dnat_and_snat");
>>>  }

I was about to say "this seems wrong; we can also have snat stateless
rules".  But after checking the man page it seems we can't.
Nevertheless please move this to a separate commit with a proper "Fixes"
tag and please rename the function to something more explicit, e.g.,
"lrouter_dnat_and_snat_is_stateless()".

>>>
>>>  /* Handles the match criteria and actions in logical flow
>>> @@ -12893,7 +12895,6 @@ build_gateway_redirect_flows_for_lrouter(
>>>              const struct ovn_nat *nat = &od->nat_entries[j];
>>>
>>>              if (!lrouter_nat_is_stateless(nat->nb) ||
>>> -                strcmp(nat->nb->type, "dnat_and_snat") ||
>>>                  (!nat->nb->allowed_ext_ips && !nat->nb->exempted_ext_ips)) 
>>> {
>>>                  continue;
>>>              }
>>> @@ -13654,10 +13655,50 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>>>      }
>>>  }
>>>
>>> +static void
>>> +build_lrouter_in_unsnat_in_czone_flow(struct hmap *lflows,
>>> +                                      struct ovn_datapath *od,
>>> +                                      const struct nbrec_nat *nat,
>>> +                                      struct ds *match, bool distributed,
>>> +                                      bool is_v6, struct ovn_port 
>>> *l3dgw_port)
>>> +{
>>> +    struct ds zone_match = DS_EMPTY_INITIALIZER;
>>> +
>>> +    ds_put_format(match, "ip && ip%c.dst == %s && inport == %s",
>>> +                  is_v6 ? '6' : '4', nat->external_ip, 
>>> l3dgw_port->json_key);
>>> +    ds_clone(&zone_match, match);
>>> +
>>> +    ds_put_cstr(match, " && flags.loopback == 0");
>>> +
>>> +    /* Update common zone match for the hairpin traffic. */
>>> +    ds_put_cstr(&zone_match, " && flags.loopback == 1"
>>> +                             " && flags.use_snat_zone == 1");
>>> +
>>> +
>>> +    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)",
>>> +                      l3dgw_port->cr_port->json_key);
>>> +        ds_put_format(&zone_match, " && is_chassis_resident(%s)",
>>> +                      l3dgw_port->cr_port->json_key);
>>> +    }
>>> +
>>> +    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
>>> +                            100, ds_cstr(match), "ct_snat_in_czone;",
>>> +                            &nat->header_);
>>> +
>>> +    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
>>> +                            100, ds_cstr(&zone_match), "ct_snat;",
>>> +                            &nat->header_);
>>> +
>>> +    ds_destroy(&zone_match);
>>> +}
>>> +
>>>  static void
>>>  build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                               const struct nbrec_nat *nat, struct ds *match,
>>> -                             struct ds *actions, bool distributed, bool 
>>> is_v6,
>>> +                             bool distributed, bool is_v6,
>>>                               struct ovn_port *l3dgw_port)
>>>  {
>>>      /* Ingress UNSNAT table: It is for already established connections'
>>> @@ -13673,66 +13714,39 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, 
>>> struct ovn_datapath *od,
>>>          return;
>>>      }
>>>
>>> +    ds_clear(match);
>>> +
>>>      bool stateless = lrouter_nat_is_stateless(nat);
>>> -    if (od->is_gw_router) {
>>> -        ds_clear(match);
>>> -        ds_clear(actions);
>>> -        ds_put_format(match, "ip && ip%s.dst == %s",
>>> -                      is_v6 ? "6" : "4", nat->external_ip);
>>> -        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
>>> -            ds_put_format(actions, "next;");
>>> -        } else {
>>> -            ds_put_cstr(actions, "ct_snat;");
>>> -        }
>>>
>>> -        ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
>>> -                                90, ds_cstr(match), ds_cstr(actions),
>>> -                                &nat->header_);
>>> -    } else {
>>> +    if (!od->is_gw_router && use_common_zone && !stateless) {
>>> +        build_lrouter_in_unsnat_in_czone_flow(lflows, od, nat, match,
>>> +                                              distributed, is_v6, 
>>> l3dgw_port);
>>> +        return;
>>> +    }
>>> +
>>> +    uint16_t priority = od->is_gw_router ? 90 : 100;

I'm not sure I understand why we need two different priorities?  A
datapath is either gw router or distributed.  But it was like this
before your change too, since ceacd9d49316 ("ovn: distributed NAT
flows").  So we can accept it I guess.

>>> +    const char *action = stateless ? "next;" : "ct_snat;";
>>> +
>>> +    ds_put_format(match, "ip && ip%c.dst == %s",
>>> +                  is_v6 ? '6' : '4', nat->external_ip);
>>> +
>>> +    if (!od->is_gw_router) {
>>>          /* Distributed router. */
>>>
>>>          /* 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 && "
>>> -                      "flags.loopback == 0", is_v6 ? "6" : "4",
>>> -                      nat->external_ip, l3dgw_port->json_key);
>>> +        ds_put_format(match, " && inport == %s", l3dgw_port->json_key);
>>> +
>>>          if (!distributed && od->n_l3dgw_ports) {
>>>              /* Flows for NAT rules that are centralized are only
>>> -            * programmed on the gateway chassis. */
>>> +             * programmed on the gateway chassis. */
>>>              ds_put_format(match, " && is_chassis_resident(%s)",
>>>                            l3dgw_port->cr_port->json_key);
>>>          }
>>> -
>>> -        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
>>> -            ds_put_format(actions, "next;");
>>> -        } else {
>>> -            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,
>>> -                          l3dgw_port->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)",
>>> -                            l3dgw_port->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_);
>>> -        }
>>>      }
>>> +
>>> +    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
>>> +                            priority, ds_cstr(match), action,
>>> +                            &nat->header_);
>>>  }
>>>
>>>  static void
>>> @@ -13745,82 +13759,64 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, 
>>> struct ovn_datapath *od,
>>>      /* Ingress DNAT table: Packets enter the pipeline with destination
>>>      * IP address that needs to be DNATted from a external IP address
>>>      * to a logical IP address. */
>>> -    if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) 
>>> {
>>> -        bool stateless = lrouter_nat_is_stateless(nat);
>>> +    if (strcmp(nat->type, "dnat") && strcmp(nat->type, "dnat_and_snat")) {
>>> +        return;
>>> +    }
>>>
>>> -        if (od->is_gw_router) {
>>> -            /* Packet when it goes from the initiator to destination.
>>> -             * We need to set flags.loopback because the router can
>>> -             * send the packet back through the same interface. */
>>> -            ds_clear(match);
>>> -            ds_put_format(match, "ip && ip%s.dst == %s",
>>> -                          is_v6 ? "6" : "4", nat->external_ip);
>>> -            ds_clear(actions);
>>> -            if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> -                lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> -                                             is_v6, true, cidr_bits);
>>> -            }
>>> +    ds_clear(match);
>>> +    ds_clear(actions);
>>>
>>> -            if (!lport_addresses_is_empty(&od->dnat_force_snat_addrs)) {
>>> -                /* Indicate to the future tables that a DNAT has taken
>>> -                 * place and a force SNAT needs to be done in the
>>> -                 * Egress SNAT table. */
>>> -                ds_put_format(actions, "flags.force_snat_for_dnat = 1; ");
>>> -            }
>>> +    const char *nat_action = !od->is_gw_router && use_common_zone
>>> +                             ? "ct_dnat_in_czone"
>>> +                             : "ct_dnat";
>>>
>>> -            if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
>>> -                ds_put_format(actions, "flags.loopback = 1; "
>>> -                              "ip%s.dst=%s; next;",
>>> -                              is_v6 ? "6" : "4", nat->logical_ip);
>>> -            } else {
>>> -                ds_put_format(actions, "flags.loopback = 1; ct_dnat(%s",
>>> -                              nat->logical_ip);
>>> +    ds_put_format(match, "ip && ip%c.dst == %s", is_v6 ? '6' : '4',
>>> +                  nat->external_ip);
>>>
>>> -                if (nat->external_port_range[0]) {
>>> -                    ds_put_format(actions, ",%s", 
>>> nat->external_port_range);
>>> -                }
>>> -                ds_put_format(actions, ");");
>>> -            }
>>> +    if (od->is_gw_router) {
>>> +        if (!lport_addresses_is_empty(&od->dnat_force_snat_addrs)) {
>>> +            /* Indicate to the future tables that a DNAT has taken
>>> +             * place and a force SNAT needs to be done in the
>>> +             * Egress SNAT table. */
>>> +            ds_put_cstr(actions, "flags.force_snat_for_dnat = 1; ");
>>> +        }
>>>
>>> -            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
>>> -                                    ds_cstr(match), ds_cstr(actions),
>>> -                                    &nat->header_);
>>> -        } else {
>>> -            /* Distributed router. */
>>> +        /* Packet when it goes from the initiator to destination.
>>> +        * We need to set flags.loopback because the router can
>>> +        * send the packet back through the same interface. */
>>> +        ds_put_cstr(actions, "flags.loopback = 1; ");
>>> +    } else {
>>> +        /* Distributed router. */
>>>
>>> -            /* Traffic received on l3dgw_port is subject to NAT. */
>>> -            ds_clear(match);
>>> -            ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
>>> -                          is_v6 ? "6" : "4", nat->external_ip,
>>> -                          l3dgw_port->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)",
>>> -                              l3dgw_port->cr_port->json_key);
>>> -            }
>>> -            ds_clear(actions);
>>> -            if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> -                lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> -                                             is_v6, true, cidr_bits);
>>> -            }
>>> +        /* Traffic received on l3dgw_port is subject to NAT. */
>>> +        ds_put_format(match, " && inport == %s", l3dgw_port->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)",
>>> +                          l3dgw_port->cr_port->json_key);
>>> +        }
>>> +    }
>>>
>>> -            if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
>>> -                ds_put_format(actions, "ip%s.dst=%s; next;",
>>> -                              is_v6 ? "6" : "4", nat->logical_ip);
>>> -            } else {
>>> -                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);
>>> -                }
>>> -                ds_put_format(actions, ");");
>>> -            }
>>> +    if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> +        lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> +                                     is_v6, true, cidr_bits);
>>> +    }
>>>
>>> -            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
>>> -                                    ds_cstr(match), ds_cstr(actions),
>>> -                                    &nat->header_);
>>> +    if (lrouter_nat_is_stateless(nat)) {
>>> +        ds_put_format(actions, "ip%c.dst=%s; next;",
>>> +                      is_v6 ? '6' : '4', nat->logical_ip);
>>> +    } else {
>>> +        ds_put_format(actions, "%s(%s", nat_action, nat->logical_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_IN_DNAT, 100,
>>> +                            ds_cstr(match), ds_cstr(actions),
>>> +                            &nat->header_);
>>>  }
>>>
>>>  static void
>>> @@ -13843,8 +13839,10 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, 
>>> struct ovn_datapath *od,
>>>      }
>>>
>>>      ds_clear(match);
>>> -    ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
>>> -                  is_v6 ? "6" : "4", nat->logical_ip,
>>> +    ds_clear(actions);
>>> +
>>> +    ds_put_format(match, "ip && ip%c.src == %s && outport == %s",
>>> +                  is_v6 ? '6' : '4', nat->logical_ip,
>>>                    l3dgw_port->json_key);
>>>      if (!distributed && od->n_l3dgw_ports) {
>>>          /* Flows for NAT rules that are centralized are only
>>> @@ -13852,18 +13850,17 @@ build_lrouter_out_undnat_flow(struct hmap 
>>> *lflows, struct ovn_datapath *od,
>>>          ds_put_format(match, " && is_chassis_resident(%s)",
>>>                        l3dgw_port->cr_port->json_key);
>>>      }
>>> -    ds_clear(actions);
>>> +
>>>      if (distributed) {
>>>          ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
>>>                        ETH_ADDR_ARGS(mac));
>>>      }
>>>
>>> -    if (!strcmp(nat->type, "dnat_and_snat") &&
>>> -        lrouter_nat_is_stateless(nat)) {
>>> -        ds_put_format(actions, "next;");
>>> +    if (lrouter_nat_is_stateless(nat)) {
>>> +        ds_put_cstr(actions, "next;");
>>>      } else {
>>> -        ds_put_format(actions,
>>> -                      od->is_gw_router ? "ct_dnat;" : "ct_dnat_in_czone;");
>>> +        ds_put_cstr(actions,
>>> +                    use_common_zone ? "ct_dnat_in_czone;" : "ct_dnat;");
>>>      }
>>>
>>>      ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
>>> @@ -13925,6 +13922,70 @@ build_lrouter_drop_ct_inv_flow(struct ovn_datapath 
>>> *od, struct hmap *lflows)
>>>                    "ip && ct.trk && ct.inv", debug_drop_action());
>>>  }
>>>
>>> +static void
>>> +build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
>>> +                                     struct ovn_datapath *od,
>>> +                                     const struct nbrec_nat *nat,
>>> +                                     struct ds *match,
>>> +                                     struct ds *actions, bool distributed,
>>> +                                     struct eth_addr mac, int cidr_bits,
>>> +                                     bool is_v6, struct ovn_port 
>>> *l3dgw_port)
>>> +{
>>> +    /* The priority here is calculated such that the
>>> +     * nat->logical_ip with the longest mask gets a higher
>>> +     * priority. */
>>> +    uint16_t priority = cidr_bits + 1;
>>> +    struct ds zone_actions = DS_EMPTY_INITIALIZER;
>>> +
>>> +    ds_put_format(match, "ip && ip%c.src == %s && outport == %s",
>>> +                  is_v6 ? '6' : '4', nat->logical_ip, 
>>> l3dgw_port->json_key);
>>> +
>>> +    if (od->n_l3dgw_ports) {
>>> +        priority += 128;
>>> +        ds_put_format(match, " && is_chassis_resident(\"%s\")",
>>> +                      distributed
>>> +                      ? nat->logical_port
>>> +                      : l3dgw_port->cr_port->key);
>>> +    }
>>> +
>>> +    if (distributed) {
>>> +        ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
>>> +                      ETH_ADDR_ARGS(mac));
>>> +        ds_put_format(&zone_actions, "eth.src = "ETH_ADDR_FMT"; ",
>>> +                      ETH_ADDR_ARGS(mac));
>>> +    }
>>> +
>>> +    if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> +        lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> +                                     is_v6, false, cidr_bits);
>>> +    }
>>> +
>>> +    ds_put_cstr(&zone_actions, REGBIT_DST_NAT_IP_LOCAL" = 0; ");
>>> +
>>> +    ds_put_format(actions, "ct_snat_in_czone(%s", nat->external_ip);
>>> +    ds_put_format(&zone_actions, "ct_snat(%s", nat->external_ip);
>>> +
>>> +    if (nat->external_port_range[0]) {
>>> +        ds_put_format(actions, ",%s", nat->external_port_range);
>>> +        ds_put_format(&zone_actions, ",%s", nat->external_port_range);
>>> +    }
>>> +
>>> +    ds_put_cstr(actions, ");");
>>> +    ds_put_cstr(&zone_actions, ");");
>>> +
>>> +    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
>>> +                            priority, ds_cstr(match),
>>> +                            ds_cstr(actions), &nat->header_);
>>> +
>>> +    ds_put_cstr(match, " && "REGBIT_DST_NAT_IP_LOCAL" == 1");
>>> +
>>> +    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
>>> +                            priority + 1, ds_cstr(match),
>>> +                            ds_cstr(&zone_actions), &nat->header_);
>>> +
>>> +    ds_destroy(&zone_actions);
>>> +}
>>> +
>>>  static void
>>>  build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>>>                              const struct nbrec_nat *nat, struct ds *match,
>>> @@ -13939,109 +14000,67 @@ build_lrouter_out_snat_flow(struct hmap *lflows, 
>>> struct ovn_datapath *od,
>>>          return;
>>>      }
>>>
>>> +    ds_clear(match);
>>> +    ds_clear(actions);
>>> +
>>>      bool stateless = lrouter_nat_is_stateless(nat);
>>> -    if (od->is_gw_router) {
>>> -        ds_clear(match);
>>> -        ds_put_format(match, "ip && ip%s.src == %s",
>>> -                      is_v6 ? "6" : "4", nat->logical_ip);
>>> -        ds_clear(actions);
>>>
>>> -        if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> -            lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> -                                         is_v6, false, cidr_bits);
>>> -        }
>>> +    if (!od->is_gw_router && use_common_zone && !stateless) {
>>> +        build_lrouter_out_snat_in_czone_flow(lflows, od, nat, match, 
>>> actions,
>>> +                                             distributed, mac, cidr_bits,
>>> +                                             is_v6, l3dgw_port);
>>> +        return;
>>> +    }

I would prefer if we have two functions (one for each case) instead of
embedding the "non-common-zone" case here.  The same comment applies to
build_lrouter_in_unsnat_in_czone_flow()

>>>
>>> -        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
>>> -            ds_put_format(actions, "ip%s.src=%s; next;",
>>> -                          is_v6 ? "6" : "4", nat->external_ip);
>>> -        } else {
>>> -            ds_put_format(match, " && (!ct.trk || !ct.rpl)");
>>> -            ds_put_format(actions, "ct_snat(%s", nat->external_ip);
>>> +    /* The priority here is calculated such that the
>>> +     * nat->logical_ip with the longest mask gets a higher
>>> +     * priority. */
>>> +    uint16_t priority = cidr_bits + 1;
>>>
>>> -            if (nat->external_port_range[0]) {
>>> -                ds_put_format(actions, ",%s",
>>> -                              nat->external_port_range);
>>> -            }
>>> -            ds_put_format(actions, ");");
>>> -        }
>>> -
>>> -        /* The priority here is calculated such that the
>>> -        * nat->logical_ip with the longest mask gets a higher
>>> -        * priority. */
>>> -        ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
>>> -                                cidr_bits + 1, ds_cstr(match),
>>> -                                ds_cstr(actions), &nat->header_);
>>> -    } else {
>>> -        uint16_t priority = cidr_bits + 1;
>>> +    ds_put_format(match, "ip && ip%c.src == %s",
>>> +                  is_v6 ? '6' : '4', nat->logical_ip);
>>>
>>> +    if (!od->is_gw_router) {
>>>          /* Distributed router. */
>>> -        ds_clear(match);
>>> -        ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
>>> -                      is_v6 ? "6" : "4", nat->logical_ip,
>>> -                      l3dgw_port->json_key);
>>> +        ds_put_format(match, " && outport == %s", l3dgw_port->json_key);
>>>          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)",
>>> -                              l3dgw_port->cr_port->json_key);
>>> -            }
>>> -        }
>>> -        ds_clear(actions);
>>> -
>>> -        if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> -            lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> -                                         is_v6, false, cidr_bits);
>>> +            priority += 128;
>>> +            ds_put_format(match, " && is_chassis_resident(\"%s\")",
>>> +                          distributed
>>> +                          ? nat->logical_port
>>> +                          : l3dgw_port->cr_port->key);
>>>          }
>>>
>>>          if (distributed) {
>>>              ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
>>>                            ETH_ADDR_ARGS(mac));
>>>          }
>>> +    }
>>>
>>> -        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
>>> -            ds_put_format(actions, "ip%s.src=%s; next;",
>>> -                          is_v6 ? "6" : "4", nat->external_ip);
>>> -        } else {
>>> -            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);
>>> -            }
>>> -            ds_put_format(actions, ");");
>>> -        }
>>> +    if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
>>> +        lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
>>> +                                     is_v6, false, cidr_bits);
>>> +    }
>>>
>>> -        /* The priority here is calculated such that the
>>> -        * nat->logical_ip with the longest mask gets a higher
>>> -        * priority. */
>>> -        ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
>>> -                                priority, ds_cstr(match),
>>> -                                ds_cstr(actions), &nat->header_);
>>> +    if (od->is_gw_router && !stateless) {
>>> +        /* Gateway router. */
>>> +        ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
>>> +    }
>>>
>>> -        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_);
>>> +    if (stateless) {
>>> +        ds_put_format(actions, "ip%c.src=%s; next;",
>>> +                      is_v6 ? '6' : '4', nat->external_ip);
>>> +    } else {
>>> +        ds_put_format(actions, "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, ds_cstr(match),
>>> +                            ds_cstr(actions), &nat->header_);
>>>  }
>>>
>>>  static void
>>> @@ -14428,11 +14447,11 @@ build_lrouter_nat_defrag_and_lb(struct 
>>> ovn_datapath *od, struct hmap *lflows,
>>>          }
>>>
>>>          /* S_ROUTER_IN_UNSNAT */
>>> -        build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, 
>>> distributed,
>>> +        build_lrouter_in_unsnat_flow(lflows, od, nat, match, distributed,
>>>                                       is_v6, l3dgw_port);
>>>          /* S_ROUTER_IN_DNAT */
>>> -        build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, 
>>> distributed,
>>> -                                   cidr_bits, is_v6, l3dgw_port);
>>> +        build_lrouter_in_dnat_flow(lflows, od, nat, match, actions,
>>> +                                   distributed, cidr_bits, is_v6, 
>>> l3dgw_port);
>>>
>>>          /* ARP resolve for NAT IPs. */
>>>          if (od->is_gw_router) {
>>> @@ -14501,16 +14520,19 @@ 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, l3dgw_port);
>>> +        if (use_common_zone) {
>>> +            /* S_ROUTER_OUT_DNAT_LOCAL */
>>> +            build_lrouter_out_is_dnat_local(lflows, od, nat, match, 
>>> actions,
>>> +                                            distributed, is_v6, 
>>> l3dgw_port);
>>> +        }
>>>
>>>          /* S_ROUTER_OUT_UNDNAT */
>>> -        build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, 
>>> distributed,
>>> -                                      mac, is_v6, l3dgw_port);
>>> +        build_lrouter_out_undnat_flow(lflows, od, nat, match, actions,
>>> +                                      distributed, mac, is_v6, l3dgw_port);
>>>          /* S_ROUTER_OUT_SNAT */
>>> -        build_lrouter_out_snat_flow(lflows, od, nat, match, actions, 
>>> distributed,
>>> -                                    mac, cidr_bits, is_v6, l3dgw_port);
>>> +        build_lrouter_out_snat_flow(lflows, od, nat, match, actions,
>>> +                                    distributed, mac, cidr_bits, is_v6,
>>> +                                    l3dgw_port);
>>>
>>>          /* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */
>>>          build_lrouter_ingress_flow(lflows, od, nat, match, actions, mac,
>>> @@ -14580,8 +14602,11 @@ 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.use_snat_zone = 
>>> "REGBIT_DST_NAT_IP_LOCAL"; ");
>>> +                          "flags = 0; flags.loopback = 1; ");
>>> +            if (use_common_zone) {
>>> +                ds_put_cstr(actions, "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);
>>>              }
>>> @@ -14594,7 +14619,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath 
>>> *od, struct hmap *lflows,
>>>          }
>>>      }
>>>
>>> -    if (od->nbr->n_nat) {
>>> +    if (use_common_zone && od->nbr->n_nat) {
>>>          ds_clear(match);
>>>          const char *ct_natted = features->ct_no_masked_label ?
>>>                                  "ct_mark.natted" :
>>> @@ -16508,6 +16533,7 @@ ovnnb_db_run(struct northd_input *input_data,
>>>      install_ls_lb_from_router = smap_get_bool(&nb->options,
>>>                                                "install_ls_lb_from_router",
>>>                                                false);
>>> +    use_common_zone = smap_get_bool(&nb->options, "use_common_zone", 
>>> false);
>>>
>>>      build_chassis_features(input_data->sbrec_chassis_table, 
>>> &data->features);
>>>
>>> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
>>> index 4100cae0d..f11cdc468 100644
>>> --- a/northd/ovn-northd.8.xml
>>> +++ b/northd/ovn-northd.8.xml
>>> @@ -3244,13 +3244,11 @@ icmp6 {
>>>              <p>
>>>                The first flow matches <code>ip &amp;&amp;
>>>                ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var>
>>> -              &amp;&amp; flags.loopback == 0</code> or
>>> -              <code>ip &amp;&amp;
>>> -              ip6.dst == <var>B</var> &amp;&amp; inport == <var>GW</var>
>>> -              &amp;&amp; flags.loopback == 0</code>
>>> +              </code> or <code>ip &amp;&amp; ip6.dst == <var>B</var> 
>>> &amp;&amp;
>>> +              inport == <var>GW</var></code>
>>>                where <var>GW</var> is the distributed gateway port
>>>                corresponding to the NAT rule (specified or inferred), with 
>>> an
>>> -              action <code>ct_snat_in_czone;</code> to unSNAT in the common
>>> +              action <code>ct_snat;</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>next;</code>.
>>> @@ -3263,32 +3261,6 @@ icmp6 {
>>>                <var>GW</var>.
>>>              </p>
>>>            </li>
>>> -
>>> -          <li>
>>> -            <p>
>>> -              The second flow matches <code>ip &amp;&amp;
>>> -              ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var>
>>> -              &amp;&amp; flags.loopback == 1 &amp;&amp;
>>> -              flags.use_snat_zone == 1</code> or
>>> -              <code>ip &amp;&amp;
>>> -              ip6.dst == <var>B</var> &amp;&amp; inport == <var>GW</var>
>>> -              &amp;&amp; flags.loopback == 0 &amp;&amp;
>>> -              flags.use_snat_zone == 1</code>
>>> -              where <var>GW</var> is the distributed gateway port
>>> -              corresponding to the NAT rule (specified or inferred), 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>
>>> @@ -4613,46 +4585,12 @@ nd_ns {
>>>      </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> &amp;&amp;
>>> -          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>
>>>
>>> -    <p>
>>> -      This table also installs a priority-50 logical flow for each logical
>>> -      router that has NATs configured on it. The flow has match
>>> -      <code>ip &amp;&amp; ct_label.natted == 1</code> and action
>>> -      <code>REGBIT_DST_NAT_IP_LOCAL = 1; next;</code>. This is intended
>>> -      to ensure that traffic that was DNATted locally will use a separate
>>> -      conntrack zone for SNAT if SNAT is required later in the egress
>>> -      pipeline. Note that this flow checks the value of
>>> -      <code>ct_label.natted</code>, which is set in the ingress pipeline.
>>> -      This means that ovn-northd assumes that this value is carried over
>>> -      from the ingress pipeline to the egress pipeline and is not altered
>>> -      or cleared. If conntrack label values are ever changed to be cleared
>>> -      between the ingress and egress pipelines, then the match conditions
>>> -      of this flow will be updated accordingly.
>>> -    </p>
>>> -
>>>      <h3>Egress Table 1: UNDNAT</h3>
>>>
>>>      <p>
>>> @@ -4694,7 +4632,7 @@ nd_ns {
>>>            gateway chassis that matches
>>>            <code>ip &amp;&amp; ip4.src == <var>B</var> &amp;&amp;
>>>            outport == <var>GW</var></code>, where <var>GW</var> is the 
>>> logical
>>> -          router gateway port with an action 
>>> <code>ct_dnat_in_czone;</code>.
>>> +          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
>>>            match also includes <code>P.src</code> == <var>PORT</var>.  These
>>> @@ -4716,7 +4654,7 @@ nd_ns {
>>>            matches <code>ip &amp;&amp; ip4.src == <var>B</var>
>>>            &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
>>>            is the logical router gateway port, with an action
>>> -          <code>ct_dnat_in_czone;</code>. If the NAT rule is of type
>>> +          <code>ct_dnat;</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>next;</code>.
>>>          </p>
>>> @@ -4724,7 +4662,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 with the action <code>ct_dnat_in_czone</code>.
>>> +          gateway chassis with the action <code>ct_dnat</code>.
>>>          </p>
>>>
>>>          <p>
>>> @@ -4900,24 +4838,11 @@ nd_ns {
>>>              and match <code>ip &amp;&amp; ip4.src == <var>A</var> 
>>> &amp;&amp;
>>>              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
>>> +            <code>ct_snat(<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 calculated priority
>>> -            <code><var>P</var> + 1 </code> and match
>>> -            <code>ip &amp;&amp; ip4.src == <var>A</var> &amp;&amp;
>>> -            outport == <var>GW</var> &amp;&amp;
>>> -            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>
>>> @@ -5024,7 +4949,6 @@ clone {
>>>      outport = "";
>>>      flags = 0;
>>>      flags.loopback = 1;
>>> -    flags.use_snat_zone = REGBIT_DST_NAT_IP_LOCAL;
>>>      reg0 = 0;
>>>      reg1 = 0;
>>>      ...
>>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>>> index d6694778f..f5d2944e6 100644
>>> --- a/ovn-nb.xml
>>> +++ b/ovn-nb.xml
>>> @@ -314,6 +314,15 @@
>>>          </p>
>>>        </column>
>>>
>>> +      <column name="options" key="use_common_zone" type='{"type": 
>>> "boolean"}'>
>>> +        Default value is <code>false</code>. If set to <code>true</code>
>>> +        the SNAT and DNAT happens in common zone, instead of happening in
>>> +        separate zones, depending on the configuration. However, this 
>>> option
>>> +        breaks traffic when there is configuration of DGP + LB + SNAT on
>>> +        this LR. The value <code>true</code> should be used only in case
>>> +        of HWOL compatibility with GDP.
>>> +      </column>
>>> +
>>>        <group title="Options for configuring interconnection route 
>>> advertisement">
>>>          <p>
>>>            These options control how routes are advertised between OVN
>>> @@ -2575,6 +2584,7 @@ or
>>>          exceeding this timeout will be automatically removed. The value
>>>          defaults to 0, which means disabled.
>>>        </column>
>>> +

Unrelated change.

>>>      </group>
>>>
>>>      <group title="Common Columns">
>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>> index a3db189ac..6f5650416 100644
>>> --- a/tests/ovn-northd.at
>>> +++ b/tests/ovn-northd.at
>>> @@ -892,7 +892,7 @@ 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 3 4 2 0 0 0 0
>>> +check_flow_match_sets 2 2 2 0 0 0 0
>>>  ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
>>>
>>>  echo
>>> @@ -904,7 +904,7 @@ 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 3 4 2 0 0 0 0
>>> +check_flow_match_sets 2 2 2 0 0 0 0
>>>  ovn-nbctl lr-nat-del R1 dnat_and_snat  fd01::1
>>>
>>>  echo
>>> @@ -939,9 +939,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 3 = `grep -c 
>>> lr_in_unsnat sbflows`])
>>> +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows && test 2 = `grep -c 
>>> lr_in_unsnat sbflows`])
>>>  AT_CHECK([grep -c 'ct_snat.*3000' sbflows && grep -c 'ct_dnat.*3000' 
>>> sbflows],
>>> -  [0], [2
>>> +  [0], [1
>>>  1
>>>  ])
>>>
>>> @@ -949,9 +949,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 3 = `grep -c 
>>> lr_in_unsnat sbflows`])
>>> +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows2 && test 2 = `grep -c 
>>> lr_in_unsnat sbflows`])
>>>  AT_CHECK([grep -c 'ct_snat.*3000' sbflows2 && grep -c 'ct_dnat.*3000' 
>>> sbflows2],
>>> -  [1], [2
>>> +  [1], [1
>>>  0
>>>  ])
>>>
>>> @@ -959,7 +959,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 4 = `grep -c 
>>> lr_in_unsnat sbflows3`])
>>> +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows3 && test 3 = `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
>>> @@ -1026,8 +1026,7 @@ AT_CAPTURE_FILE([crflows])
>>>  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);)
>>> +  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(172.16.1.1);)
>>>  ])
>>>
>>>  AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | 
>>> sort], [0], [dnl
>>> @@ -1057,8 +1056,7 @@ AT_CAPTURE_FILE([crflows2])
>>>  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=161  , match=(ip && ip4.src == 
>>> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(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;)
>>>  ])
>>>
>>> @@ -1087,8 +1085,7 @@ AT_CAPTURE_FILE([crflows2])
>>>  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);)
>>> +  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(172.16.1.2);)
>>>  ])
>>>
>>>  AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | 
>>> sort], [0], [dnl
>>> @@ -1115,8 +1112,7 @@ AT_CAPTURE_FILE([crflows2])
>>>  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=161  , match=(ip && ip4.src == 
>>> 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(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;)
>>>  ])
>>>
>>> @@ -5144,6 +5140,9 @@ ovn-nbctl lsp-add public public-lr0 -- set 
>>> Logical_Switch_Port public-lr0 \
>>>      type=router options:router-port=lr0-public \
>>>      -- lsp-set-addresses public-lr0 router
>>>
>>> +# Common zone for DGP
>>> +
>>> +check ovn-nbctl set nb_global . options:use_common_zone="true"
>>>  check ovn-nbctl --wait=sb sync
>>>
>>>  ovn-sbctl dump-flows lr0 > lr0flows
>>> @@ -5196,6 +5195,51 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 
>>> 's/table=./table=?/' | sort], [0], [
>>>    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);)
>>>  ])
>>>
>>> +# Separate zones for DGP
>>> +
>>> +check ovn-nbctl remove nb_global . options use_common_zone
>>> +check ovn-nbctl --wait=sb sync
>>> +
>>> +ovn-sbctl dump-flows lr0 > lr0flows
>>> +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;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
>>> +  table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>> +  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
>>> +  table=7 (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);)
>>> +])
>>> +
>>> +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=100  , match=(ip && ip4.src == 
>>> 10.0.0.3 && outport == "lr0-public" && 
>>> is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> +])
>>> +
>>> +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;)
>>> +  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(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(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(172.168.0.20);)
>>> +])
>>> +
>>>  # Associate load balancer to lr0
>>>
>>>  check ovn-nbctl lb-add lb0 172.168.0.100:8082 "10.0.0.50:82,10.0.0.60:82"
>>> @@ -5207,6 +5251,10 @@ check ovn-nbctl lb-add lb2 172.168.0.210:60 
>>> "10.0.0.50:6062,10.0.0.60:6062" udp
>>>  check ovn-nbctl lr-lb-add lr0 lb0
>>>  check ovn-nbctl lr-lb-add lr0 lb1
>>>  check ovn-nbctl lr-lb-add lr0 lb2
>>> +
>>> +# Common zone for DGP
>>> +
>>> +check ovn-nbctl set nb_global . options:use_common_zone="true"
>>>  check ovn-nbctl --wait=sb sync
>>>
>>>  ovn-sbctl dump-flows lr0 > lr0flows
>>> @@ -5256,10 +5304,10 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | 
>>> sed 's/table=./table=?/' | sor
>>>  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;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src 
>>> == 10.0.0.4 && tcp.src == 8080)) && (inport == "lr0-public" || 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)) 
>>> && (inport == "lr0-public" || 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)) && (inport == "lr0-public" || 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)) && (inport == "lr0-public" || 
>>> outport == "lr0-public") && is_chassis_resident("cr-lr0-public")), 
>>> action=(ct_dnat_in_czone;)
>>>  ])
>>>
>>>  AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | 
>>> sort], [0], [dnl
>>> @@ -5277,6 +5325,69 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 
>>> 's/table=./table=?/' | sort], [0], [
>>>    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);)
>>>  ])
>>>
>>> +# Separate zones for DGP
>>> +
>>> +check ovn-nbctl remove nb_global . options use_common_zone
>>> +check ovn-nbctl --wait=sb sync
>>> +
>>> +ovn-sbctl dump-flows lr0 > lr0flows
>>> +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;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
>>> +  table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
>>> +  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10), action=(ct_dnat;)
>>> +  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.168.0.100), action=(ct_dnat;)
>>> +  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.168.0.200), action=(ct_dnat;)
>>> +  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.168.0.210), action=(ct_dnat;)
>>> +])
>>> +
>>> +AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
>>> +  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
>>> +  table=7 (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=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel 
>>> && ip4 && ip4.dst == 172.168.0.200 && 
>>> is_chassis_resident("cr-lr0-public")), 
>>> action=(ct_lb_mark(backends=10.0.0.80,10.0.0.81);)
>>> +  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel 
>>> && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && 
>>> is_chassis_resident("cr-lr0-public")), 
>>> action=(ct_lb_mark(backends=10.0.0.4:8080);)
>>> +  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel 
>>> && ip4 && ip4.dst == 172.168.0.100 && tcp && tcp.dst == 8082 && 
>>> is_chassis_resident("cr-lr0-public")), 
>>> action=(ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82);)
>>> +  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel 
>>> && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60 && 
>>> is_chassis_resident("cr-lr0-public")), 
>>> action=(ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062);)
>>> +  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel 
>>> && !ct.new && ct_mark.natted), action=(next;)
>>> +  table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est 
>>> && !ct.new), action=(ct_commit_nat;)
>>> +  table=7 (lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel 
>>> && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), 
>>> action=(flags.force_snat_for_lb = 1; next;)
>>> +  table=7 (lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel 
>>> && !ct.new && ct_mark.natted && ct_mark.skip_snat == 1), 
>>> action=(flags.skip_snat_for_lb = 1; next;)
>>> +  table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est 
>>> && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 
>>> 1; ct_commit_nat;)
>>> +  table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est 
>>> && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; 
>>> ct_commit_nat;)
>>> +])
>>> +
>>> +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=100  , match=(ip && ip4.src == 
>>> 10.0.0.3 && outport == "lr0-public" && 
>>> is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src 
>>> == 10.0.0.4 && tcp.src == 8080)) && (inport == "lr0-public" || outport == 
>>> "lr0-public") && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> +  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)) 
>>> && (inport == "lr0-public" || outport == "lr0-public") && 
>>> is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> +  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)) && (inport == "lr0-public" || outport == "lr0-public") && 
>>> is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
>>> +  table=? (lr_out_undnat      ), priority=120  , match=(ip4 && ((ip4.src 
>>> == 10.0.0.80) || (ip4.src == 10.0.0.81)) && (inport == "lr0-public" || 
>>> outport == "lr0-public") && is_chassis_resident("cr-lr0-public")), 
>>> action=(ct_dnat;)
>>> +])
>>> +
>>> +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;)
>>> +  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(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(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(172.168.0.20);)
>>> +])
>>> +
>>>  # Make the logical router as Gateway router
>>>  check ovn-nbctl clear logical_router_port lr0-public gateway_chassis
>>>  check ovn-nbctl set logical_router lr0 options:chassis=gw1
>>> @@ -5318,7 +5429,6 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], 
>>> [dnl
>>>
>>>  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 && 
>>> ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
>>>  ])
>>>
>>>  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | 
>>> sort], [0], [dnl
>>> @@ -5382,7 +5492,6 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], 
>>> [dnl
>>>
>>>  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 && 
>>> ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
>>>  ])
>>>
>>>  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | 
>>> sort], [0], [dnl
>>> @@ -5450,7 +5559,6 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], 
>>> [dnl
>>>
>>>  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 && 
>>> ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
>>>  ])
>>>
>>>  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | 
>>> sort], [0], [dnl
>>> @@ -5530,7 +5638,6 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], 
>>> [dnl
>>>
>>>  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 && 
>>> ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
>>>  ])
>>>
>>>  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | 
>>> sort], [0], [dnl
>>> @@ -7056,21 +7163,15 @@ AT_CHECK([grep lr_in_ip_input lrflows | grep arp | 
>>> grep -e 172.16.1.10 -e 10.0.0
>>>  ])
>>>
>>>  AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && flags.loopback == 0 && 
>>> is_chassis_resident("cr-DR-S2")), action=(ct_snat_in_czone;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && flags.loopback == 1 && 
>>> flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && flags.loopback == 0 && 
>>> is_chassis_resident("cr-DR-S1")), action=(ct_snat_in_czone;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && flags.loopback == 1 && 
>>> flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && flags.loopback == 0 && 
>>> is_chassis_resident("cr-DR-S3")), action=(ct_snat_in_czone;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && flags.loopback == 1 && 
>>> flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat;)
>>> +  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat;)
>>> +  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat;)
>>> +  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat;)
>>>  ])
>>>
>>>  AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat_in_czone(172.16.1.10);)
>>> -  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat_in_czone(10.0.0.10);)
>>> -  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat_in_czone(192.168.0.10);)
>>> -  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && 
>>> reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.10);)
>>> -  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && 
>>> reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(10.0.0.10);)
>>> -  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && 
>>> reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(192.168.0.10);)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat(172.16.1.10);)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat(10.0.0.10);)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat(192.168.0.10);)
>>>  ])
>>>
>>>  check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10
>>> @@ -7099,15 +7200,15 @@ AT_CHECK([grep lr_in_ip_input lrflows | grep arp | 
>>> grep -e 172.16.1.10 -e 10.0.0
>>>  ])
>>>
>>>  AT_CHECK([grep lr_in_dnat lrflows | grep ct_dnat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat_in_czone(20.0.0.10);)
>>> -  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat_in_czone(20.0.0.10);)
>>> -  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat_in_czone(20.0.0.10);)
>>> +  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat(20.0.0.10);)
>>> +  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat(20.0.0.10);)
>>> +  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat(20.0.0.10);)
>>>  ])
>>>
>>>  AT_CHECK([grep lr_out_undnat lrflows | grep ct_dnat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat_in_czone;)
>>> -  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat_in_czone;)
>>> -  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat_in_czone;)
>>> +  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat;)
>>> +  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat;)
>>> +  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat;)
>>>  ])
>>>
>>>  check ovn-nbctl --wait=sb lr-nat-del DR dnat
>>> @@ -7138,33 +7239,27 @@ AT_CHECK([grep lr_in_ip_input lrflows | grep arp | 
>>> grep -e 172.16.1.10 -e 10.0.0
>>>  ])
>>>
>>>  AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && flags.loopback == 0 && 
>>> is_chassis_resident("cr-DR-S2")), action=(ct_snat_in_czone;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && flags.loopback == 1 && 
>>> flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && flags.loopback == 0 && 
>>> is_chassis_resident("cr-DR-S1")), action=(ct_snat_in_czone;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && flags.loopback == 1 && 
>>> flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && flags.loopback == 0 && 
>>> is_chassis_resident("cr-DR-S3")), action=(ct_snat_in_czone;)
>>> -  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && flags.loopback == 1 && 
>>> flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat;)
>>> +  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat;)
>>> +  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat;)
>>> +  table=??(lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat;)
>>>  ])
>>>
>>>  AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat_in_czone(172.16.1.10);)
>>> -  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat_in_czone(10.0.0.10);)
>>> -  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat_in_czone(192.168.0.10);)
>>> -  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && 
>>> reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.10);)
>>> -  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && 
>>> reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(10.0.0.10);)
>>> -  table=??(lr_out_snat        ), priority=162  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && 
>>> reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(192.168.0.10);)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_snat(172.16.1.10);)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_snat(10.0.0.10);)
>>> +  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_snat(192.168.0.10);)
>>>  ])
>>>
>>>  AT_CHECK([grep lr_in_dnat lrflows | grep ct_dnat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat_in_czone(20.0.0.10);)
>>> -  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat_in_czone(20.0.0.10);)
>>> -  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat_in_czone(20.0.0.10);)
>>> +  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 10.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat(20.0.0.10);)
>>> +  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 172.16.1.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat(20.0.0.10);)
>>> +  table=??(lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 
>>> 192.168.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat(20.0.0.10);)
>>>  ])
>>>
>>>  AT_CHECK([grep lr_out_undnat lrflows | grep ct_dnat | sed 
>>> 's/table=../table=??/' | sort], [0], [dnl
>>> -  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat_in_czone;)
>>> -  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat_in_czone;)
>>> -  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat_in_czone;)
>>> +  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
>>> action=(ct_dnat;)
>>> +  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), 
>>> action=(ct_dnat;)
>>> +  table=??(lr_out_undnat      ), priority=100  , match=(ip && ip4.src == 
>>> 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), 
>>> action=(ct_dnat;)
>>>  ])
>>>
>>>  check ovn-nbctl --wait=sb lr-nat-del DR dnat_and_snat
>>> diff --git a/tests/ovn.at b/tests/ovn.at
>>> index a892691ca..a56e32f55 100644
>>> --- a/tests/ovn.at
>>> +++ b/tests/ovn.at
>>> @@ -33566,6 +33566,9 @@ check ovn-nbctl lrp-set-gateway-chassis lr0-ext hv1
>>>  check ovn-nbctl lr-nat-add lr0 snat 172.16.0.2 10.0.0.0/24
>>>  check ovn-nbctl lr-nat-add lr0 dnat 172.16.0.2 10.0.0.2
>>>
>>> +# Use common zone
>>> +check ovn-nbctl set nb_global . options:use_common_zone="true"
>>> +
>>>  check ovn-nbctl --wait=hv sync
>>>  # Use constants so that if tables or registers change, this test can
>>>  # be updated easily.
>>> diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
>>> index c1272d5ef..981cc598c 100644
>>> --- a/tests/system-ovn-kmod.at
>>> +++ b/tests/system-ovn-kmod.at
>>> @@ -928,6 +928,172 @@ EOF
>>>  OVS_WAIT_UNTIL([test "$(cat server.pcap | wc -l)" = "4"])
>>>
>>>
>>> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
>>> +
>>> +as ovn-sb
>>> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>>> +
>>> +as ovn-nb
>>> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>>> +
>>> +as northd
>>> +OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
>>> +
>>> +as
>>> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
>>> +/connection dropped.*/d"])
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([SNAT in separate zone from DNAT])
>>> +

Please mention explicitly in the commit message the fact that we skip
this test for the userspace datapath.  Also, please mention why.

>>> +AT_SKIP_IF([test $HAVE_NC = no])
>>> +CHECK_CONNTRACK()
>>> +CHECK_CONNTRACK_NAT()
>>> +ovn_start
>>> +OVS_TRAFFIC_VSWITCHD_START()
>>> +ADD_BR([br-int])
>>> +
>>> +# Set external-ids in br-int needed for ovn-controller
>>> +ovs-vsctl \
>>> +        -- set Open_vSwitch . external-ids:system-id=hv1 \
>>> +        -- set Open_vSwitch . 
>>> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
>>> +        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
>>> +        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
>>> +        -- set bridge br-int fail-mode=secure 
>>> other-config:disable-in-band=true
>>> +
>>> +# The goal of this test is to ensure that when traffic is first DNATted
>>> +# (by way of a load balancer), and then SNATted, the SNAT happens in a
>>> +# separate conntrack zone from the DNAT.
>>> +
>>> +start_daemon ovn-controller
>>> +
>>> +check ovn-nbctl ls-add public
>>> +
>>> +check ovn-nbctl lr-add r1
>>> +check ovn-nbctl lrp-add r1 r1_public 00:de:ad:ff:00:01 172.16.0.1/16
>>> +check ovn-nbctl lrp-add r1 r1_s1 00:de:ad:fe:00:01 173.0.1.1/24
>>> +check ovn-nbctl lrp-set-gateway-chassis r1_public hv1
>>> +
>>> +check ovn-nbctl lb-add r1_lb 30.0.0.1 172.16.0.102
>>> +check ovn-nbctl lr-lb-add r1 r1_lb
>>> +
>>> +check ovn-nbctl ls-add s1
>>> +check ovn-nbctl lsp-add s1 s1_r1
>>> +check ovn-nbctl lsp-set-type s1_r1 router
>>> +check ovn-nbctl lsp-set-addresses s1_r1 router
>>> +check ovn-nbctl lsp-set-options s1_r1 router-port=r1_s1
>>> +
>>> +check ovn-nbctl lsp-add s1 vm1
>>> +check ovn-nbctl lsp-set-addresses vm1 "00:de:ad:01:00:01 173.0.1.2"
>>> +
>>> +check ovn-nbctl lsp-add public public_r1
>>> +check ovn-nbctl lsp-set-type public_r1 router
>>> +check ovn-nbctl lsp-set-addresses public_r1 router
>>> +check ovn-nbctl lsp-set-options public_r1 router-port=r1_public 
>>> nat-addresses=router
>>> +
>>> +check ovn-nbctl lr-add r2
>>> +check ovn-nbctl lrp-add r2 r2_public 00:de:ad:ff:00:02 172.16.0.2/16
>>> +check ovn-nbctl lrp-add r2 r2_s2 00:de:ad:fe:00:02 173.0.2.1/24
>>> +check ovn-nbctl lr-nat-add r2 dnat_and_snat 172.16.0.102 173.0.2.2
>>> +check ovn-nbctl lrp-set-gateway-chassis r2_public hv1
>>> +
>>> +check ovn-nbctl ls-add s2
>>> +check ovn-nbctl lsp-add s2 s2_r2
>>> +check ovn-nbctl lsp-set-type s2_r2 router
>>> +check ovn-nbctl lsp-set-addresses s2_r2 router
>>> +check ovn-nbctl lsp-set-options s2_r2 router-port=r2_s2
>>> +
>>> +check ovn-nbctl lsp-add s2 vm2
>>> +check ovn-nbctl lsp-set-addresses vm2 "00:de:ad:01:00:02 173.0.2.2"
>>> +
>>> +check ovn-nbctl lsp-add public public_r2
>>> +check ovn-nbctl lsp-set-type public_r2 router
>>> +check ovn-nbctl lsp-set-addresses public_r2 router
>>> +check ovn-nbctl lsp-set-options public_r2 router-port=r2_public 
>>> nat-addresses=router
>>> +
>>> +ADD_NAMESPACES(vm1)
>>> +ADD_VETH(vm1, vm1, br-int, "173.0.1.2/24", "00:de:ad:01:00:01", \
>>> +         "173.0.1.1")
>>> +ADD_NAMESPACES(vm2)
>>> +ADD_VETH(vm2, vm2, br-int, "173.0.2.2/24", "00:de:ad:01:00:02", \
>>> +         "173.0.2.1")
>>> +
>>> +check ovn-nbctl lr-nat-add r1 dnat_and_snat 172.16.0.101 173.0.1.2 vm1 
>>> 00:00:00:01:02:03
>>> +
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +# Create service that listens for TCP and UDP
>>> +NETNS_DAEMONIZE([vm2], [nc -l -u 1234], [nc0.pid])
>>> +NETNS_DAEMONIZE([vm2], [nc -l -k 1235], [nc1.pid])
>>> +
>>> +test_icmp() {
>>> +    # Make sure that a ping works as expected
>>> +    NS_CHECK_EXEC([vm1], [ping -c 3 -i 0.3 -w 2 30.0.0.1 | FORMAT_PING], \
>>> +    [0], [dnl
>>> +3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>> +])
>>> +
>>> +    # Finally, make sure that conntrack shows two separate zones being 
>>> used for
>>> +    # DNAT and SNAT
>>> +    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
>>> +    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +icmp,orig=(src=173.0.1.2,dst=30.0.0.1,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>,mark=2
>>> +])
>>> +
>>> +    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.102) | \
>>> +    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +icmp,orig=(src=172.16.0.101,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
>>> +icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
>>> +])
>>> +}
>>> +
>>> +test_udp() {
>>> +    NS_CHECK_EXEC([vm1], [nc -u 30.0.0.1 1234 -p 1222 -z])
>>> +
>>> +    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
>>> +    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +udp,orig=(src=173.0.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.0.102,dst=173.0.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
>>> +])
>>> +
>>> +    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.102) | \
>>> +    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +udp,orig=(src=172.16.0.101,dst=172.16.0.102,sport=<cleared>,dport=<cleared>),reply=(src=173.0.2.2,dst=172.16.0.101,sport=<cleared>,dport=<cleared>),zone=<cleared>
>>> +udp,orig=(src=173.0.1.2,dst=172.16.0.102,sport=<cleared>,dport=<cleared>),reply=(src=172.16.0.102,dst=172.16.0.101,sport=<cleared>,dport=<cleared>),zone=<cleared>
>>> +])
>>> +}
>>> +
>>> +test_tcp() {
>>> +    NS_CHECK_EXEC([vm1], [nc 30.0.0.1 1235 -z])
>>> +
>>> +    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
>>> +    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +tcp,orig=(src=173.0.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.0.102,dst=173.0.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
>>> +])
>>> +
>>> +    AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.102) | \
>>> +    sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +tcp,orig=(src=172.16.0.101,dst=172.16.0.102,sport=<cleared>,dport=<cleared>),reply=(src=173.0.2.2,dst=172.16.0.101,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>>> +tcp,orig=(src=173.0.1.2,dst=172.16.0.102,sport=<cleared>,dport=<cleared>),reply=(src=172.16.0.102,dst=172.16.0.101,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>>> +])
>>> +}
>>> +
>>> +for type in icmp udp tcp; do
>>> +    AS_BOX([Testing $type])
>>> +    # First time, when the packet needs to pass through pinctrl buffering
>>> +    check ovs-appctl dpctl/flush-conntrack
>>> +    ovn-sbctl --all destroy mac_binding
>>> +    wait_row_count mac_binding 0
>>> +    test_$type
>>> +
>>> +    # Second time with MAC binding being already set
>>> +    check ovs-appctl dpctl/flush-conntrack
>>> +    wait_row_count mac_binding 1 ip="172.16.0.102"
>>> +    test_$type
>>> +done
>>> +
>>>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>>>
>>>  as ovn-sb
>>> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
>>> index bcd829c8b..14c2a2555 100644
>>> --- a/tests/system-ovn.at
>>> +++ b/tests/system-ovn.at
>>> @@ -8699,123 +8699,6 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
>>> patch-.*/d
>>>  AT_CLEANUP
>>>  ])
>>>
>>> -OVN_FOR_EACH_NORTHD([
>>> -AT_SETUP([SNAT in separate zone from DNAT])
>>> -
>>> -CHECK_CONNTRACK()
>>> -CHECK_CONNTRACK_NAT()
>>> -ovn_start
>>> -OVS_TRAFFIC_VSWITCHD_START()
>>> -ADD_BR([br-int])
>>> -
>>> -# Set external-ids in br-int needed for ovn-controller
>>> -ovs-vsctl \
>>> -        -- set Open_vSwitch . external-ids:system-id=hv1 \
>>> -        -- set Open_vSwitch . 
>>> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
>>> -        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
>>> -        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
>>> -        -- set bridge br-int fail-mode=secure 
>>> other-config:disable-in-band=true
>>> -
>>> -# The goal of this test is to ensure that when traffic is first DNATted
>>> -# (by way of a load balancer), and then SNATted, the SNAT happens in a
>>> -# separate conntrack zone from the DNAT.
>>> -
>>> -start_daemon ovn-controller
>>> -
>>> -check ovn-nbctl ls-add public
>>> -
>>> -check ovn-nbctl lr-add r1
>>> -check ovn-nbctl lrp-add r1 r1_public 00:de:ad:ff:00:01 172.16.0.1/16
>>> -check ovn-nbctl lrp-add r1 r1_s1 00:de:ad:fe:00:01 173.0.1.1/24
>>> -check ovn-nbctl lrp-set-gateway-chassis r1_public hv1
>>> -
>>> -check ovn-nbctl lb-add r1_lb 30.0.0.1 172.16.0.102
>>> -check ovn-nbctl lr-lb-add r1 r1_lb
>>> -
>>> -check ovn-nbctl ls-add s1
>>> -check ovn-nbctl lsp-add s1 s1_r1
>>> -check ovn-nbctl lsp-set-type s1_r1 router
>>> -check ovn-nbctl lsp-set-addresses s1_r1 router
>>> -check ovn-nbctl lsp-set-options s1_r1 router-port=r1_s1
>>> -
>>> -check ovn-nbctl lsp-add s1 vm1
>>> -check ovn-nbctl lsp-set-addresses vm1 "00:de:ad:01:00:01 173.0.1.2"
>>> -
>>> -check ovn-nbctl lsp-add public public_r1
>>> -check ovn-nbctl lsp-set-type public_r1 router
>>> -check ovn-nbctl lsp-set-addresses public_r1 router
>>> -check ovn-nbctl lsp-set-options public_r1 router-port=r1_public 
>>> nat-addresses=router
>>> -
>>> -check ovn-nbctl lr-add r2
>>> -check ovn-nbctl lrp-add r2 r2_public 00:de:ad:ff:00:02 172.16.0.2/16
>>> -check ovn-nbctl lrp-add r2 r2_s2 00:de:ad:fe:00:02 173.0.2.1/24
>>> -check ovn-nbctl lr-nat-add r2 dnat_and_snat 172.16.0.102 173.0.2.2
>>> -check ovn-nbctl lrp-set-gateway-chassis r2_public hv1
>>> -
>>> -check ovn-nbctl ls-add s2
>>> -check ovn-nbctl lsp-add s2 s2_r2
>>> -check ovn-nbctl lsp-set-type s2_r2 router
>>> -check ovn-nbctl lsp-set-addresses s2_r2 router
>>> -check ovn-nbctl lsp-set-options s2_r2 router-port=r2_s2
>>> -
>>> -check ovn-nbctl lsp-add s2 vm2
>>> -check ovn-nbctl lsp-set-addresses vm2 "00:de:ad:01:00:02 173.0.2.2"
>>> -
>>> -check ovn-nbctl lsp-add public public_r2
>>> -check ovn-nbctl lsp-set-type public_r2 router
>>> -check ovn-nbctl lsp-set-addresses public_r2 router
>>> -check ovn-nbctl lsp-set-options public_r2 router-port=r2_public 
>>> nat-addresses=router
>>> -
>>> -ADD_NAMESPACES(vm1)
>>> -ADD_VETH(vm1, vm1, br-int, "173.0.1.2/24", "00:de:ad:01:00:01", \
>>> -         "173.0.1.1")
>>> -ADD_NAMESPACES(vm2)
>>> -ADD_VETH(vm2, vm2, br-int, "173.0.2.2/24", "00:de:ad:01:00:02", \
>>> -         "173.0.2.1")
>>> -
>>> -check ovn-nbctl lr-nat-add r1 dnat_and_snat 172.16.0.101 173.0.1.2 vm1 
>>> 00:00:00:01:02:03
>>> -check ovn-nbctl --wait=hv sync
>>> -
>>> -# Next, make sure that a ping works as expected
>>> -NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.1 | FORMAT_PING], \
>>> -[0], [dnl
>>> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
>>> -])
>>> -
>>> -# Finally, make sure that conntrack shows two separate zones being used for
>>> -# DNAT and SNAT
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
>>> -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmp,orig=(src=173.0.1.2,dst=30.0.0.1,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>,mark=2
>>> -])
>>> -
>>> -# The final two entries appear identical here. That is because FORMAT_CT
>>> -# scrubs the zone numbers. In actuality, the zone numbers are different,
>>> -# which is why there are two entries.
>>> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.102) | \
>>> -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> -icmp,orig=(src=172.16.0.101,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
>>> -icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
>>> -icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
>>> -])
>>> -
>>> -OVS_APP_EXIT_AND_WAIT([ovn-controller])
>>> -
>>> -as ovn-sb
>>> -OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>>> -
>>> -as ovn-nb
>>> -OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>>> -
>>> -as northd
>>> -OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
>>> -
>>> -as
>>> -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
>>> -/connection dropped.*/d"])
>>> -AT_CLEANUP
>>> -])
>>> -
>>>  OVN_FOR_EACH_NORTHD([
>>>  AT_SETUP([LB - ICMP related traffic])
>>>
>>
>>
>> _______________________________________________
>> dev mailing list
>> d...@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
> 


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to