diff mbox series

[ovs-dev,v5] northd: Add support for NAT with multiple DGP

Message ID 20220401130748.6095-1-sangana.abhiram@nutanix.com
State Accepted
Headers show
Series [ovs-dev,v5] northd: Add support for NAT with multiple DGP | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Abhiram Sangana April 1, 2022, 1:07 p.m. UTC
Currently, if multiple distributed gateway ports (DGP) are configured
on a logical router, NAT is disabled as part of commit 15348b7
(northd: Multiple distributed gateway port support.)

This patch adds a new column called "gateway_port" in NAT table that
references a distributed gateway port in the Logical_Router_Port
table. A NAT rule is only applied on matching packets entering or
leaving the DGP configured for the rule, when a router has
multiple DGPs.

If a router has a single DGP, NAT rules are applied at the DGP even if
the "gateway_port" column is not set. It is an error to not set this
column for a NAT rule when the router has multiple DGPs.

This patch also updates the NAT commands in ovn-nbctl to support the
new column.

Signed-off-by: Abhiram Sangana <sangana.abhiram@nutanix.com>
---
 NEWS                      |   1 +
 northd/northd.c           | 184 +++++++++++++++++++++--------------
 northd/ovn-northd.8.xml   |  27 +++---
 ovn-architecture.7.xml    |   6 +-
 ovn-nb.ovsschema          |  10 +-
 ovn-nb.xml                |  37 ++++++-
 tests/ovn-nbctl.at        | 197 +++++++++++++++++++++++++-------------
 tests/ovn-northd.at       | 176 ++++++++++++++++++++++++++++++++--
 tests/ovn.at              |   2 +-
 utilities/ovn-nbctl.8.xml |  48 ++++++----
 utilities/ovn-nbctl.c     | 170 +++++++++++++++++++++++++++-----
 11 files changed, 651 insertions(+), 207 deletions(-)

Comments

0-day Robot April 1, 2022, 2:19 p.m. UTC | #1
References:  <20220401130748.6095-1-sangana.abhiram@nutanix.com>
 

Bleep bloop.  Greetings Abhiram Sangana, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line is 275 characters long (recommended limit is 79)
#1214 FILE: utilities/ovn-nbctl.8.xml:1145:
      <dt>[<code>--may-exist</code>] [<code>--stateless</code>] [<code>--gateway_port</code>=<var>GATEWAY_PORT</var>] <code>lr-nat-add</code> <var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>

WARNING: Line is 143 characters long (recommended limit is 79)
#1259 FILE: utilities/ovn-nbctl.8.xml:1222:
      <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>] [<var>gateway_port</var>]]</dt>

WARNING: Line lacks whitespace around operator
WARNING: Line lacks whitespace around operator
WARNING: Line lacks whitespace around operator
#1305 FILE: utilities/ovn-nbctl.c:381:
  [--gateway-port=GATEWAY_PORT]\n\

WARNING: Line lacks whitespace around operator
#1310 FILE: utilities/ovn-nbctl.c:385:
  lr-nat-del ROUTER [TYPE [IP] [GATEWAY_PORT]]\n\

Lines checked: 1596, Warnings: 6, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Mark Michelson April 4, 2022, 8:05 p.m. UTC | #2
Hi Abhriam,

Thanks for you patience on this. It looks good by me.

Acked-by: Mark Michelson <mmichels@redhat.com>

On 4/1/22 09:07, Abhiram Sangana wrote:
> Currently, if multiple distributed gateway ports (DGP) are configured
> on a logical router, NAT is disabled as part of commit 15348b7
> (northd: Multiple distributed gateway port support.)
> 
> This patch adds a new column called "gateway_port" in NAT table that
> references a distributed gateway port in the Logical_Router_Port
> table. A NAT rule is only applied on matching packets entering or
> leaving the DGP configured for the rule, when a router has
> multiple DGPs.
> 
> If a router has a single DGP, NAT rules are applied at the DGP even if
> the "gateway_port" column is not set. It is an error to not set this
> column for a NAT rule when the router has multiple DGPs.
> 
> This patch also updates the NAT commands in ovn-nbctl to support the
> new column.
> 
> Signed-off-by: Abhiram Sangana <sangana.abhiram@nutanix.com>
> ---
>   NEWS                      |   1 +
>   northd/northd.c           | 184 +++++++++++++++++++++--------------
>   northd/ovn-northd.8.xml   |  27 +++---
>   ovn-architecture.7.xml    |   6 +-
>   ovn-nb.ovsschema          |  10 +-
>   ovn-nb.xml                |  37 ++++++-
>   tests/ovn-nbctl.at        | 197 +++++++++++++++++++++++++-------------
>   tests/ovn-northd.at       | 176 ++++++++++++++++++++++++++++++++--
>   tests/ovn.at              |   2 +-
>   utilities/ovn-nbctl.8.xml |  48 ++++++----
>   utilities/ovn-nbctl.c     | 170 +++++++++++++++++++++++++++-----
>   11 files changed, 651 insertions(+), 207 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 3e8358723..c881764a6 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -4,6 +4,7 @@ Post v22.03.0
>       different OVN Interconnection availability zones.
>     - Replaced the usage of masked ct_label by ct_mark in most cases to work
>       better with hardware-offloading.
> +  - Support NAT for logical routers with multiple distributed gateway ports.
>   
>   OVN v22.03.0 - 11 Mar 2022
>   --------------------------
> diff --git a/northd/northd.c b/northd/northd.c
> index 2fb0a93c2..ad0e8b95d 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -617,11 +617,11 @@ struct ovn_datapath {
>   
>       /* Applies to only logical router datapath.
>        * True if logical router is a gateway router. i.e options:chassis is set.
> -     * If this is true, then 'l3dgw_port' will be ignored. */
> +     * If this is true, then 'l3dgw_ports' will be ignored. */
>       bool is_gw_router;
>   
> -    /* OVN northd only needs to know about the logical router gateway port for
> -     * NAT on a distributed router.  The "distributed gateway ports" are
> +    /* OVN northd only needs to know about logical router gateway ports for
> +     * NAT/LB on a distributed router.  The "distributed gateway ports" are
>        * populated only when there is a gateway chassis or ha chassis group
>        * specified for some of the ports on the logical router. Otherwise this
>        * will be NULL. */
> @@ -2713,8 +2713,9 @@ join_logical_ports(struct northd_input *input_data,
>    * by one or more IP addresses, and if the port is a distributed gateway
>    * port, followed by 'is_chassis_resident("LPORT_NAME")', where the
>    * LPORT_NAME is the name of the L3 redirect port or the name of the
> - * logical_port specified in a NAT rule.  These strings include the
> - * external IP addresses of all NAT rules defined on that router, and all
> + * logical_port specified in a NAT rule. These strings include the
> + * external IP addresses of NAT rules defined on that router which have
> + * gateway_port not set or have gateway_port as the router port 'op', and all
>    * of the IP addresses used in load balancer VIPs defined on that router.
>    *
>    * The caller must free each of the n returned strings with free(),
> @@ -2727,8 +2728,7 @@ get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
>       struct eth_addr mac;
>       if (!op || !op->nbrp || !op->od || !op->od->nbr
>           || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
> -        || !eth_addr_from_string(op->nbrp->mac, &mac)
> -        || op->od->n_l3dgw_ports > 1) {
> +        || !eth_addr_from_string(op->nbrp->mac, &mac)) {
>           *n = n_nats;
>           return NULL;
>       }
> @@ -2757,6 +2757,12 @@ get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
>               continue;
>           }
>   
> +        /* Not including external IP of NAT rules whose gateway_port is
> +         * not 'op'. */
> +        if (nat->gateway_port && nat->gateway_port != op->nbrp) {
> +            continue;
> +        }
> +
>           /* Determine whether this NAT rule satisfies the conditions for
>            * distributed NAT processing. */
>           if (op->od->n_l3dgw_ports && !strcmp(nat->type, "dnat_and_snat")
> @@ -2827,9 +2833,9 @@ get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
>       if (central_ip_address) {
>           /* Gratuitous ARP for centralized NAT rules on distributed gateway
>            * ports should be restricted to the gateway chassis. */
> -        if (op->od->n_l3dgw_ports) {
> +        if (is_l3dgw_port(op)) {
>               ds_put_format(&c_addresses, " is_chassis_resident(%s)",
> -                          op->od->l3dgw_ports[0]->cr_port->json_key);
> +                          op->cr_port->json_key);
>           }
>   
>           addresses[n_nats++] = ds_steal_cstr(&c_addresses);
> @@ -3469,9 +3475,12 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>                   }
>   
>                   if (op->peer->od->n_l3dgw_ports) {
> +                    const struct ovn_port *l3dgw_port = (
> +                        is_l3dgw_port(op->peer)
> +                        ? op->peer
> +                        : op->peer->od->l3dgw_ports[0]);
>                       ds_put_format(&garp_info, " is_chassis_resident(%s)",
> -                                  op->peer->od->l3dgw_ports[0]
> -                                  ->cr_port->json_key);
> +                                  l3dgw_port->cr_port->json_key);
>                   }
>   
>                   n_nats++;
> @@ -10367,6 +10376,12 @@ build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
>       const struct nbrec_nat *nat = nat_entry->nb;
>       struct ds match = DS_EMPTY_INITIALIZER;
>   
> +    /* ARP/ND should be sent from distributed gateway port specified in
> +     * the NAT rule. */
> +    if (nat->gateway_port && nat->gateway_port != op->nbrp) {
> +        return;
> +    }
> +
>       /* Mac address to use when replying to ARP/NS. */
>       const char *mac_s = REG_INPORT_ETH_ADDR;
>       struct eth_addr mac;
> @@ -10390,10 +10405,9 @@ build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
>            * upstream MAC learning points to the gateway chassis.
>            * Also need to avoid generation of multiple ARP responses
>            * from different chassis. */
> -        if (op->od->n_l3dgw_ports) {
> -            ds_put_format(&match, "is_chassis_resident(%s)",
> -                          op->od->l3dgw_ports[0]->cr_port->json_key);
> -        }
> +        ovs_assert(is_l3dgw_port(op));
> +        ds_put_format(&match, "is_chassis_resident(%s)",
> +                      op->cr_port->json_key);
>       }
>   
>       /* Respond to ARP/NS requests on the chassis that binds the gw
> @@ -12139,7 +12153,7 @@ build_ipv6_input_flows_for_lrouter_port(
>           struct ds *match, struct ds *actions,
>           const struct shash *meter_groups)
>   {
> -    if (op->nbrp && (!op->l3dgw_port)) {
> +    if (op->nbrp && !is_cr_port(op)) {
>           /* No ingress packets are accepted on a chassisredirect
>            * port, so no need to program flows for that port. */
>           if (op->lrp_networks.n_ipv6_addrs) {
> @@ -12268,7 +12282,7 @@ build_ipv6_input_flows_for_lrouter_port(
>               ds_clear(match);
>               ds_clear(actions);
>               ds_clear(&ip_ds);
> -            if (op->od->n_l3dgw_ports && op->od->l3dgw_ports[0] == op) {
> +            if (is_l3dgw_port(op)) {
>                   ds_put_cstr(&ip_ds, "ip6.dst <-> ip6.src");
>               } else {
>                   ds_put_format(&ip_ds, "ip6.dst = ip6.src; ip6.src = %s",
> @@ -12358,7 +12372,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>   {
>       /* No ingress packets are accepted on a chassisredirect
>        * port, so no need to program flows for that port. */
> -    if (op->nbrp && (!op->l3dgw_port)) {
> +    if (op->nbrp && !is_cr_port(op)) {
>           if (op->lrp_networks.n_ipv4_addrs) {
>               /* L3 admission control: drop packets that originate from an
>                * IPv4 address owned by the router or a broadcast address
> @@ -12400,7 +12414,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>               ds_clear(match);
>               ds_clear(actions);
>               ds_clear(&ip_ds);
> -            if (op->od->n_l3dgw_ports && op->od->l3dgw_ports[0] == op) {
> +            if (is_l3dgw_port(op)) {
>                   ds_put_cstr(&ip_ds, "ip4.dst <-> ip4.src");
>               } else {
>                   ds_put_format(&ip_ds, "ip4.dst = ip4.src; ip4.src = %s",
> @@ -12645,7 +12659,8 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>   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)
> +                             struct ds *actions, bool distributed, bool is_v6,
> +                             struct ovn_port *l3dgw_port)
>   {
>       /* Ingress UNSNAT table: It is for already established connections'
>       * reverse traffic. i.e., SNAT has already been done in egress
> @@ -12684,12 +12699,12 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>           ds_clear(actions);
>           ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
>                         "flags.loopback == 0", is_v6 ? "6" : "4",
> -                      nat->external_ip, od->l3dgw_ports[0]->json_key);
> +                      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)",
> -                          od->l3dgw_ports[0]->cr_port->json_key);
> +                          l3dgw_port->cr_port->json_key);
>           }
>   
>           if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
> @@ -12709,12 +12724,12 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>               ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
>                             "flags.loopback == 1 && flags.use_snat_zone == 1",
>                             is_v6 ? "6" : "4", nat->external_ip,
> -                          od->l3dgw_ports[0]->json_key);
> +                          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)",
> -                            od->l3dgw_ports[0]->cr_port->json_key);
> +                            l3dgw_port->cr_port->json_key);
>               }
>               ds_put_cstr(actions, "ct_snat;");
>               ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
> @@ -12728,7 +12743,8 @@ static void
>   build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>                              const struct nbrec_nat *nat, struct ds *match,
>                              struct ds *actions, bool distributed,
> -                           ovs_be32 mask, bool is_v6)
> +                           ovs_be32 mask, bool is_v6,
> +                           struct ovn_port *l3dgw_port)
>   {
>       /* Ingress DNAT table: Packets enter the pipeline with destination
>       * IP address that needs to be DNATted from a external IP address
> @@ -12780,12 +12796,12 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>               ds_clear(match);
>               ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
>                             is_v6 ? "6" : "4", nat->external_ip,
> -                          od->l3dgw_ports[0]->json_key);
> +                          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)",
> -                              od->l3dgw_ports[0]->cr_port->json_key);
> +                              l3dgw_port->cr_port->json_key);
>               }
>               ds_clear(actions);
>               if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
> @@ -12815,7 +12831,8 @@ static void
>   build_lrouter_out_undnat_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, bool is_v6)
> +                              struct eth_addr mac, bool is_v6,
> +                              struct ovn_port *l3dgw_port)
>   {
>       /* Egress UNDNAT table: It is for already established connections'
>       * reverse traffic. i.e., DNAT has already been done in ingress
> @@ -12832,12 +12849,12 @@ 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,
> -                  od->l3dgw_ports[0]->json_key);
> +                  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)",
> -                      od->l3dgw_ports[0]->cr_port->json_key);
> +                      l3dgw_port->cr_port->json_key);
>       }
>       ds_clear(actions);
>       if (distributed) {
> @@ -12863,7 +12880,7 @@ static void
>   build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
>                                   const struct nbrec_nat *nat, struct ds *match,
>                                   struct ds *actions, bool distributed,
> -                                bool is_v6)
> +                                bool is_v6, struct ovn_port *l3dgw_port)
>   {
>       /* Note that this only applies for NAT on a distributed router.
>        */
> @@ -12878,7 +12895,7 @@ build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
>           ds_put_format(match, "is_chassis_resident(\"%s\")", nat->logical_port);
>       } else {
>           ds_put_format(match, "is_chassis_resident(%s)",
> -                      od->l3dgw_ports[0]->cr_port->json_key);
> +                      l3dgw_port->cr_port->json_key);
>       }
>   
>       ds_clear(actions);
> @@ -12894,7 +12911,8 @@ build_lrouter_out_snat_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, ovs_be32 mask,
> -                            int cidr_bits, bool is_v6)
> +                            int cidr_bits, bool is_v6,
> +                            struct ovn_port *l3dgw_port)
>   {
>       /* Egress SNAT table: Packets enter the egress pipeline with
>       * source ip address that needs to be SNATted to a external ip
> @@ -12941,7 +12959,7 @@ build_lrouter_out_snat_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,
> -                      od->l3dgw_ports[0]->json_key);
> +                      l3dgw_port->json_key);
>           if (od->n_l3dgw_ports) {
>               if (distributed) {
>                   ovs_assert(nat->logical_port);
> @@ -12953,7 +12971,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>                   * programmed on the gateway chassis. */
>                   priority += 128;
>                   ds_put_format(match, " && is_chassis_resident(%s)",
> -                              od->l3dgw_ports[0]->cr_port->json_key);
> +                              l3dgw_port->cr_port->json_key);
>               }
>           }
>           ds_clear(actions);
> @@ -13012,13 +13030,13 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
>                                           const struct nbrec_nat *nat,
>                                           struct ovn_datapath *od, bool is_v6,
>                                           struct ds *match, struct ds *actions,
> -                                        int mtu,
> +                                        int mtu, struct ovn_port *l3dgw_port,
>                                           const struct shash *meter_groups)
>   {
>           ds_clear(match);
>           ds_put_format(match, "inport == %s && "REGBIT_PKT_LARGER
>                         " && "REGBIT_EGRESS_LOOPBACK" == 0",
> -                      od->l3dgw_ports[0]->json_key);
> +                      l3dgw_port->json_key);
>   
>           ds_clear(actions);
>           if (!is_v6) {
> @@ -13039,7 +13057,7 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
>                   "outport = %s; flags.loopback = 1; output; };",
>                   nat->external_mac,
>                   nat->external_ip,
> -                mtu, od->l3dgw_ports[0]->json_key);
> +                mtu, l3dgw_port->json_key);
>               ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, 160,
>                                         ds_cstr(match), ds_cstr(actions),
>                                         NULL,
> @@ -13066,7 +13084,7 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
>                   "outport = %s; flags.loopback = 1; output; };",
>                   nat->external_mac,
>                   nat->external_ip,
> -                mtu, od->l3dgw_ports[0]->json_key);
> +                mtu, l3dgw_port->json_key);
>               ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, 160,
>                                         ds_cstr(match), ds_cstr(actions),
>                                         NULL,
> @@ -13083,13 +13101,14 @@ build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
>                              const struct nbrec_nat *nat, struct ds *match,
>                              struct ds *actions, struct eth_addr mac,
>                              bool distributed, bool is_v6,
> +                           struct ovn_port *l3dgw_port,
>                              const struct shash *meter_groups)
>   {
>       if (od->n_l3dgw_ports && !strcmp(nat->type, "snat")) {
>           ds_clear(match);
>           ds_put_format(
>               match, "inport == %s && %s == %s",
> -            od->l3dgw_ports[0]->json_key,
> +            l3dgw_port->json_key,
>               is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
>           ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
>                                   120, ds_cstr(match), "next;",
> @@ -13105,32 +13124,33 @@ build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
>           * This will save us from having to match on inport further
>           * down in the pipeline.
>           */
> -        int gw_mtu = smap_get_int(&od->l3dgw_ports[0]->nbrp->options,
> +        int gw_mtu = smap_get_int(&l3dgw_port->nbrp->options,
>                                     "gateway_mtu", 0);
>           ds_clear(match);
>           ds_put_format(match,
>                         "eth.dst == "ETH_ADDR_FMT" && inport == %s"
>                         " && is_chassis_resident(\"%s\")",
>                         ETH_ADDR_ARGS(mac),
> -                      od->l3dgw_ports[0]->json_key,
> +                      l3dgw_port->json_key,
>                         nat->logical_port);
> -        build_gateway_mtu_flow(lflows, od->l3dgw_ports[0],
> +        build_gateway_mtu_flow(lflows, l3dgw_port,
>                                  S_ROUTER_IN_ADMISSION, 50, 55,
>                                  match, actions, &nat->header_,
>                                  REG_INPORT_ETH_ADDR " = %s; next;",
> -                               od->l3dgw_ports[0]->lrp_networks.ea_s);
> +                               l3dgw_port->lrp_networks.ea_s);
>           if (gw_mtu) {
>               build_lrouter_ingress_nat_check_pkt_len(lflows, nat, od, is_v6,
>                                                       match, actions, gw_mtu,
> -                                                    meter_groups);
> +                                                    l3dgw_port, meter_groups);
>           }
>       }
>   }
>   
>   static int
>   lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
> -                        ovs_be32 *mask, bool *is_v6, int *cidr_bits,
> -                        struct eth_addr *mac, bool *distributed)
> +                        const struct hmap *ports, ovs_be32 *mask,
> +                        bool *is_v6, int *cidr_bits, struct eth_addr *mac,
> +                        bool *distributed, struct ovn_port **nat_l3dgw_port)
>   {
>       struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
>       ovs_be32 ip;
> @@ -13164,6 +13184,32 @@ lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
>           *is_v6 = true;
>       }
>   
> +    /* Validate gateway_port of NAT rule. */
> +    *nat_l3dgw_port = NULL;
> +    if (nat->gateway_port == NULL) {
> +        if (od->n_l3dgw_ports > 1) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +            VLOG_WARN_RL(&rl, "NAT configured on logical router: %s with"
> +                         "multiple distributed gateway ports needs to specify"
> +                         "valid gateway_port.", od->nbr->name);
> +            return -EINVAL;
> +        } else if (od->n_l3dgw_ports) {
> +            *nat_l3dgw_port = od->l3dgw_ports[0];
> +        }
> +    } else {
> +        *nat_l3dgw_port = ovn_port_find(ports, nat->gateway_port->name);
> +
> +        if (!(*nat_l3dgw_port) || (*nat_l3dgw_port)->od != od ||
> +            !is_l3dgw_port(*nat_l3dgw_port)) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +            VLOG_WARN_RL(&rl, "gateway_port: %s of NAT configured on "
> +                         "logical router: %s is not a valid distributed "
> +                         "gateway port on that router",
> +                         nat->gateway_port->name, od->nbr->name);
> +            return -EINVAL;
> +        }
> +    }
> +
>       /* Check the validity of nat->logical_ip. 'logical_ip' can
>       * be a subnet when the type is "snat". */
>       if (*is_v6) {
> @@ -13263,24 +13309,12 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>       ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
>   
>       /* NAT rules are only valid on Gateway routers and routers with
> -     * l3dgw_port (router has a port with gateway chassis
> +     * l3dgw_ports (router has port(s) with gateway chassis
>        * specified). */
>       if (!od->is_gw_router && !od->n_l3dgw_ports) {
>           return;
>       }
>   
> -    /* NAT rules are not currently supported on logical routers with multiple
> -     * distributed gateway ports. */
> -    if (od->n_l3dgw_ports > 1) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_WARN_RL(&rl, "NAT is configured on logical router %s, which has %"
> -                     PRIuSIZE" distributed gateway ports. NAT is not supported"
> -                     " yet when there is more than one distributed gateway "
> -                     "port on the router.",
> -                     od->nbr->name, od->n_l3dgw_ports);
> -        return;
> -    }
> -
>       struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
>   
>       bool dnat_force_snat_ip =
> @@ -13294,18 +13328,19 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>           bool is_v6, distributed;
>           ovs_be32 mask;
>           int cidr_bits;
> +        struct ovn_port *l3dgw_port;
>   
> -        if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits,
> -                                    &mac, &distributed) < 0) {
> +        if (lrouter_check_nat_entry(od, nat, ports, &mask, &is_v6, &cidr_bits,
> +                                    &mac, &distributed, &l3dgw_port) < 0) {
>               continue;
>           }
>   
>           /* S_ROUTER_IN_UNSNAT */
>           build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, distributed,
> -                                     is_v6);
> +                                     is_v6, l3dgw_port);
>           /* S_ROUTER_IN_DNAT */
>           build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, distributed,
> -                                   mask, is_v6);
> +                                   mask, is_v6, l3dgw_port);
>   
>           /* ARP resolve for NAT IPs. */
>           if (od->is_gw_router) {
> @@ -13318,14 +13353,14 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>                   ds_clear(match);
>                   ds_put_format(
>                       match, "outport == %s && %s == %s",
> -                    od->l3dgw_ports[0]->json_key,
> +                    l3dgw_port->json_key,
>                       is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
>                       nat->external_ip);
>                   ds_clear(actions);
>                   ds_put_format(
>                       actions, "eth.dst = %s; next;",
>                       distributed ? nat->external_mac :
> -                    od->l3dgw_ports[0]->lrp_networks.ea_s);
> +                    l3dgw_port->lrp_networks.ea_s);
>                   ovn_lflow_add_with_hint(lflows, od,
>                                           S_ROUTER_IN_ARP_RESOLVE,
>                                           100, ds_cstr(match),
> @@ -13337,18 +13372,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);
> +                                        distributed, is_v6, l3dgw_port);
>   
>           /* S_ROUTER_OUT_UNDNAT */
>           build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed,
> -                                      mac, is_v6);
> +                                      mac, is_v6, l3dgw_port);
>           /* S_ROUTER_OUT_SNAT */
>           build_lrouter_out_snat_flow(lflows, od, nat, match, actions, distributed,
> -                                    mac, mask, cidr_bits, is_v6);
> +                                    mac, mask, 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, distributed, is_v6, meter_groups);
> +        build_lrouter_ingress_flow(lflows, od, nat, match, actions, mac,
> +                                   distributed, is_v6, l3dgw_port,
> +                                   meter_groups);
>   
>           /* Ingress Gateway Redirect Table: For NAT on a distributed
>            * router, add flows that are specific to a NAT rule.  These
> @@ -13365,7 +13401,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>               ds_put_format(match,
>                             "ip%s.src == %s && outport == %s",
>                             is_v6 ? "6" : "4", nat->logical_ip,
> -                          od->l3dgw_ports[0]->json_key);
> +                          l3dgw_port->json_key);
>               /* Add a rule to drop traffic from a distributed NAT if
>                * the virtual port has not claimed yet becaused otherwise
>                * the traffic will be centralized misconfiguring the TOR switch.
> @@ -13398,10 +13434,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>               ds_put_format(match, "ip%s.dst == %s && outport == %s",
>                             is_v6 ? "6" : "4",
>                             nat->external_ip,
> -                          od->l3dgw_ports[0]->json_key);
> +                          l3dgw_port->json_key);
>               if (!distributed) {
>                   ds_put_format(match, " && is_chassis_resident(%s)",
> -                              od->l3dgw_ports[0]->cr_port->json_key);
> +                              l3dgw_port->cr_port->json_key);
>               } else {
>                   ds_put_format(match, " && is_chassis_resident(\"%s\")",
>                                 nat->logical_port);
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index 7ebf846aa..85a65703d 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -3026,7 +3026,8 @@ icmp6 {
>                 <code>ip &amp;&amp;
>                 ip6.dst == <var>B</var> &amp;&amp; inport == <var>GW</var>
>                 &amp;&amp; flags.loopback == 0</code>
> -              where <var>GW</var> is the logical router gateway port, with an
> +              where <var>GW</var> is the distributed gateway port
> +              specified in the NAT rule, with an
>                 action <code>ct_snat_in_czone;</code> to unSNAT in the common
>                 zone.  If the NAT rule is of type dnat_and_snat and has
>                 <code>stateless=true</code> in the options, then the action
> @@ -3051,7 +3052,8 @@ icmp6 {
>                 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 logical router gateway port, with an
> +              where <var>GW</var> is the distributed gateway port
> +              specified in the NAT rule, 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
> @@ -3332,9 +3334,10 @@ icmp6 {
>             to change the destination IP address of a packet from <var>A</var> to
>             <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
>             ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
> -          where <var>GW</var> is the logical router gateway port, with an
> -          action <code>ct_dnat(<var>B</var>);</code>.  The match will
> -          include <code>ip6.dst == <var>B</var></code> in the IPv6 case.
> +          where <var>GW</var> is the logical router gateway port configured
> +          for the NAT rule, with an action
> +          <code>ct_dnat(<var>B</var>);</code>.  The match will include
> +          <code>ip6.dst == <var>B</var></code> in the IPv6 case.
>             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>.
> @@ -4178,10 +4181,11 @@ icmp6 {
>           flow with match <code>ip4.src == <var>B</var> &amp;&amp;
>           outport == <var>GW</var></code> &amp;&amp;
>           is_chassis_resident(<var>P</var>), where <var>GW</var> is
> -        the logical router distributed gateway port and <var>P</var>
> -        is the NAT logical port. IP traffic matching the above rule
> -        will be managed locally setting <code>reg1</code> to <var>C</var>
> -        and <code>eth.src</code> to <var>D</var>, where <var>C</var> is NAT
> +        the distributed gateway port specified in the
> +        NAT rule and <var>P</var> is the NAT logical port. IP traffic
> +        matching the above rule will be managed locally setting
> +        <code>reg1</code> to <var>C</var> and
> +        <code>eth.src</code> to <var>D</var>, where <var>C</var> is NAT
>           external ip and <var>D</var> is NAT external mac.
>         </li>
>   
> @@ -4648,8 +4652,9 @@ nd_ns {
>             outport == <var>GW</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.
> +          is the distributed gateway port specified in the NAT rule.
> +          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
> diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
> index ef8d669a2..a48757761 100644
> --- a/ovn-architecture.7.xml
> +++ b/ovn-architecture.7.xml
> @@ -742,9 +742,9 @@
>   
>     <p>
>       A logical router can have multiple distributed gateway ports, each
> -    connecting different external networks. However, some features, such as NAT
> -    and load balancers, are not supported yet for logical routers with more
> -    than one distributed gateway port configured.
> +    connecting different external networks. Load balancing is not yet
> +    supported for logical routers with more than one distributed gateway
> +    port configured.
>     </p>
>   
>     <h4>Physical VLAN MTU Issues</h4>
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index 80b830629..6c69732e9 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>   {
>       "name": "OVN_Northbound",
> -    "version": "6.1.0",
> -    "cksum": "4010776751 31237",
> +    "version": "6.2.0",
> +    "cksum": "2862883635 31533",
>       "tables": {
>           "NB_Global": {
>               "columns": {
> @@ -479,6 +479,12 @@
>                               "refType": "strong"},
>                       "min": 0,
>                       "max": 1}},
> +                "gateway_port": {
> +                    "type": {"key": {"type": "uuid",
> +                                     "refTable": "Logical_Router_Port",
> +                                     "refType": "weak"},
> +                             "min": 0,
> +                             "max": 1}},
>                   "options": {"type": {"key": "string", "value": "string",
>                                        "min": 0, "max": "unlimited"}},
>                   "external_ids": {
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 4d7a23c52..0e3ad2b9b 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -2619,8 +2619,8 @@
>         <p>
>           There can be more than one distributed gateway ports configured
>           on each logical router, each connecting to different L2 segments.
> -        However, features such as NAT and load-balancer are not supported
> -        on logical routers with more than one distributed gateway ports.
> +        Load-balancing is not yet supported on logical routers with more
> +        than one distributed gateway ports.
>         </p>
>   
>         <p>
> @@ -3352,6 +3352,39 @@
>         </p>
>       </column>
>   
> +    <column name="gateway_port">
> +      <p>
> +        A distributed gateway port in the <ref table="Logical_Router_Port"/>
> +        table where the NAT rule needs to be applied.
> +      </p>
> +
> +      <p>
> +        This column needs to be set when multiple distributed gateway ports
> +        are configured on a <ref table="Logical_Router"/> for the NAT rule to
> +        be applied. If logical router has a single distributed gateway port,
> +        NAT rule is applied at the distributed gateway port even if this
> +        column is not set.
> +      </p>
> +
> +      <p>
> +        When multiple distributed gateway ports are configured on a
> +        <ref table="Logical_Router"/>, applying a NAT rule at each of the
> +        distributed gateway ports might not be desired. Consider the case
> +        where a logical router has 2 distributed gateway port, one with
> +        <ref column="networks" table="Logical_Router_Port"/>
> +        <code>50.0.0.10/24</code> and the other with
> +        <ref column="networks" table="Logical_Router_Port"/>
> +        <code>60.0.0.10/24</code>. If the logical router has a
> +        NAT rule of <ref column="type"/> <code>snat</code>,
> +        <ref column="logical_ip"/> <code>10.1.1.0/24</code> and
> +        <ref column="external_ip"/> <code>50.1.1.20/24</code>, the rule needs
> +        to be selectively applied on matching packets entering/leaving
> +        through the distributed gateway port with
> +        <ref column="networks" table="Logical_Router_Port"/>
> +        <code>50.0.0.10/24</code>.
> +      </p>
> +    </column>
> +
>       <column name="options" key="stateless">
>         Indicates if a dnat_and_snat rule should lead to connection
>         tracking state or not.
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 539a121c0..db0d23099 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -510,64 +510,64 @@ AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat fd01::1 fd11::2])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:01:02:03])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat fd01::2 fd11::3 lp0 00:00:00:01:02:03])
>   AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> -TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
> -dnat             30.0.0.1                            192.168.1.2
> -dnat             fd01::1                             fd11::2
> -dnat_and_snat    30.0.0.1                            192.168.1.2
> -dnat_and_snat    30.0.0.2                            192.168.1.3           00:00:00:01:02:03    lp0
> -dnat_and_snat    fd01::1                             fd11::2
> -dnat_and_snat    fd01::2                             fd11::3               00:00:00:01:02:03    lp0
> -snat             30.0.0.1                            192.168.1.0/24
> -snat             fd01::1                             fd11::/64
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat                                   30.0.0.1                            192.168.1.2
> +dnat                                   fd01::1                             fd11::2
> +dnat_and_snat                          30.0.0.1                            192.168.1.2
> +dnat_and_snat                          30.0.0.2                            192.168.1.3         00:00:00:01:02:03    lp0
> +dnat_and_snat                          fd01::1                             fd11::2
> +dnat_and_snat                          fd01::2                             fd11::3             00:00:00:01:02:03    lp0
> +snat                                   30.0.0.1                            192.168.1.0/24
> +snat                                   fd01::1                             fd11::/64
>   ])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24], [1], [],
> -[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists
> +[ovn-nbctl: 30.0.0.1, 192.168.1.0/24, : a NAT with this external_ip, logical_ip and gateway_port already exists
>   ])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.10/24], [1], [],
> -[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists
> +[ovn-nbctl: 30.0.0.1, 192.168.1.0/24, : a NAT with this external_ip, logical_ip and gateway_port already exists
>   ])
>   AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.0/24], [1], [],
> -[ovn-nbctl: a NAT with this type (snat) and logical_ip (192.168.1.0/24) already exists
> +[ovn-nbctl: a NAT with this type (snat), logical_ip (192.168.1.0/24) and gateway_port () already exists
>   ])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2], [1], [],
> -[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists
> +[ovn-nbctl: 30.0.0.1, 192.168.1.2, : a NAT with this external_ip, logical_ip and gateway_port already exists
>   ])
>   AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.3], [1], [],
> -[ovn-nbctl: a NAT with this type (dnat) and external_ip (30.0.0.1) already exists
> +[ovn-nbctl: a NAT with this type (dnat), external_ip (30.0.0.1) and gateway_port () already exists
>   ])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2], [1], [],
> -[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists
> +[ovn-nbctl: 30.0.0.1, 192.168.1.2, : a NAT with this external_ip, logical_ip and gateway_port already exists
>   ])
>   AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.3], [1], [],
> -[ovn-nbctl: a NAT with this type (dnat_and_snat) and external_ip (30.0.0.1) already exists
> +[ovn-nbctl: a NAT with this type (dnat_and_snat), external_ip (30.0.0.1) and gateway_port () already exists
>   ])
>   AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:04:05:06])
>   AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> -TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
> -dnat             30.0.0.1                            192.168.1.2
> -dnat             fd01::1                             fd11::2
> -dnat_and_snat    30.0.0.1                            192.168.1.2
> -dnat_and_snat    30.0.0.2                            192.168.1.3           00:00:00:04:05:06    lp0
> -dnat_and_snat    fd01::1                             fd11::2
> -dnat_and_snat    fd01::2                             fd11::3               00:00:00:01:02:03    lp0
> -snat             30.0.0.1                            192.168.1.0/24
> -snat             fd01::1                             fd11::/64
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat                                   30.0.0.1                            192.168.1.2
> +dnat                                   fd01::1                             fd11::2
> +dnat_and_snat                          30.0.0.1                            192.168.1.2
> +dnat_and_snat                          30.0.0.2                            192.168.1.3         00:00:00:04:05:06    lp0
> +dnat_and_snat                          fd01::1                             fd11::2
> +dnat_and_snat                          fd01::2                             fd11::3             00:00:00:01:02:03    lp0
> +snat                                   30.0.0.1                            192.168.1.0/24
> +snat                                   fd01::1                             fd11::/64
>   ])
>   AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3])
>   AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat fd01::2 fd11::3])
>   AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> -TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
> -dnat             30.0.0.1                            192.168.1.2
> -dnat             fd01::1                             fd11::2
> -dnat_and_snat    30.0.0.1                            192.168.1.2
> -dnat_and_snat    30.0.0.2                            192.168.1.3
> -dnat_and_snat    fd01::1                             fd11::2
> -dnat_and_snat    fd01::2                             fd11::3
> -snat             30.0.0.1                            192.168.1.0/24
> -snat             fd01::1                             fd11::/64
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat                                   30.0.0.1                            192.168.1.2
> +dnat                                   fd01::1                             fd11::2
> +dnat_and_snat                          30.0.0.1                            192.168.1.2
> +dnat_and_snat                          30.0.0.2                            192.168.1.3
> +dnat_and_snat                          fd01::1                             fd11::2
> +dnat_and_snat                          fd01::2                             fd11::3
> +snat                                   30.0.0.1                            192.168.1.0/24
> +snat                                   fd01::1                             fd11::/64
>   ])
>   
>   check_row_count nb:NAT 0 options:stateless=true
> @@ -598,40 +598,31 @@ AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0 dnat_and_snat 40.0.0.3 192.168.1.
>   ])
>   
>   dnl Deletes the NATs
> -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3], [1], [],
> -[ovn-nbctl: no matching NAT with the type (dnat_and_snat) and external_ip (30.0.0.3)
> -])
> -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.2], [1], [],
> -[ovn-nbctl: no matching NAT with the type (dnat) and external_ip (30.0.0.2)
> -])
> -AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 192.168.10.0/24], [1], [],
> -[ovn-nbctl: no matching NAT with the type (snat) and logical_ip (192.168.10.0/24)
> -])
> -AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 192.168.10.0/24])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3])
>   
>   AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.1])
>   AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat fd01::1])
>   AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> -TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
> -dnat             30.0.0.1                            192.168.1.2
> -dnat             fd01::1                             fd11::2
> -dnat_and_snat    30.0.0.2                            192.168.1.3
> -dnat_and_snat    40.0.0.2                            192.168.1.4
> -dnat_and_snat    fd01::2                             fd11::3
> -snat             30.0.0.1                            192.168.1.0/24
> -snat             40.0.0.3                            192.168.1.6
> -snat             fd01::1                             fd11::/64
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat                                   30.0.0.1                            192.168.1.2
> +dnat                                   fd01::1                             fd11::2
> +dnat_and_snat                          30.0.0.2                            192.168.1.3
> +dnat_and_snat                          40.0.0.2                            192.168.1.4
> +dnat_and_snat                          fd01::2                             fd11::3
> +snat                                   30.0.0.1                            192.168.1.0/24
> +snat                                   40.0.0.3                            192.168.1.6
> +snat                                   fd01::1                             fd11::/64
>   ])
>   
>   AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])
>   AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> -TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
> -dnat_and_snat    30.0.0.2                            192.168.1.3
> -dnat_and_snat    40.0.0.2                            192.168.1.4
> -dnat_and_snat    fd01::2                             fd11::3
> -snat             30.0.0.1                            192.168.1.0/24
> -snat             40.0.0.3                            192.168.1.6
> -snat             fd01::1                             fd11::/64
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat_and_snat                          30.0.0.2                            192.168.1.3
> +dnat_and_snat                          40.0.0.2                            192.168.1.4
> +dnat_and_snat                          fd01::2                             fd11::3
> +snat                                   30.0.0.1                            192.168.1.0/24
> +snat                                   40.0.0.3                            192.168.1.6
> +snat                                   fd01::1                             fd11::/64
>   ])
>   
>   AT_CHECK([ovn-nbctl lr-nat-del lr0])
> @@ -702,12 +693,12 @@ AT_CHECK([ovn-nbctl show lr0 | grep -C2 'external port(s): "1"' | uuidfilt], [0]
>   ])
>   
>   AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> -TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
> -dnat             40.0.0.4           1-3000           192.168.1.7
> -dnat             40.0.0.5           1                192.168.1.10
> -dnat_and_snat    40.0.0.5           1-3000           192.168.1.8
> -dnat_and_snat    40.0.0.6           1-3000           192.168.1.9           00:00:00:04:05:06    lp0
> -snat             40.0.0.3           21-65535         192.168.1.6
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat                                   40.0.0.4           1-3000           192.168.1.7
> +dnat                                   40.0.0.5           1                192.168.1.10
> +dnat_and_snat                          40.0.0.5           1-3000           192.168.1.8
> +dnat_and_snat                          40.0.0.6           1-3000           192.168.1.9         00:00:00:04:05:06    lp0
> +snat                                   40.0.0.3           21-65535         192.168.1.6
>   ])
>   
>   AT_CHECK([ovn-nbctl lr-nat-del lr0])
> @@ -755,7 +746,79 @@ AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.16 allowed_range], [1]
>   [ovn-nbctl: 192.168.16: Invalid IP address or CIDR
>   ])
>   
> -AT_CHECK([ovn-nbctl lr-nat-del lr0])])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0])
> +
> +AT_CHECK([ovn-nbctl lrp-add lr0 lrp00 00:00:00:01:02:03 172.64.0.10/24])
> +AT_CHECK([ovn-nbctl lrp-add lr0 lrp01 00:00:00:01:02:04 172.64.1.10/24])
> +AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp00 chassis1])
> +AT_CHECK([ovn-nbctl lr-add lr1])
> +AT_CHECK([ovn-nbctl lrp-add lr1 lrp10 00:00:00:01:02:05 172.64.2.10/24])
> +
> +AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 snat 172.64.0.10 20.0.0.10])
> +AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 172.64.1.10 20.0.0.10], [1], [],
> +[ovn-nbctl: lrp01 is not a distributed gateway router port.
> +])
> +AT_CHECK([ovn-nbctl --gateway-port=lrp10 lr-nat-add lr0 dnat_and_snat 172.64.2.10 20.0.0.10], [1], [],
> +[ovn-nbctl: lrp10 is not a router port of logical router: lr0.
> +])
> +
> +AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp01 chassis2])
> +
> +AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 172.64.1.10 20.0.0.10], [1], [],
> +[ovn-nbctl: logical router: lr0 has multiple distributed gateway ports. NAT rule needs to specify gateway_port.
> +])
> +AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 dnat 30.0.0.10 20.0.0.10])
> +AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 30.0.0.10 20.0.0.10])
> +AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 30.0.0.10 20.0.0.20], [1], [],
> +[ovn-nbctl: a NAT with this type (dnat), external_ip (30.0.0.10) and gateway_port (lrp01) already exists
> +])
> +
> +AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +dnat             lrp00                 30.0.0.10                           20.0.0.10
> +dnat             lrp01                 30.0.0.10                           20.0.0.10
> +snat             lrp00                 172.64.0.10                         20.0.0.10
> +])
> +
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.10])
> +AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +snat             lrp00                 172.64.0.10                         20.0.0.10
> +])
> +
> +AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 snat 30.0.0.10 20.0.0.20])
> +AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 snat 30.0.0.10 20.0.0.20])
> +AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +snat             lrp00                 172.64.0.10                         20.0.0.10
> +snat             lrp00                 30.0.0.10                           20.0.0.20
> +snat             lrp01                 30.0.0.10                           20.0.0.20
> +])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat lrp11], [1], [],
> +[ovn-nbctl: lrp11: port name not found
> +])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat lrp00])
> +AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +snat             lrp00                 172.64.0.10                         20.0.0.10
> +snat             lrp00                 30.0.0.10                           20.0.0.20
> +snat             lrp01                 30.0.0.10                           20.0.0.20
> +])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 snat lrp00])
> +AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
> +TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
> +snat             lrp01                 30.0.0.10                           20.0.0.20
> +])
> +
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0 lrp01], [1], [],
> +[ovn-nbctl: 20.0.0: Invalid IP address or CIDR
> +])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0.10 lrp01], [1], [],
> +[ovn-nbctl: no matching NAT with the type (snat), logical_ip (20.0.0.10) and gateway_port (lrp01)
> +])
> +AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 20.0.0.10 lrp01])
> +AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0.20 lrp01])
> +])
>   
>   dnl ---------------------------------------------------------------------
>   
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index daa664982..f9621b804 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -4306,8 +4306,6 @@ check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1
>   check ovn-nbctl lr-nat-add lr0 dnat 42.42.42.42 192.168.0.2
>   check ovn-nbctl --wait=sb sync
>   
> -ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64'
> -
>   AT_CHECK([ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64' | grep "output(\"sw0-port1\")"], [0], [ignore])
>   
>   dnl If we remove the DNAT entry we will be unable to trace to the DNAT address
> @@ -5808,12 +5806,6 @@ AT_CHECK([grep lr_in_gw_redirect lrflows | grep cr-DR | sed 's/table=../table=??
>     table=??(lr_in_gw_redirect  ), priority=50   , match=(outport == "DR-S3"), action=(outport = "cr-DR-S3"; next;)
>   ])
>   
> -# Check that ovn-northd logs a warning when trying to configure NAT
> -# on the router with multiple distributed gw ports.  Such configurations are
> -# not supported yet.
> -check ovn-nbctl lr-nat-add DR dnat_and_snat 42.42.42.1 20.0.0.2
> -AT_CHECK([grep -q 'NAT is configured on logical router DR, which has 2 distributed gateway ports. NAT is not supported yet when there is more than one distributed gateway port on the router.' northd/ovn-northd.log], [0])
> -
>   AT_CLEANUP
>   ])
>   
> @@ -6479,3 +6471,171 @@ AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort],
>   
>   AT_CLEANUP
>   ])
> +
> +AT_SETUP([ovn-northd -- lr multiple gw ports NAT])
> +AT_KEYWORDS([multiple-l3dgw-ports])
> +ovn_start
> +
> +# Logical network:
> +# 1 Logical Router, 3 bridged Logical Switches,
> +# 1 gateway chassis attached to each corresponding LRP.
> +#
> +#                | S1 (gw1)
> +#                |
> +#      ls  ----  DR -- S3 (gw3)
> +# (20.0.0.0/24)  |
> +#                | S2 (gw2)
> +#
> +# Validate SNAT, DNAT and DNAT_AND_SNAT behavior with multiple
> +# distributed gateway LRPs.
> +
> +check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
> +check ovn-sbctl chassis-add gw2 geneve 128.0.0.1
> +check ovn-sbctl chassis-add gw3 geneve 129.0.0.1
> +
> +check ovn-nbctl lr-add DR
> +check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
> +check ovn-nbctl lrp-add DR DR-S2 03:ac:10:01:00:01 10.0.0.1/24
> +check ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.1/24
> +check ovn-nbctl lrp-add DR DR-ls 05:ac:10:01:00:01 20.0.0.1/24
> +
> +check ovn-nbctl ls-add S1
> +check ovn-nbctl lsp-add S1 S1-DR
> +check ovn-nbctl lsp-set-type S1-DR router
> +check ovn-nbctl lsp-set-addresses S1-DR router
> +check ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
> +
> +check ovn-nbctl ls-add S2
> +check ovn-nbctl lsp-add S2 S2-DR
> +check ovn-nbctl lsp-set-type S2-DR router
> +check ovn-nbctl lsp-set-addresses S2-DR router
> +check ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
> +
> +check ovn-nbctl ls-add S3
> +check ovn-nbctl lsp-add S3 S3-DR
> +check ovn-nbctl lsp-set-type S3-DR router
> +check ovn-nbctl lsp-set-addresses S3-DR router
> +check ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
> +
> +check ovn-nbctl ls-add  ls
> +check ovn-nbctl lsp-add ls ls-DR
> +check ovn-nbctl lsp-set-type ls-DR router
> +check ovn-nbctl lsp-set-addresses ls-DR router
> +check ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
> +
> +check ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
> +check ovn-nbctl lrp-set-gateway-chassis DR-S2 gw2
> +check ovn-nbctl lrp-set-gateway-chassis DR-S3 gw3
> +
> +# Configure SNAT
> +check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR snat  172.16.1.10    20.0.0.10
> +check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR snat  10.0.0.10      20.0.0.10
> +check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR snat  192.168.0.10   20.0.0.10
> +
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows DR > lrflows
> +AT_CAPTURE_FILE([lrflows])
> +
> +check_lr_in_arp_nat_flows() {
> +    AT_CHECK([grep lr_in_ip_input lrflows | grep arp | grep -e 172.16.1.10 -e 10.0.0.10 -e 192.168.0.10 | sed 's/table=../table=??/' | sort], [0], [dnl
> +  table=??(lr_in_ip_input     ), priority=90   , match=(arp.op == 1 && arp.tpa == 10.0.0.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
> +  table=??(lr_in_ip_input     ), priority=90   , match=(arp.op == 1 && arp.tpa == 172.16.1.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
> +  table=??(lr_in_ip_input     ), priority=90   , match=(arp.op == 1 && arp.tpa == 192.168.0.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
> +  table=??(lr_in_ip_input     ), priority=91   , match=(inport == "DR-S1" && arp.op == 1 && arp.tpa == 172.16.1.10), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=91   , match=(inport == "DR-S2" && arp.op == 1 && arp.tpa == 10.0.0.10), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=91   , match=(inport == "DR-S3" && arp.op == 1 && arp.tpa == 192.168.0.10), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=92   , match=(inport == "DR-S1" && arp.op == 1 && arp.tpa == 172.16.1.10 && is_chassis_resident("cr-DR-S1")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
> +  table=??(lr_in_ip_input     ), priority=92   , match=(inport == "DR-S2" && arp.op == 1 && arp.tpa == 10.0.0.10 && is_chassis_resident("cr-DR-S2")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
> +  table=??(lr_in_ip_input     ), priority=92   , match=(inport == "DR-S3" && arp.op == 1 && arp.tpa == 192.168.0.10 && is_chassis_resident("cr-DR-S3")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
> +])
> +}
> +
> +check_lr_in_unsnat_flows() {
> +    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;)
> +])
> +}
> +
> +check_lr_out_snat_flows() {
> +    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);)
> +])
> +}
> +
> +check_lr_in_unsnat_flows
> +check_lr_out_snat_flows
> +check_lr_in_arp_nat_flows
> +
> +check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10
> +AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat | grep ct_snat | wc -l], [0], [0
> +])
> +
> +# Configure DNAT
> +check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR dnat  172.16.1.10    20.0.0.10
> +check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR dnat  10.0.0.10      20.0.0.10
> +check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR dnat  192.168.0.10   20.0.0.10
> +
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows DR > lrflows
> +AT_CAPTURE_FILE([lrflows])
> +
> +check_lr_in_dnat_flows() {
> +    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);)
> +])
> +}
> +
> +check_lr_out_undnat_flows() {
> +    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;)
> +])
> +}
> +
> +check_lr_in_dnat_flows
> +check_lr_out_undnat_flows
> +check_lr_in_arp_nat_flows
> +
> +check ovn-nbctl --wait=sb lr-nat-del DR dnat
> +
> +AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_dnat -e lr_out_undnat | grep ct_dnat | wc -l], [0], [0
> +])
> +
> +# Configure DNAT_AND_SNAT
> +check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR dnat_and_snat  172.16.1.10    20.0.0.10
> +check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR dnat_and_snat  10.0.0.10      20.0.0.10
> +check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR dnat_and_snat  192.168.0.10   20.0.0.10
> +
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows DR > lrflows
> +AT_CAPTURE_FILE([lrflows])
> +
> +check_lr_in_unsnat_flows
> +check_lr_out_snat_flows
> +check_lr_in_dnat_flows
> +check_lr_out_undnat_flows
> +check_lr_in_arp_nat_flows
> +
> +check ovn-nbctl --wait=sb lr-nat-del DR dnat_and_snat
> +
> +AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat -e lr_in_dnat -e lr_out_undnat | grep ct_snat| wc -l], [0], [0
> +])
> +
> +AT_CLEANUP
> +])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 0c2fe9f97..2a1011b1a 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -29556,7 +29556,7 @@ as hv2 check ovn-appctl -t ovn-controller recompute
>   AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [1])
>   
>   # Enable dnat_and_snat on lr, and now hv2 should see flows for lsp1.
> -AT_CHECK([ovn-nbctl --wait=hv lr-nat-add lr dnat_and_snat 192.168.0.1 10.0.1.3 lsp1 f0:00:00:00:00:03])
> +AT_CHECK([ovn-nbctl --wait=hv --gateway-port=lrp_lr_ls1 lr-nat-add lr dnat_and_snat 192.168.0.1 10.0.1.3 lsp1 f0:00:00:00:00:03])
>   AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [0], [ignore])
>   
>   OVN_CLEANUP([hv1],[hv2])
> diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
> index 74651c762..3e9176fa0 100644
> --- a/utilities/ovn-nbctl.8.xml
> +++ b/utilities/ovn-nbctl.8.xml
> @@ -1142,7 +1142,7 @@
>       <h2>NAT Commands</h2>
>   
>       <dl>
> -      <dt>[<code>--may-exist</code>] [<code>--stateless</code>]<code>lr-nat-add</code> <var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
> +      <dt>[<code>--may-exist</code>] [<code>--stateless</code>] [<code>--gateway_port</code>=<var>GATEWAY_PORT</var>] <code>lr-nat-add</code> <var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
>         <dd>
>           <p>
>             Adds the specified NAT to <var>router</var>.
> @@ -1158,7 +1158,6 @@
>             The <var>logical_port</var> is the name of an existing logical
>             switch port where the <var>logical_ip</var> resides.
>             The <var>external_mac</var> is an Ethernet address.
> -          The <var>--stateless</var>
>           </p>
>           <p>
>             When <code>--stateless</code> is specified then it implies that
> @@ -1169,6 +1168,16 @@
>             with any other NAT rule.
>           </p>
>   
> +        <p>
> +          <code>--gateway-port</code> option allows specifying the distributed
> +          gateway port of <var>router</var> where the NAT rule needs to be
> +          applied. <var>GATEWAY_PORT</var> should reference a
> +          <ref table="Logical_Router_Port"/> row that is a distributed gateway
> +          port of <var>router</var>. When <var>router</var> has multiple
> +          distributed gateway ports, it is an error to not specify the
> +          <var>GATEWAY_PORT</var>.
> +        </p>
> +
>           <p>
>             When <var>type</var> is <code>dnat</code>, the externally
>             visible IP address <var>external_ip</var> is DNATted to the
> @@ -1201,32 +1210,39 @@
>           <p>
>             It is an error if a NAT already exists with the same values
>             of <var>router</var>, <var>type</var>, <var>external_ip</var>,
> -          and <var>logical_ip</var>, unless <code>--may-exist</code> is
> -          specified.  When <code>--may-exist</code>,
> +          <var>logical_ip</var> and <var>GATEWAY_PORT</var> (in case of
> +          multiple distributed gateway ports), unless <code>--may-exist</code>
> +          is specified.  When <code>--may-exist</code>,
>             <var>logical_port</var>, and <var>external_mac</var> are all
>             specified, the existing values of <var>logical_port</var> and
>             <var>external_mac</var> are overwritten.
>           </p>
>         </dd>
>   
> -      <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>]]</dt>
> +      <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>] [<var>gateway_port</var>]]</dt>
>         <dd>
>           <p>
>             Deletes NATs from <var>router</var>.  If only <var>router</var>
>             is supplied, all the NATs from the logical router are
>             deleted.  If <var>type</var> is also specified, then all the
>             NATs that match the <var>type</var> will be deleted from the logical
> -          router.  If all the fields are given, then a single NAT rule
> -          that matches all the fields will be deleted.  When <var>type</var>
> -          is <code>snat</code>, the <var>ip</var> should be logical_ip.
> -          When <var>type</var> is <code>dnat</code> or
> -          <code>dnat_and_snat</code>, the <var>ip</var> shoud be external_ip.
> -        </p>
> -
> -        <p>
> -          It is an error if <var>ip</var> is specified and there
> -          is no matching NAT entry, unless <code>--if-exists</code> is
> -          specified.
> +          router. If <var>ip</var> is also specified without specifying
> +          <var>gateway_port</var>, then all the NATs that match the
> +          <var>type</var> and <var>ip</var> will be deleted from the logical
> +          router. If <var>gateway_port</var> is specified without specifying
> +          <var>ip</var>, then all the NATs that match the <var>type</var> and
> +          <var>gateway_port</var> will be deleted from the logical router.
> +          If all the fields are given, then a single NAT rule that matches all
> +          the fields will be deleted. When <var>type</var> is
> +          <code>snat</code>, the <var>ip</var> should be logical_ip. When
> +          <var>type</var> is <code>dnat</code> or <code>dnat_and_snat</code>,
> +          the <var>ip</var> shoud be external_ip.
> +        </p>
> +
> +        <p>
> +          It is an error if both <var>ip</var> and <var>gateway_port</var> are
> +          specified and there is no matching NAT entry, unless
> +          <code>--if-exists</code> is specified.
>           </p>
>         </dd>
>   
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index 7bcc2c66a..f8b957792 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -378,10 +378,11 @@ NAT commands:\n\
>     [--stateless]\n\
>     [--portrange]\n\
>     [--add-route]\n\
> +  [--gateway-port=GATEWAY_PORT]\n\
>     lr-nat-add ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]\n\
>                               [EXTERNAL_PORT_RANGE]\n\
>                               add a NAT to ROUTER\n\
> -  lr-nat-del ROUTER [TYPE [IP]]\n\
> +  lr-nat-del ROUTER [TYPE [IP] [GATEWAY_PORT]]\n\
>                               remove NATs from ROUTER\n\
>     lr-nat-list ROUTER        print NATs for ROUTER\n\
>   \n\
> @@ -4566,6 +4567,7 @@ nbctl_pre_lr_nat_add(struct ctl_context *ctx)
>   {
>       ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_nat);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_ports);
>   
>       ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>   
> @@ -4574,7 +4576,14 @@ nbctl_pre_lr_nat_add(struct ctl_context *ctx)
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_type);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_options);
> +
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl,
> +                         &nbrec_logical_router_port_col_gateway_chassis);
> +    ovsdb_idl_add_column(ctx->idl,
> +                         &nbrec_logical_router_port_col_ha_chassis_group);
>   }
>   
>   static void
> @@ -4709,6 +4718,52 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
>           ctl_error(ctx, "routes cannot be added for snat types.");
>           goto cleanup;
>       }
> +
> +    const char *dgw_port_name = shash_find_data(&ctx->options,
> +                                                "--gateway-port");
> +    const struct nbrec_logical_router_port *dgw_port = NULL;
> +    if (dgw_port_name) {
> +        error = lrp_by_name_or_uuid(ctx, dgw_port_name,
> +                                    true, &dgw_port);
> +        if (error) {
> +            ctx->error = error;
> +            goto cleanup;
> +        }
> +
> +        bool nat_lr_port = false;
> +        for (size_t i = 0; i < lr->n_ports; i++) {
> +            const struct nbrec_logical_router_port *lrp = lr->ports[i];
> +            if (lrp == dgw_port) {
> +                nat_lr_port = true;
> +            }
> +        }
> +        if (!nat_lr_port) {
> +            ctl_error(ctx, "%s is not a router port of logical router: %s.",
> +                      dgw_port_name, ctx->argv[1]);
> +            goto cleanup;
> +        }
> +
> +        if (!dgw_port->ha_chassis_group && !dgw_port->n_gateway_chassis) {
> +            ctl_error(ctx, "%s is not a distributed gateway router port.",
> +                      dgw_port_name);
> +            goto cleanup;
> +        }
> +    } else {
> +        size_t num_l3dgw_ports = 0;
> +        for (size_t i = 0; i < lr->n_ports; i++) {
> +            const struct nbrec_logical_router_port *lrp = lr->ports[i];
> +            if (lrp->ha_chassis_group || lrp->n_gateway_chassis) {
> +                num_l3dgw_ports++;
> +            }
> +        }
> +        if (num_l3dgw_ports > 1) {
> +            ctl_error(ctx, "logical router: %s has multiple distributed "
> +                      "gateway ports. NAT rule needs to specify "
> +                      "gateway_port.", ctx->argv[1]);
> +            goto cleanup;
> +        }
> +    }
> +
>       for (size_t i = 0; i < lr->n_nat; i++) {
>           const struct nbrec_nat *nat = lr->nat[i];
>   
> @@ -4725,7 +4780,8 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
>               continue;
>           }
>   
> -        if (!strcmp(nat_type, nat->type)) {
> +        if (!strcmp(nat_type, nat->type) &&
> +            dgw_port == nat->gateway_port) {
>               if (!strcmp(is_snat ? new_logical_ip : new_external_ip,
>                           is_snat ? old_logical_ip : old_external_ip)) {
>                   if (!strcmp(is_snat ? new_external_ip : new_logical_ip,
> @@ -4737,18 +4793,20 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
>                               nbrec_nat_set_external_mac(nat, external_mac);
>                               should_return = true;
>                           } else {
> -                            ctl_error(ctx, "%s, %s: a NAT with this "
> -                                      "external_ip and logical_ip already "
> -                                      "exists", new_external_ip,
> -                                      new_logical_ip);
> +                            ctl_error(ctx, "%s, %s, %s: a NAT with this "
> +                                      "external_ip, logical_ip and "
> +                                      "gateway_port already exists",
> +                                      new_external_ip, new_logical_ip,
> +                                      dgw_port ? dgw_port->name : "");
>                               should_return = true;
>                           }
>                   } else {
> -                    ctl_error(ctx, "a NAT with this type (%s) and %s (%s) "
> -                              "already exists",
> +                    ctl_error(ctx, "a NAT with this type (%s), %s (%s) "
> +                              "and gateway_port (%s) already exists",
>                                 nat_type,
>                                 is_snat ? "logical_ip" : "external_ip",
> -                              is_snat ? new_logical_ip : new_external_ip);
> +                              is_snat ? new_logical_ip : new_external_ip,
> +                              dgw_port ? dgw_port->name : "");
>                       should_return = true;
>                   }
>               }
> @@ -4792,6 +4850,10 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
>       if (add_route) {
>           smap_add(&nat_options, "add_route", "true");
>       }
> +
> +    if (dgw_port) {
> +        nbrec_nat_update_gateway_port_addvalue(nat, dgw_port);
> +    }
>       nbrec_nat_set_options(nat, &nat_options);
>   
>       smap_destroy(&nat_options);
> @@ -4813,6 +4875,9 @@ nbctl_pre_lr_nat_del(struct ctl_context *ctx)
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_type);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_ip);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
> +
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
>   }
>   
>   static void
> @@ -4853,13 +4918,58 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
>       }
>   
>       char *nat_ip = normalize_prefix_str(ctx->argv[3]);
> +    int is_snat = !strcmp("snat", nat_type);
> +    const struct nbrec_logical_router_port *dgw_port = NULL;
> +
> +    if (ctx->argc == 4) {
> +        if (!nat_ip) {
> +            /* GATEWAY_PORT is assumed to be passed. */
> +            error = lrp_by_name_or_uuid(ctx, ctx->argv[3], true, &dgw_port);
> +            if (error) {
> +                ctx->error = error;
> +                return;
> +            }
> +
> +            /* Deletes all NATs matching the type and gateway_port
> +             * specified. */
> +            for (size_t i = 0; i < lr->n_nat; i++) {
> +                if (!strcmp(nat_type, lr->nat[i]->type) &&
> +                    lr->nat[i]->gateway_port == dgw_port) {
> +                    nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]);
> +                }
> +            }
> +            return;
> +        }
> +
> +        /* Remove NAT rules matching the type and IP (based on type). */
> +        for (size_t i = 0; i < lr->n_nat; i++) {
> +            struct nbrec_nat *nat = lr->nat[i];
> +            char *old_ip = normalize_prefix_str(is_snat
> +                                                ? nat->logical_ip
> +                                                : nat->external_ip);
> +            if (!old_ip) {
> +                continue;
> +            }
> +            if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
> +                nbrec_logical_router_update_nat_delvalue(lr, nat);
> +            }
> +            free(old_ip);
> +        }
> +        goto cleanup;
> +    }
> +
>       if (!nat_ip) {
>           ctl_error(ctx, "%s: Invalid IP address or CIDR", ctx->argv[3]);
>           return;
>       }
>   
> -    int is_snat = !strcmp("snat", nat_type);
> -    /* Remove the matching NAT. */
> +    error = lrp_by_name_or_uuid(ctx, ctx->argv[4], true, &dgw_port);
> +    if (error) {
> +        ctx->error = error;
> +        goto cleanup;
> +    }
> +
> +    /* Remove matching NAT. */
>       for (size_t i = 0; i < lr->n_nat; i++) {
>           struct nbrec_nat *nat = lr->nat[i];
>           bool should_return = false;
> @@ -4869,7 +4979,8 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
>           if (!old_ip) {
>               continue;
>           }
> -        if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
> +        if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip) &&
> +            nat->gateway_port == dgw_port) {
>               nbrec_logical_router_update_nat_delvalue(lr, nat);
>               should_return = true;
>           }
> @@ -4880,8 +4991,10 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
>       }
>   
>       if (must_exist) {
> -        ctl_error(ctx, "no matching NAT with the type (%s) and %s (%s)",
> -                  nat_type, is_snat ? "logical_ip" : "external_ip", nat_ip);
> +        ctl_error(ctx, "no matching NAT with the type (%s), %s (%s) and "
> +                  "gateway_port (%s)", nat_type,
> +                  is_snat ? "logical_ip" : "external_ip", nat_ip,
> +                  ctx->argv[4]);
>       }
>   
>   cleanup:
> @@ -4900,6 +5013,9 @@ nbctl_pre_lr_nat_list(struct ctl_context *ctx)
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
>       ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
> +
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
>   }
>   
>   static void
> @@ -4915,11 +5031,18 @@ nbctl_lr_nat_list(struct ctl_context *ctx)
>       struct smap lr_nats = SMAP_INITIALIZER(&lr_nats);
>       for (size_t i = 0; i < lr->n_nat; i++) {
>           const struct nbrec_nat *nat = lr->nat[i];
> -        char *key = xasprintf("%-17.13s%s", nat->type, nat->external_ip);
> +        char *key = xasprintf("%-17.13s%-22.18s%s",
> +                              nat->type,
> +                              nat->gateway_port
> +                              ? nat->gateway_port->name
> +                              : "",
> +                              nat->external_ip);
>           if (nat->external_mac && nat->logical_port) {
> -            smap_add_format(&lr_nats, key, "%-17.13s%-22.18s%-21.17s%s",
> +            smap_add_format(&lr_nats, key,
> +                            "%-17.13s%-20.16s%-21.17s%s",
>                               nat->external_port_range,
> -                            nat->logical_ip, nat->external_mac,
> +                            nat->logical_ip,
> +                            nat->external_mac,
>                               nat->logical_port);
>           } else {
>               smap_add_format(&lr_nats, key, "%-17.13s%s",
> @@ -4932,12 +5055,12 @@ nbctl_lr_nat_list(struct ctl_context *ctx)
>       const struct smap_node **nodes = smap_sort(&lr_nats);
>       if (nodes) {
>           ds_put_format(&ctx->output,
> -                "%-17.13s%-19.15s%-17.13s%-22.18s%-21.17s%s\n",
> -                "TYPE", "EXTERNAL_IP", "EXTERNAL_PORT", "LOGICAL_IP",
> -                "EXTERNAL_MAC", "LOGICAL_PORT");
> +                "%-17.13s%-22.18s%-19.15s%-17.13s%-20.16s%-21.17s%s\n",
> +                "TYPE", "GATEWAY_PORT", "EXTERNAL_IP", "EXTERNAL_PORT",
> +                "LOGICAL_IP", "EXTERNAL_MAC", "LOGICAL_PORT");
>           for (size_t i = 0; i < smap_count(&lr_nats); i++) {
>               const struct smap_node *node = nodes[i];
> -            ds_put_format(&ctx->output, "%-36.32s%s\n",
> +            ds_put_format(&ctx->output, "%-58.54s%s\n",
>                       node->key, node->value);
>           }
>           free(nodes);
> @@ -7109,8 +7232,9 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>         "ROUTER TYPE EXTERNAL_IP LOGICAL_IP"
>         "[LOGICAL_PORT EXTERNAL_MAC] [EXTERNAL_PORT_RANGE]",
>         nbctl_pre_lr_nat_add, nbctl_lr_nat_add,
> -      NULL, "--may-exist,--stateless,--portrange,--add-route", RW },
> -    { "lr-nat-del", 1, 3, "ROUTER [TYPE [IP]]",
> +      NULL, "--may-exist,--stateless,--portrange,--add-route,"
> +      "--gateway-port=", RW },
> +    { "lr-nat-del", 1, 4, "ROUTER [TYPE [IP] [GATEWAY_PORT]]",
>         nbctl_pre_lr_nat_del, nbctl_lr_nat_del, NULL, "--if-exists", RW },
>       { "lr-nat-list", 1, 1, "ROUTER", nbctl_pre_lr_nat_list,
>         nbctl_lr_nat_list, NULL, "", RO },
>
Abhiram Sangana April 6, 2022, 12:08 p.m. UTC | #3
Thanks for reviewing, Mark. Can we merge the patch if it looks good?

Thanks,
Abhiram Sangana

> On 4 Apr 2022, at 21:05, Mark Michelson <mmichels@redhat.com> wrote:
> 
> Hi Abhriam,
> 
> Thanks for you patience on this. It looks good by me.
> 
> Acked-by: Mark Michelson <mmichels@redhat.com>
Numan Siddique April 6, 2022, 1:47 p.m. UTC | #4
On Wed, Apr 6, 2022 at 8:08 AM Abhiram Sangana
<sangana.abhiram@nutanix.com> wrote:
>
> Thanks for reviewing, Mark. Can we merge the patch if it looks good?


Thanks.  I applied this patch to the main branch,

Numan

>
> Thanks,
> Abhiram Sangana
>
> > On 4 Apr 2022, at 21:05, Mark Michelson <mmichels@redhat.com> wrote:
> >
> > Hi Abhriam,
> >
> > Thanks for you patience on this. It looks good by me.
> >
> > Acked-by: Mark Michelson <mmichels@redhat.com>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 3e8358723..c881764a6 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@  Post v22.03.0
     different OVN Interconnection availability zones.
   - Replaced the usage of masked ct_label by ct_mark in most cases to work
     better with hardware-offloading.
+  - Support NAT for logical routers with multiple distributed gateway ports.
 
 OVN v22.03.0 - 11 Mar 2022
 --------------------------
diff --git a/northd/northd.c b/northd/northd.c
index 2fb0a93c2..ad0e8b95d 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -617,11 +617,11 @@  struct ovn_datapath {
 
     /* Applies to only logical router datapath.
      * True if logical router is a gateway router. i.e options:chassis is set.
-     * If this is true, then 'l3dgw_port' will be ignored. */
+     * If this is true, then 'l3dgw_ports' will be ignored. */
     bool is_gw_router;
 
-    /* OVN northd only needs to know about the logical router gateway port for
-     * NAT on a distributed router.  The "distributed gateway ports" are
+    /* OVN northd only needs to know about logical router gateway ports for
+     * NAT/LB on a distributed router.  The "distributed gateway ports" are
      * populated only when there is a gateway chassis or ha chassis group
      * specified for some of the ports on the logical router. Otherwise this
      * will be NULL. */
@@ -2713,8 +2713,9 @@  join_logical_ports(struct northd_input *input_data,
  * by one or more IP addresses, and if the port is a distributed gateway
  * port, followed by 'is_chassis_resident("LPORT_NAME")', where the
  * LPORT_NAME is the name of the L3 redirect port or the name of the
- * logical_port specified in a NAT rule.  These strings include the
- * external IP addresses of all NAT rules defined on that router, and all
+ * logical_port specified in a NAT rule. These strings include the
+ * external IP addresses of NAT rules defined on that router which have
+ * gateway_port not set or have gateway_port as the router port 'op', and all
  * of the IP addresses used in load balancer VIPs defined on that router.
  *
  * The caller must free each of the n returned strings with free(),
@@ -2727,8 +2728,7 @@  get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
     struct eth_addr mac;
     if (!op || !op->nbrp || !op->od || !op->od->nbr
         || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
-        || !eth_addr_from_string(op->nbrp->mac, &mac)
-        || op->od->n_l3dgw_ports > 1) {
+        || !eth_addr_from_string(op->nbrp->mac, &mac)) {
         *n = n_nats;
         return NULL;
     }
@@ -2757,6 +2757,12 @@  get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
             continue;
         }
 
+        /* Not including external IP of NAT rules whose gateway_port is
+         * not 'op'. */
+        if (nat->gateway_port && nat->gateway_port != op->nbrp) {
+            continue;
+        }
+
         /* Determine whether this NAT rule satisfies the conditions for
          * distributed NAT processing. */
         if (op->od->n_l3dgw_ports && !strcmp(nat->type, "dnat_and_snat")
@@ -2827,9 +2833,9 @@  get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
     if (central_ip_address) {
         /* Gratuitous ARP for centralized NAT rules on distributed gateway
          * ports should be restricted to the gateway chassis. */
-        if (op->od->n_l3dgw_ports) {
+        if (is_l3dgw_port(op)) {
             ds_put_format(&c_addresses, " is_chassis_resident(%s)",
-                          op->od->l3dgw_ports[0]->cr_port->json_key);
+                          op->cr_port->json_key);
         }
 
         addresses[n_nats++] = ds_steal_cstr(&c_addresses);
@@ -3469,9 +3475,12 @@  ovn_port_update_sbrec(struct northd_input *input_data,
                 }
 
                 if (op->peer->od->n_l3dgw_ports) {
+                    const struct ovn_port *l3dgw_port = (
+                        is_l3dgw_port(op->peer)
+                        ? op->peer
+                        : op->peer->od->l3dgw_ports[0]);
                     ds_put_format(&garp_info, " is_chassis_resident(%s)",
-                                  op->peer->od->l3dgw_ports[0]
-                                  ->cr_port->json_key);
+                                  l3dgw_port->cr_port->json_key);
                 }
 
                 n_nats++;
@@ -10367,6 +10376,12 @@  build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
     const struct nbrec_nat *nat = nat_entry->nb;
     struct ds match = DS_EMPTY_INITIALIZER;
 
+    /* ARP/ND should be sent from distributed gateway port specified in
+     * the NAT rule. */
+    if (nat->gateway_port && nat->gateway_port != op->nbrp) {
+        return;
+    }
+
     /* Mac address to use when replying to ARP/NS. */
     const char *mac_s = REG_INPORT_ETH_ADDR;
     struct eth_addr mac;
@@ -10390,10 +10405,9 @@  build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
          * upstream MAC learning points to the gateway chassis.
          * Also need to avoid generation of multiple ARP responses
          * from different chassis. */
-        if (op->od->n_l3dgw_ports) {
-            ds_put_format(&match, "is_chassis_resident(%s)",
-                          op->od->l3dgw_ports[0]->cr_port->json_key);
-        }
+        ovs_assert(is_l3dgw_port(op));
+        ds_put_format(&match, "is_chassis_resident(%s)",
+                      op->cr_port->json_key);
     }
 
     /* Respond to ARP/NS requests on the chassis that binds the gw
@@ -12139,7 +12153,7 @@  build_ipv6_input_flows_for_lrouter_port(
         struct ds *match, struct ds *actions,
         const struct shash *meter_groups)
 {
-    if (op->nbrp && (!op->l3dgw_port)) {
+    if (op->nbrp && !is_cr_port(op)) {
         /* No ingress packets are accepted on a chassisredirect
          * port, so no need to program flows for that port. */
         if (op->lrp_networks.n_ipv6_addrs) {
@@ -12268,7 +12282,7 @@  build_ipv6_input_flows_for_lrouter_port(
             ds_clear(match);
             ds_clear(actions);
             ds_clear(&ip_ds);
-            if (op->od->n_l3dgw_ports && op->od->l3dgw_ports[0] == op) {
+            if (is_l3dgw_port(op)) {
                 ds_put_cstr(&ip_ds, "ip6.dst <-> ip6.src");
             } else {
                 ds_put_format(&ip_ds, "ip6.dst = ip6.src; ip6.src = %s",
@@ -12358,7 +12372,7 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
 {
     /* No ingress packets are accepted on a chassisredirect
      * port, so no need to program flows for that port. */
-    if (op->nbrp && (!op->l3dgw_port)) {
+    if (op->nbrp && !is_cr_port(op)) {
         if (op->lrp_networks.n_ipv4_addrs) {
             /* L3 admission control: drop packets that originate from an
              * IPv4 address owned by the router or a broadcast address
@@ -12400,7 +12414,7 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
             ds_clear(match);
             ds_clear(actions);
             ds_clear(&ip_ds);
-            if (op->od->n_l3dgw_ports && op->od->l3dgw_ports[0] == op) {
+            if (is_l3dgw_port(op)) {
                 ds_put_cstr(&ip_ds, "ip4.dst <-> ip4.src");
             } else {
                 ds_put_format(&ip_ds, "ip4.dst = ip4.src; ip4.src = %s",
@@ -12645,7 +12659,8 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
 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)
+                             struct ds *actions, bool distributed, bool is_v6,
+                             struct ovn_port *l3dgw_port)
 {
     /* Ingress UNSNAT table: It is for already established connections'
     * reverse traffic. i.e., SNAT has already been done in egress
@@ -12684,12 +12699,12 @@  build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
         ds_clear(actions);
         ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
                       "flags.loopback == 0", is_v6 ? "6" : "4",
-                      nat->external_ip, od->l3dgw_ports[0]->json_key);
+                      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)",
-                          od->l3dgw_ports[0]->cr_port->json_key);
+                          l3dgw_port->cr_port->json_key);
         }
 
         if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
@@ -12709,12 +12724,12 @@  build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
             ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
                           "flags.loopback == 1 && flags.use_snat_zone == 1",
                           is_v6 ? "6" : "4", nat->external_ip,
-                          od->l3dgw_ports[0]->json_key);
+                          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)",
-                            od->l3dgw_ports[0]->cr_port->json_key);
+                            l3dgw_port->cr_port->json_key);
             }
             ds_put_cstr(actions, "ct_snat;");
             ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
@@ -12728,7 +12743,8 @@  static void
 build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
                            const struct nbrec_nat *nat, struct ds *match,
                            struct ds *actions, bool distributed,
-                           ovs_be32 mask, bool is_v6)
+                           ovs_be32 mask, bool is_v6,
+                           struct ovn_port *l3dgw_port)
 {
     /* Ingress DNAT table: Packets enter the pipeline with destination
     * IP address that needs to be DNATted from a external IP address
@@ -12780,12 +12796,12 @@  build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
             ds_clear(match);
             ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
                           is_v6 ? "6" : "4", nat->external_ip,
-                          od->l3dgw_ports[0]->json_key);
+                          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)",
-                              od->l3dgw_ports[0]->cr_port->json_key);
+                              l3dgw_port->cr_port->json_key);
             }
             ds_clear(actions);
             if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
@@ -12815,7 +12831,8 @@  static void
 build_lrouter_out_undnat_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, bool is_v6)
+                              struct eth_addr mac, bool is_v6,
+                              struct ovn_port *l3dgw_port)
 {
     /* Egress UNDNAT table: It is for already established connections'
     * reverse traffic. i.e., DNAT has already been done in ingress
@@ -12832,12 +12849,12 @@  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,
-                  od->l3dgw_ports[0]->json_key);
+                  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)",
-                      od->l3dgw_ports[0]->cr_port->json_key);
+                      l3dgw_port->cr_port->json_key);
     }
     ds_clear(actions);
     if (distributed) {
@@ -12863,7 +12880,7 @@  static void
 build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
                                 const struct nbrec_nat *nat, struct ds *match,
                                 struct ds *actions, bool distributed,
-                                bool is_v6)
+                                bool is_v6, struct ovn_port *l3dgw_port)
 {
     /* Note that this only applies for NAT on a distributed router.
      */
@@ -12878,7 +12895,7 @@  build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
         ds_put_format(match, "is_chassis_resident(\"%s\")", nat->logical_port);
     } else {
         ds_put_format(match, "is_chassis_resident(%s)",
-                      od->l3dgw_ports[0]->cr_port->json_key);
+                      l3dgw_port->cr_port->json_key);
     }
 
     ds_clear(actions);
@@ -12894,7 +12911,8 @@  build_lrouter_out_snat_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, ovs_be32 mask,
-                            int cidr_bits, bool is_v6)
+                            int cidr_bits, bool is_v6,
+                            struct ovn_port *l3dgw_port)
 {
     /* Egress SNAT table: Packets enter the egress pipeline with
     * source ip address that needs to be SNATted to a external ip
@@ -12941,7 +12959,7 @@  build_lrouter_out_snat_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,
-                      od->l3dgw_ports[0]->json_key);
+                      l3dgw_port->json_key);
         if (od->n_l3dgw_ports) {
             if (distributed) {
                 ovs_assert(nat->logical_port);
@@ -12953,7 +12971,7 @@  build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
                 * programmed on the gateway chassis. */
                 priority += 128;
                 ds_put_format(match, " && is_chassis_resident(%s)",
-                              od->l3dgw_ports[0]->cr_port->json_key);
+                              l3dgw_port->cr_port->json_key);
             }
         }
         ds_clear(actions);
@@ -13012,13 +13030,13 @@  build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
                                         const struct nbrec_nat *nat,
                                         struct ovn_datapath *od, bool is_v6,
                                         struct ds *match, struct ds *actions,
-                                        int mtu,
+                                        int mtu, struct ovn_port *l3dgw_port,
                                         const struct shash *meter_groups)
 {
         ds_clear(match);
         ds_put_format(match, "inport == %s && "REGBIT_PKT_LARGER
                       " && "REGBIT_EGRESS_LOOPBACK" == 0",
-                      od->l3dgw_ports[0]->json_key);
+                      l3dgw_port->json_key);
 
         ds_clear(actions);
         if (!is_v6) {
@@ -13039,7 +13057,7 @@  build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
                 "outport = %s; flags.loopback = 1; output; };",
                 nat->external_mac,
                 nat->external_ip,
-                mtu, od->l3dgw_ports[0]->json_key);
+                mtu, l3dgw_port->json_key);
             ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, 160,
                                       ds_cstr(match), ds_cstr(actions),
                                       NULL,
@@ -13066,7 +13084,7 @@  build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
                 "outport = %s; flags.loopback = 1; output; };",
                 nat->external_mac,
                 nat->external_ip,
-                mtu, od->l3dgw_ports[0]->json_key);
+                mtu, l3dgw_port->json_key);
             ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, 160,
                                       ds_cstr(match), ds_cstr(actions),
                                       NULL,
@@ -13083,13 +13101,14 @@  build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
                            const struct nbrec_nat *nat, struct ds *match,
                            struct ds *actions, struct eth_addr mac,
                            bool distributed, bool is_v6,
+                           struct ovn_port *l3dgw_port,
                            const struct shash *meter_groups)
 {
     if (od->n_l3dgw_ports && !strcmp(nat->type, "snat")) {
         ds_clear(match);
         ds_put_format(
             match, "inport == %s && %s == %s",
-            od->l3dgw_ports[0]->json_key,
+            l3dgw_port->json_key,
             is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
                                 120, ds_cstr(match), "next;",
@@ -13105,32 +13124,33 @@  build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
         * This will save us from having to match on inport further
         * down in the pipeline.
         */
-        int gw_mtu = smap_get_int(&od->l3dgw_ports[0]->nbrp->options,
+        int gw_mtu = smap_get_int(&l3dgw_port->nbrp->options,
                                   "gateway_mtu", 0);
         ds_clear(match);
         ds_put_format(match,
                       "eth.dst == "ETH_ADDR_FMT" && inport == %s"
                       " && is_chassis_resident(\"%s\")",
                       ETH_ADDR_ARGS(mac),
-                      od->l3dgw_ports[0]->json_key,
+                      l3dgw_port->json_key,
                       nat->logical_port);
-        build_gateway_mtu_flow(lflows, od->l3dgw_ports[0],
+        build_gateway_mtu_flow(lflows, l3dgw_port,
                                S_ROUTER_IN_ADMISSION, 50, 55,
                                match, actions, &nat->header_,
                                REG_INPORT_ETH_ADDR " = %s; next;",
-                               od->l3dgw_ports[0]->lrp_networks.ea_s);
+                               l3dgw_port->lrp_networks.ea_s);
         if (gw_mtu) {
             build_lrouter_ingress_nat_check_pkt_len(lflows, nat, od, is_v6,
                                                     match, actions, gw_mtu,
-                                                    meter_groups);
+                                                    l3dgw_port, meter_groups);
         }
     }
 }
 
 static int
 lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
-                        ovs_be32 *mask, bool *is_v6, int *cidr_bits,
-                        struct eth_addr *mac, bool *distributed)
+                        const struct hmap *ports, ovs_be32 *mask,
+                        bool *is_v6, int *cidr_bits, struct eth_addr *mac,
+                        bool *distributed, struct ovn_port **nat_l3dgw_port)
 {
     struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
     ovs_be32 ip;
@@ -13164,6 +13184,32 @@  lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
         *is_v6 = true;
     }
 
+    /* Validate gateway_port of NAT rule. */
+    *nat_l3dgw_port = NULL;
+    if (nat->gateway_port == NULL) {
+        if (od->n_l3dgw_ports > 1) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "NAT configured on logical router: %s with"
+                         "multiple distributed gateway ports needs to specify"
+                         "valid gateway_port.", od->nbr->name);
+            return -EINVAL;
+        } else if (od->n_l3dgw_ports) {
+            *nat_l3dgw_port = od->l3dgw_ports[0];
+        }
+    } else {
+        *nat_l3dgw_port = ovn_port_find(ports, nat->gateway_port->name);
+
+        if (!(*nat_l3dgw_port) || (*nat_l3dgw_port)->od != od ||
+            !is_l3dgw_port(*nat_l3dgw_port)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "gateway_port: %s of NAT configured on "
+                         "logical router: %s is not a valid distributed "
+                         "gateway port on that router",
+                         nat->gateway_port->name, od->nbr->name);
+            return -EINVAL;
+        }
+    }
+
     /* Check the validity of nat->logical_ip. 'logical_ip' can
     * be a subnet when the type is "snat". */
     if (*is_v6) {
@@ -13263,24 +13309,12 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
     ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
 
     /* NAT rules are only valid on Gateway routers and routers with
-     * l3dgw_port (router has a port with gateway chassis
+     * l3dgw_ports (router has port(s) with gateway chassis
      * specified). */
     if (!od->is_gw_router && !od->n_l3dgw_ports) {
         return;
     }
 
-    /* NAT rules are not currently supported on logical routers with multiple
-     * distributed gateway ports. */
-    if (od->n_l3dgw_ports > 1) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "NAT is configured on logical router %s, which has %"
-                     PRIuSIZE" distributed gateway ports. NAT is not supported"
-                     " yet when there is more than one distributed gateway "
-                     "port on the router.",
-                     od->nbr->name, od->n_l3dgw_ports);
-        return;
-    }
-
     struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
 
     bool dnat_force_snat_ip =
@@ -13294,18 +13328,19 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
         bool is_v6, distributed;
         ovs_be32 mask;
         int cidr_bits;
+        struct ovn_port *l3dgw_port;
 
-        if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits,
-                                    &mac, &distributed) < 0) {
+        if (lrouter_check_nat_entry(od, nat, ports, &mask, &is_v6, &cidr_bits,
+                                    &mac, &distributed, &l3dgw_port) < 0) {
             continue;
         }
 
         /* S_ROUTER_IN_UNSNAT */
         build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, distributed,
-                                     is_v6);
+                                     is_v6, l3dgw_port);
         /* S_ROUTER_IN_DNAT */
         build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, distributed,
-                                   mask, is_v6);
+                                   mask, is_v6, l3dgw_port);
 
         /* ARP resolve for NAT IPs. */
         if (od->is_gw_router) {
@@ -13318,14 +13353,14 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
                 ds_clear(match);
                 ds_put_format(
                     match, "outport == %s && %s == %s",
-                    od->l3dgw_ports[0]->json_key,
+                    l3dgw_port->json_key,
                     is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
                     nat->external_ip);
                 ds_clear(actions);
                 ds_put_format(
                     actions, "eth.dst = %s; next;",
                     distributed ? nat->external_mac :
-                    od->l3dgw_ports[0]->lrp_networks.ea_s);
+                    l3dgw_port->lrp_networks.ea_s);
                 ovn_lflow_add_with_hint(lflows, od,
                                         S_ROUTER_IN_ARP_RESOLVE,
                                         100, ds_cstr(match),
@@ -13337,18 +13372,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);
+                                        distributed, is_v6, l3dgw_port);
 
         /* S_ROUTER_OUT_UNDNAT */
         build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed,
-                                      mac, is_v6);
+                                      mac, is_v6, l3dgw_port);
         /* S_ROUTER_OUT_SNAT */
         build_lrouter_out_snat_flow(lflows, od, nat, match, actions, distributed,
-                                    mac, mask, cidr_bits, is_v6);
+                                    mac, mask, 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, distributed, is_v6, meter_groups);
+        build_lrouter_ingress_flow(lflows, od, nat, match, actions, mac,
+                                   distributed, is_v6, l3dgw_port,
+                                   meter_groups);
 
         /* Ingress Gateway Redirect Table: For NAT on a distributed
          * router, add flows that are specific to a NAT rule.  These
@@ -13365,7 +13401,7 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
             ds_put_format(match,
                           "ip%s.src == %s && outport == %s",
                           is_v6 ? "6" : "4", nat->logical_ip,
-                          od->l3dgw_ports[0]->json_key);
+                          l3dgw_port->json_key);
             /* Add a rule to drop traffic from a distributed NAT if
              * the virtual port has not claimed yet becaused otherwise
              * the traffic will be centralized misconfiguring the TOR switch.
@@ -13398,10 +13434,10 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
             ds_put_format(match, "ip%s.dst == %s && outport == %s",
                           is_v6 ? "6" : "4",
                           nat->external_ip,
-                          od->l3dgw_ports[0]->json_key);
+                          l3dgw_port->json_key);
             if (!distributed) {
                 ds_put_format(match, " && is_chassis_resident(%s)",
-                              od->l3dgw_ports[0]->cr_port->json_key);
+                              l3dgw_port->cr_port->json_key);
             } else {
                 ds_put_format(match, " && is_chassis_resident(\"%s\")",
                               nat->logical_port);
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 7ebf846aa..85a65703d 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -3026,7 +3026,8 @@  icmp6 {
               <code>ip &amp;&amp;
               ip6.dst == <var>B</var> &amp;&amp; inport == <var>GW</var>
               &amp;&amp; flags.loopback == 0</code>
-              where <var>GW</var> is the logical router gateway port, with an
+              where <var>GW</var> is the distributed gateway port
+              specified in the NAT rule, with an
               action <code>ct_snat_in_czone;</code> to unSNAT in the common
               zone.  If the NAT rule is of type dnat_and_snat and has
               <code>stateless=true</code> in the options, then the action
@@ -3051,7 +3052,8 @@  icmp6 {
               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 logical router gateway port, with an
+              where <var>GW</var> is the distributed gateway port
+              specified in the NAT rule, 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
@@ -3332,9 +3334,10 @@  icmp6 {
           to change the destination IP address of a packet from <var>A</var> to
           <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
           ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
-          where <var>GW</var> is the logical router gateway port, with an
-          action <code>ct_dnat(<var>B</var>);</code>.  The match will
-          include <code>ip6.dst == <var>B</var></code> in the IPv6 case.
+          where <var>GW</var> is the logical router gateway port configured
+          for the NAT rule, with an action
+          <code>ct_dnat(<var>B</var>);</code>.  The match will include
+          <code>ip6.dst == <var>B</var></code> in the IPv6 case.
           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>.
@@ -4178,10 +4181,11 @@  icmp6 {
         flow with match <code>ip4.src == <var>B</var> &amp;&amp;
         outport == <var>GW</var></code> &amp;&amp;
         is_chassis_resident(<var>P</var>), where <var>GW</var> is
-        the logical router distributed gateway port and <var>P</var>
-        is the NAT logical port. IP traffic matching the above rule
-        will be managed locally setting <code>reg1</code> to <var>C</var>
-        and <code>eth.src</code> to <var>D</var>, where <var>C</var> is NAT
+        the distributed gateway port specified in the
+        NAT rule and <var>P</var> is the NAT logical port. IP traffic
+        matching the above rule will be managed locally setting
+        <code>reg1</code> to <var>C</var> and
+        <code>eth.src</code> to <var>D</var>, where <var>C</var> is NAT
         external ip and <var>D</var> is NAT external mac.
       </li>
 
@@ -4648,8 +4652,9 @@  nd_ns {
           outport == <var>GW</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.
+          is the distributed gateway port specified in the NAT rule.
+          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
diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
index ef8d669a2..a48757761 100644
--- a/ovn-architecture.7.xml
+++ b/ovn-architecture.7.xml
@@ -742,9 +742,9 @@ 
 
   <p>
     A logical router can have multiple distributed gateway ports, each
-    connecting different external networks. However, some features, such as NAT
-    and load balancers, are not supported yet for logical routers with more
-    than one distributed gateway port configured.
+    connecting different external networks. Load balancing is not yet
+    supported for logical routers with more than one distributed gateway
+    port configured.
   </p>
 
   <h4>Physical VLAN MTU Issues</h4>
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 80b830629..6c69732e9 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "6.1.0",
-    "cksum": "4010776751 31237",
+    "version": "6.2.0",
+    "cksum": "2862883635 31533",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -479,6 +479,12 @@ 
                             "refType": "strong"},
                     "min": 0,
                     "max": 1}},
+                "gateway_port": {
+                    "type": {"key": {"type": "uuid",
+                                     "refTable": "Logical_Router_Port",
+                                     "refType": "weak"},
+                             "min": 0,
+                             "max": 1}},
                 "options": {"type": {"key": "string", "value": "string",
                                      "min": 0, "max": "unlimited"}},
                 "external_ids": {
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 4d7a23c52..0e3ad2b9b 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2619,8 +2619,8 @@ 
       <p>
         There can be more than one distributed gateway ports configured
         on each logical router, each connecting to different L2 segments.
-        However, features such as NAT and load-balancer are not supported
-        on logical routers with more than one distributed gateway ports.
+        Load-balancing is not yet supported on logical routers with more
+        than one distributed gateway ports.
       </p>
 
       <p>
@@ -3352,6 +3352,39 @@ 
       </p>
     </column>
 
+    <column name="gateway_port">
+      <p>
+        A distributed gateway port in the <ref table="Logical_Router_Port"/>
+        table where the NAT rule needs to be applied.
+      </p>
+
+      <p>
+        This column needs to be set when multiple distributed gateway ports
+        are configured on a <ref table="Logical_Router"/> for the NAT rule to
+        be applied. If logical router has a single distributed gateway port,
+        NAT rule is applied at the distributed gateway port even if this
+        column is not set.
+      </p>
+
+      <p>
+        When multiple distributed gateway ports are configured on a
+        <ref table="Logical_Router"/>, applying a NAT rule at each of the
+        distributed gateway ports might not be desired. Consider the case
+        where a logical router has 2 distributed gateway port, one with
+        <ref column="networks" table="Logical_Router_Port"/>
+        <code>50.0.0.10/24</code> and the other with
+        <ref column="networks" table="Logical_Router_Port"/>
+        <code>60.0.0.10/24</code>. If the logical router has a
+        NAT rule of <ref column="type"/> <code>snat</code>,
+        <ref column="logical_ip"/> <code>10.1.1.0/24</code> and
+        <ref column="external_ip"/> <code>50.1.1.20/24</code>, the rule needs
+        to be selectively applied on matching packets entering/leaving
+        through the distributed gateway port with
+        <ref column="networks" table="Logical_Router_Port"/>
+        <code>50.0.0.10/24</code>.
+      </p>
+    </column>
+
     <column name="options" key="stateless">
       Indicates if a dnat_and_snat rule should lead to connection
       tracking state or not.
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 539a121c0..db0d23099 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -510,64 +510,64 @@  AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat fd01::1 fd11::2])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:01:02:03])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat fd01::2 fd11::3 lp0 00:00:00:01:02:03])
 AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1                            192.168.1.2
-dnat             fd01::1                             fd11::2
-dnat_and_snat    30.0.0.1                            192.168.1.2
-dnat_and_snat    30.0.0.2                            192.168.1.3           00:00:00:01:02:03    lp0
-dnat_and_snat    fd01::1                             fd11::2
-dnat_and_snat    fd01::2                             fd11::3               00:00:00:01:02:03    lp0
-snat             30.0.0.1                            192.168.1.0/24
-snat             fd01::1                             fd11::/64
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat                                   30.0.0.1                            192.168.1.2
+dnat                                   fd01::1                             fd11::2
+dnat_and_snat                          30.0.0.1                            192.168.1.2
+dnat_and_snat                          30.0.0.2                            192.168.1.3         00:00:00:01:02:03    lp0
+dnat_and_snat                          fd01::1                             fd11::2
+dnat_and_snat                          fd01::2                             fd11::3             00:00:00:01:02:03    lp0
+snat                                   30.0.0.1                            192.168.1.0/24
+snat                                   fd01::1                             fd11::/64
 ])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.0/24, : a NAT with this external_ip, logical_ip and gateway_port already exists
 ])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.10/24], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.0/24, : a NAT with this external_ip, logical_ip and gateway_port already exists
 ])
 AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.0/24], [1], [],
-[ovn-nbctl: a NAT with this type (snat) and logical_ip (192.168.1.0/24) already exists
+[ovn-nbctl: a NAT with this type (snat), logical_ip (192.168.1.0/24) and gateway_port () already exists
 ])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.2, : a NAT with this external_ip, logical_ip and gateway_port already exists
 ])
 AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.3], [1], [],
-[ovn-nbctl: a NAT with this type (dnat) and external_ip (30.0.0.1) already exists
+[ovn-nbctl: a NAT with this type (dnat), external_ip (30.0.0.1) and gateway_port () already exists
 ])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.2, : a NAT with this external_ip, logical_ip and gateway_port already exists
 ])
 AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.3], [1], [],
-[ovn-nbctl: a NAT with this type (dnat_and_snat) and external_ip (30.0.0.1) already exists
+[ovn-nbctl: a NAT with this type (dnat_and_snat), external_ip (30.0.0.1) and gateway_port () already exists
 ])
 AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:04:05:06])
 AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1                            192.168.1.2
-dnat             fd01::1                             fd11::2
-dnat_and_snat    30.0.0.1                            192.168.1.2
-dnat_and_snat    30.0.0.2                            192.168.1.3           00:00:00:04:05:06    lp0
-dnat_and_snat    fd01::1                             fd11::2
-dnat_and_snat    fd01::2                             fd11::3               00:00:00:01:02:03    lp0
-snat             30.0.0.1                            192.168.1.0/24
-snat             fd01::1                             fd11::/64
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat                                   30.0.0.1                            192.168.1.2
+dnat                                   fd01::1                             fd11::2
+dnat_and_snat                          30.0.0.1                            192.168.1.2
+dnat_and_snat                          30.0.0.2                            192.168.1.3         00:00:00:04:05:06    lp0
+dnat_and_snat                          fd01::1                             fd11::2
+dnat_and_snat                          fd01::2                             fd11::3             00:00:00:01:02:03    lp0
+snat                                   30.0.0.1                            192.168.1.0/24
+snat                                   fd01::1                             fd11::/64
 ])
 AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3])
 AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat fd01::2 fd11::3])
 AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1                            192.168.1.2
-dnat             fd01::1                             fd11::2
-dnat_and_snat    30.0.0.1                            192.168.1.2
-dnat_and_snat    30.0.0.2                            192.168.1.3
-dnat_and_snat    fd01::1                             fd11::2
-dnat_and_snat    fd01::2                             fd11::3
-snat             30.0.0.1                            192.168.1.0/24
-snat             fd01::1                             fd11::/64
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat                                   30.0.0.1                            192.168.1.2
+dnat                                   fd01::1                             fd11::2
+dnat_and_snat                          30.0.0.1                            192.168.1.2
+dnat_and_snat                          30.0.0.2                            192.168.1.3
+dnat_and_snat                          fd01::1                             fd11::2
+dnat_and_snat                          fd01::2                             fd11::3
+snat                                   30.0.0.1                            192.168.1.0/24
+snat                                   fd01::1                             fd11::/64
 ])
 
 check_row_count nb:NAT 0 options:stateless=true
@@ -598,40 +598,31 @@  AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0 dnat_and_snat 40.0.0.3 192.168.1.
 ])
 
 dnl Deletes the NATs
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3], [1], [],
-[ovn-nbctl: no matching NAT with the type (dnat_and_snat) and external_ip (30.0.0.3)
-])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.2], [1], [],
-[ovn-nbctl: no matching NAT with the type (dnat) and external_ip (30.0.0.2)
-])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 192.168.10.0/24], [1], [],
-[ovn-nbctl: no matching NAT with the type (snat) and logical_ip (192.168.10.0/24)
-])
-AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 192.168.10.0/24])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3])
 
 AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.1])
 AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat fd01::1])
 AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1                            192.168.1.2
-dnat             fd01::1                             fd11::2
-dnat_and_snat    30.0.0.2                            192.168.1.3
-dnat_and_snat    40.0.0.2                            192.168.1.4
-dnat_and_snat    fd01::2                             fd11::3
-snat             30.0.0.1                            192.168.1.0/24
-snat             40.0.0.3                            192.168.1.6
-snat             fd01::1                             fd11::/64
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat                                   30.0.0.1                            192.168.1.2
+dnat                                   fd01::1                             fd11::2
+dnat_and_snat                          30.0.0.2                            192.168.1.3
+dnat_and_snat                          40.0.0.2                            192.168.1.4
+dnat_and_snat                          fd01::2                             fd11::3
+snat                                   30.0.0.1                            192.168.1.0/24
+snat                                   40.0.0.3                            192.168.1.6
+snat                                   fd01::1                             fd11::/64
 ])
 
 AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])
 AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat_and_snat    30.0.0.2                            192.168.1.3
-dnat_and_snat    40.0.0.2                            192.168.1.4
-dnat_and_snat    fd01::2                             fd11::3
-snat             30.0.0.1                            192.168.1.0/24
-snat             40.0.0.3                            192.168.1.6
-snat             fd01::1                             fd11::/64
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat_and_snat                          30.0.0.2                            192.168.1.3
+dnat_and_snat                          40.0.0.2                            192.168.1.4
+dnat_and_snat                          fd01::2                             fd11::3
+snat                                   30.0.0.1                            192.168.1.0/24
+snat                                   40.0.0.3                            192.168.1.6
+snat                                   fd01::1                             fd11::/64
 ])
 
 AT_CHECK([ovn-nbctl lr-nat-del lr0])
@@ -702,12 +693,12 @@  AT_CHECK([ovn-nbctl show lr0 | grep -C2 'external port(s): "1"' | uuidfilt], [0]
 ])
 
 AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             40.0.0.4           1-3000           192.168.1.7
-dnat             40.0.0.5           1                192.168.1.10
-dnat_and_snat    40.0.0.5           1-3000           192.168.1.8
-dnat_and_snat    40.0.0.6           1-3000           192.168.1.9           00:00:00:04:05:06    lp0
-snat             40.0.0.3           21-65535         192.168.1.6
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat                                   40.0.0.4           1-3000           192.168.1.7
+dnat                                   40.0.0.5           1                192.168.1.10
+dnat_and_snat                          40.0.0.5           1-3000           192.168.1.8
+dnat_and_snat                          40.0.0.6           1-3000           192.168.1.9         00:00:00:04:05:06    lp0
+snat                                   40.0.0.3           21-65535         192.168.1.6
 ])
 
 AT_CHECK([ovn-nbctl lr-nat-del lr0])
@@ -755,7 +746,79 @@  AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.16 allowed_range], [1]
 [ovn-nbctl: 192.168.16: Invalid IP address or CIDR
 ])
 
-AT_CHECK([ovn-nbctl lr-nat-del lr0])])
+AT_CHECK([ovn-nbctl lr-nat-del lr0])
+
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp00 00:00:00:01:02:03 172.64.0.10/24])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp01 00:00:00:01:02:04 172.64.1.10/24])
+AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp00 chassis1])
+AT_CHECK([ovn-nbctl lr-add lr1])
+AT_CHECK([ovn-nbctl lrp-add lr1 lrp10 00:00:00:01:02:05 172.64.2.10/24])
+
+AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 snat 172.64.0.10 20.0.0.10])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 172.64.1.10 20.0.0.10], [1], [],
+[ovn-nbctl: lrp01 is not a distributed gateway router port.
+])
+AT_CHECK([ovn-nbctl --gateway-port=lrp10 lr-nat-add lr0 dnat_and_snat 172.64.2.10 20.0.0.10], [1], [],
+[ovn-nbctl: lrp10 is not a router port of logical router: lr0.
+])
+
+AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp01 chassis2])
+
+AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 172.64.1.10 20.0.0.10], [1], [],
+[ovn-nbctl: logical router: lr0 has multiple distributed gateway ports. NAT rule needs to specify gateway_port.
+])
+AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 dnat 30.0.0.10 20.0.0.10])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 30.0.0.10 20.0.0.10])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 30.0.0.10 20.0.0.20], [1], [],
+[ovn-nbctl: a NAT with this type (dnat), external_ip (30.0.0.10) and gateway_port (lrp01) already exists
+])
+
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+dnat             lrp00                 30.0.0.10                           20.0.0.10
+dnat             lrp01                 30.0.0.10                           20.0.0.10
+snat             lrp00                 172.64.0.10                         20.0.0.10
+])
+
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.10])
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+snat             lrp00                 172.64.0.10                         20.0.0.10
+])
+
+AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 snat 30.0.0.10 20.0.0.20])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 snat 30.0.0.10 20.0.0.20])
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+snat             lrp00                 172.64.0.10                         20.0.0.10
+snat             lrp00                 30.0.0.10                           20.0.0.20
+snat             lrp01                 30.0.0.10                           20.0.0.20
+])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat lrp11], [1], [],
+[ovn-nbctl: lrp11: port name not found
+])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat lrp00])
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+snat             lrp00                 172.64.0.10                         20.0.0.10
+snat             lrp00                 30.0.0.10                           20.0.0.20
+snat             lrp01                 30.0.0.10                           20.0.0.20
+])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 snat lrp00])
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE             GATEWAY_PORT          EXTERNAL_IP        EXTERNAL_PORT    LOGICAL_IP          EXTERNAL_MAC         LOGICAL_PORT
+snat             lrp01                 30.0.0.10                           20.0.0.20
+])
+
+AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0 lrp01], [1], [],
+[ovn-nbctl: 20.0.0: Invalid IP address or CIDR
+])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0.10 lrp01], [1], [],
+[ovn-nbctl: no matching NAT with the type (snat), logical_ip (20.0.0.10) and gateway_port (lrp01)
+])
+AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 20.0.0.10 lrp01])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0.20 lrp01])
+])
 
 dnl ---------------------------------------------------------------------
 
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index daa664982..f9621b804 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -4306,8 +4306,6 @@  check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1
 check ovn-nbctl lr-nat-add lr0 dnat 42.42.42.42 192.168.0.2
 check ovn-nbctl --wait=sb sync
 
-ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64'
-
 AT_CHECK([ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64' | grep "output(\"sw0-port1\")"], [0], [ignore])
 
 dnl If we remove the DNAT entry we will be unable to trace to the DNAT address
@@ -5808,12 +5806,6 @@  AT_CHECK([grep lr_in_gw_redirect lrflows | grep cr-DR | sed 's/table=../table=??
   table=??(lr_in_gw_redirect  ), priority=50   , match=(outport == "DR-S3"), action=(outport = "cr-DR-S3"; next;)
 ])
 
-# Check that ovn-northd logs a warning when trying to configure NAT
-# on the router with multiple distributed gw ports.  Such configurations are
-# not supported yet.
-check ovn-nbctl lr-nat-add DR dnat_and_snat 42.42.42.1 20.0.0.2
-AT_CHECK([grep -q 'NAT is configured on logical router DR, which has 2 distributed gateway ports. NAT is not supported yet when there is more than one distributed gateway port on the router.' northd/ovn-northd.log], [0])
-
 AT_CLEANUP
 ])
 
@@ -6479,3 +6471,171 @@  AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort],
 
 AT_CLEANUP
 ])
+
+AT_SETUP([ovn-northd -- lr multiple gw ports NAT])
+AT_KEYWORDS([multiple-l3dgw-ports])
+ovn_start
+
+# Logical network:
+# 1 Logical Router, 3 bridged Logical Switches,
+# 1 gateway chassis attached to each corresponding LRP.
+#
+#                | S1 (gw1)
+#                |
+#      ls  ----  DR -- S3 (gw3)
+# (20.0.0.0/24)  |
+#                | S2 (gw2)
+#
+# Validate SNAT, DNAT and DNAT_AND_SNAT behavior with multiple
+# distributed gateway LRPs.
+
+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+check ovn-sbctl chassis-add gw2 geneve 128.0.0.1
+check ovn-sbctl chassis-add gw3 geneve 129.0.0.1
+
+check ovn-nbctl lr-add DR
+check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl lrp-add DR DR-S2 03:ac:10:01:00:01 10.0.0.1/24
+check ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.1/24
+check ovn-nbctl lrp-add DR DR-ls 05:ac:10:01:00:01 20.0.0.1/24
+
+check ovn-nbctl ls-add S1
+check ovn-nbctl lsp-add S1 S1-DR
+check ovn-nbctl lsp-set-type S1-DR router
+check ovn-nbctl lsp-set-addresses S1-DR router
+check ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+
+check ovn-nbctl ls-add S2
+check ovn-nbctl lsp-add S2 S2-DR
+check ovn-nbctl lsp-set-type S2-DR router
+check ovn-nbctl lsp-set-addresses S2-DR router
+check ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
+
+check ovn-nbctl ls-add S3
+check ovn-nbctl lsp-add S3 S3-DR
+check ovn-nbctl lsp-set-type S3-DR router
+check ovn-nbctl lsp-set-addresses S3-DR router
+check ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
+
+check ovn-nbctl ls-add  ls
+check ovn-nbctl lsp-add ls ls-DR
+check ovn-nbctl lsp-set-type ls-DR router
+check ovn-nbctl lsp-set-addresses ls-DR router
+check ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
+
+check ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
+check ovn-nbctl lrp-set-gateway-chassis DR-S2 gw2
+check ovn-nbctl lrp-set-gateway-chassis DR-S3 gw3
+
+# Configure SNAT
+check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR snat  172.16.1.10    20.0.0.10
+check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR snat  10.0.0.10      20.0.0.10
+check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR snat  192.168.0.10   20.0.0.10
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows DR > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+check_lr_in_arp_nat_flows() {
+    AT_CHECK([grep lr_in_ip_input lrflows | grep arp | grep -e 172.16.1.10 -e 10.0.0.10 -e 192.168.0.10 | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_ip_input     ), priority=90   , match=(arp.op == 1 && arp.tpa == 10.0.0.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
+  table=??(lr_in_ip_input     ), priority=90   , match=(arp.op == 1 && arp.tpa == 172.16.1.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
+  table=??(lr_in_ip_input     ), priority=90   , match=(arp.op == 1 && arp.tpa == 192.168.0.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
+  table=??(lr_in_ip_input     ), priority=91   , match=(inport == "DR-S1" && arp.op == 1 && arp.tpa == 172.16.1.10), action=(drop;)
+  table=??(lr_in_ip_input     ), priority=91   , match=(inport == "DR-S2" && arp.op == 1 && arp.tpa == 10.0.0.10), action=(drop;)
+  table=??(lr_in_ip_input     ), priority=91   , match=(inport == "DR-S3" && arp.op == 1 && arp.tpa == 192.168.0.10), action=(drop;)
+  table=??(lr_in_ip_input     ), priority=92   , match=(inport == "DR-S1" && arp.op == 1 && arp.tpa == 172.16.1.10 && is_chassis_resident("cr-DR-S1")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
+  table=??(lr_in_ip_input     ), priority=92   , match=(inport == "DR-S2" && arp.op == 1 && arp.tpa == 10.0.0.10 && is_chassis_resident("cr-DR-S2")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
+  table=??(lr_in_ip_input     ), priority=92   , match=(inport == "DR-S3" && arp.op == 1 && arp.tpa == 192.168.0.10 && is_chassis_resident("cr-DR-S3")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
+])
+}
+
+check_lr_in_unsnat_flows() {
+    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;)
+])
+}
+
+check_lr_out_snat_flows() {
+    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);)
+])
+}
+
+check_lr_in_unsnat_flows
+check_lr_out_snat_flows
+check_lr_in_arp_nat_flows
+
+check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10
+AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat | grep ct_snat | wc -l], [0], [0
+])
+
+# Configure DNAT
+check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR dnat  172.16.1.10    20.0.0.10
+check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR dnat  10.0.0.10      20.0.0.10
+check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR dnat  192.168.0.10   20.0.0.10
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows DR > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+check_lr_in_dnat_flows() {
+    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);)
+])
+}
+
+check_lr_out_undnat_flows() {
+    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;)
+])
+}
+
+check_lr_in_dnat_flows
+check_lr_out_undnat_flows
+check_lr_in_arp_nat_flows
+
+check ovn-nbctl --wait=sb lr-nat-del DR dnat
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_dnat -e lr_out_undnat | grep ct_dnat | wc -l], [0], [0
+])
+
+# Configure DNAT_AND_SNAT
+check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR dnat_and_snat  172.16.1.10    20.0.0.10
+check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR dnat_and_snat  10.0.0.10      20.0.0.10
+check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR dnat_and_snat  192.168.0.10   20.0.0.10
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows DR > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+check_lr_in_unsnat_flows
+check_lr_out_snat_flows
+check_lr_in_dnat_flows
+check_lr_out_undnat_flows
+check_lr_in_arp_nat_flows
+
+check ovn-nbctl --wait=sb lr-nat-del DR dnat_and_snat
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat -e lr_in_dnat -e lr_out_undnat | grep ct_snat| wc -l], [0], [0
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index 0c2fe9f97..2a1011b1a 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -29556,7 +29556,7 @@  as hv2 check ovn-appctl -t ovn-controller recompute
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [1])
 
 # Enable dnat_and_snat on lr, and now hv2 should see flows for lsp1.
-AT_CHECK([ovn-nbctl --wait=hv lr-nat-add lr dnat_and_snat 192.168.0.1 10.0.1.3 lsp1 f0:00:00:00:00:03])
+AT_CHECK([ovn-nbctl --wait=hv --gateway-port=lrp_lr_ls1 lr-nat-add lr dnat_and_snat 192.168.0.1 10.0.1.3 lsp1 f0:00:00:00:00:03])
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [0], [ignore])
 
 OVN_CLEANUP([hv1],[hv2])
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 74651c762..3e9176fa0 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -1142,7 +1142,7 @@ 
     <h2>NAT Commands</h2>
 
     <dl>
-      <dt>[<code>--may-exist</code>] [<code>--stateless</code>]<code>lr-nat-add</code> <var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
+      <dt>[<code>--may-exist</code>] [<code>--stateless</code>] [<code>--gateway_port</code>=<var>GATEWAY_PORT</var>] <code>lr-nat-add</code> <var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
       <dd>
         <p>
           Adds the specified NAT to <var>router</var>.
@@ -1158,7 +1158,6 @@ 
           The <var>logical_port</var> is the name of an existing logical
           switch port where the <var>logical_ip</var> resides.
           The <var>external_mac</var> is an Ethernet address.
-          The <var>--stateless</var>
         </p>
         <p>
           When <code>--stateless</code> is specified then it implies that
@@ -1169,6 +1168,16 @@ 
           with any other NAT rule.
         </p>
 
+        <p>
+          <code>--gateway-port</code> option allows specifying the distributed
+          gateway port of <var>router</var> where the NAT rule needs to be
+          applied. <var>GATEWAY_PORT</var> should reference a
+          <ref table="Logical_Router_Port"/> row that is a distributed gateway
+          port of <var>router</var>. When <var>router</var> has multiple
+          distributed gateway ports, it is an error to not specify the
+          <var>GATEWAY_PORT</var>.
+        </p>
+
         <p>
           When <var>type</var> is <code>dnat</code>, the externally
           visible IP address <var>external_ip</var> is DNATted to the
@@ -1201,32 +1210,39 @@ 
         <p>
           It is an error if a NAT already exists with the same values
           of <var>router</var>, <var>type</var>, <var>external_ip</var>,
-          and <var>logical_ip</var>, unless <code>--may-exist</code> is
-          specified.  When <code>--may-exist</code>,
+          <var>logical_ip</var> and <var>GATEWAY_PORT</var> (in case of
+          multiple distributed gateway ports), unless <code>--may-exist</code>
+          is specified.  When <code>--may-exist</code>,
           <var>logical_port</var>, and <var>external_mac</var> are all
           specified, the existing values of <var>logical_port</var> and
           <var>external_mac</var> are overwritten.
         </p>
       </dd>
 
-      <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>]]</dt>
+      <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>] [<var>gateway_port</var>]]</dt>
       <dd>
         <p>
           Deletes NATs from <var>router</var>.  If only <var>router</var>
           is supplied, all the NATs from the logical router are
           deleted.  If <var>type</var> is also specified, then all the
           NATs that match the <var>type</var> will be deleted from the logical
-          router.  If all the fields are given, then a single NAT rule
-          that matches all the fields will be deleted.  When <var>type</var>
-          is <code>snat</code>, the <var>ip</var> should be logical_ip.
-          When <var>type</var> is <code>dnat</code> or
-          <code>dnat_and_snat</code>, the <var>ip</var> shoud be external_ip.
-        </p>
-
-        <p>
-          It is an error if <var>ip</var> is specified and there
-          is no matching NAT entry, unless <code>--if-exists</code> is
-          specified.
+          router. If <var>ip</var> is also specified without specifying
+          <var>gateway_port</var>, then all the NATs that match the
+          <var>type</var> and <var>ip</var> will be deleted from the logical
+          router. If <var>gateway_port</var> is specified without specifying
+          <var>ip</var>, then all the NATs that match the <var>type</var> and
+          <var>gateway_port</var> will be deleted from the logical router.
+          If all the fields are given, then a single NAT rule that matches all
+          the fields will be deleted. When <var>type</var> is
+          <code>snat</code>, the <var>ip</var> should be logical_ip. When
+          <var>type</var> is <code>dnat</code> or <code>dnat_and_snat</code>,
+          the <var>ip</var> shoud be external_ip.
+        </p>
+
+        <p>
+          It is an error if both <var>ip</var> and <var>gateway_port</var> are
+          specified and there is no matching NAT entry, unless
+          <code>--if-exists</code> is specified.
         </p>
       </dd>
 
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 7bcc2c66a..f8b957792 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -378,10 +378,11 @@  NAT commands:\n\
   [--stateless]\n\
   [--portrange]\n\
   [--add-route]\n\
+  [--gateway-port=GATEWAY_PORT]\n\
   lr-nat-add ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]\n\
                             [EXTERNAL_PORT_RANGE]\n\
                             add a NAT to ROUTER\n\
-  lr-nat-del ROUTER [TYPE [IP]]\n\
+  lr-nat-del ROUTER [TYPE [IP] [GATEWAY_PORT]]\n\
                             remove NATs from ROUTER\n\
   lr-nat-list ROUTER        print NATs for ROUTER\n\
 \n\
@@ -4566,6 +4567,7 @@  nbctl_pre_lr_nat_add(struct ctl_context *ctx)
 {
     ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name);
     ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_nat);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_ports);
 
     ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
 
@@ -4574,7 +4576,14 @@  nbctl_pre_lr_nat_add(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_type);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_options);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
+    ovsdb_idl_add_column(ctx->idl,
+                         &nbrec_logical_router_port_col_gateway_chassis);
+    ovsdb_idl_add_column(ctx->idl,
+                         &nbrec_logical_router_port_col_ha_chassis_group);
 }
 
 static void
@@ -4709,6 +4718,52 @@  nbctl_lr_nat_add(struct ctl_context *ctx)
         ctl_error(ctx, "routes cannot be added for snat types.");
         goto cleanup;
     }
+
+    const char *dgw_port_name = shash_find_data(&ctx->options,
+                                                "--gateway-port");
+    const struct nbrec_logical_router_port *dgw_port = NULL;
+    if (dgw_port_name) {
+        error = lrp_by_name_or_uuid(ctx, dgw_port_name,
+                                    true, &dgw_port);
+        if (error) {
+            ctx->error = error;
+            goto cleanup;
+        }
+
+        bool nat_lr_port = false;
+        for (size_t i = 0; i < lr->n_ports; i++) {
+            const struct nbrec_logical_router_port *lrp = lr->ports[i];
+            if (lrp == dgw_port) {
+                nat_lr_port = true;
+            }
+        }
+        if (!nat_lr_port) {
+            ctl_error(ctx, "%s is not a router port of logical router: %s.",
+                      dgw_port_name, ctx->argv[1]);
+            goto cleanup;
+        }
+
+        if (!dgw_port->ha_chassis_group && !dgw_port->n_gateway_chassis) {
+            ctl_error(ctx, "%s is not a distributed gateway router port.",
+                      dgw_port_name);
+            goto cleanup;
+        }
+    } else {
+        size_t num_l3dgw_ports = 0;
+        for (size_t i = 0; i < lr->n_ports; i++) {
+            const struct nbrec_logical_router_port *lrp = lr->ports[i];
+            if (lrp->ha_chassis_group || lrp->n_gateway_chassis) {
+                num_l3dgw_ports++;
+            }
+        }
+        if (num_l3dgw_ports > 1) {
+            ctl_error(ctx, "logical router: %s has multiple distributed "
+                      "gateway ports. NAT rule needs to specify "
+                      "gateway_port.", ctx->argv[1]);
+            goto cleanup;
+        }
+    }
+
     for (size_t i = 0; i < lr->n_nat; i++) {
         const struct nbrec_nat *nat = lr->nat[i];
 
@@ -4725,7 +4780,8 @@  nbctl_lr_nat_add(struct ctl_context *ctx)
             continue;
         }
 
-        if (!strcmp(nat_type, nat->type)) {
+        if (!strcmp(nat_type, nat->type) &&
+            dgw_port == nat->gateway_port) {
             if (!strcmp(is_snat ? new_logical_ip : new_external_ip,
                         is_snat ? old_logical_ip : old_external_ip)) {
                 if (!strcmp(is_snat ? new_external_ip : new_logical_ip,
@@ -4737,18 +4793,20 @@  nbctl_lr_nat_add(struct ctl_context *ctx)
                             nbrec_nat_set_external_mac(nat, external_mac);
                             should_return = true;
                         } else {
-                            ctl_error(ctx, "%s, %s: a NAT with this "
-                                      "external_ip and logical_ip already "
-                                      "exists", new_external_ip,
-                                      new_logical_ip);
+                            ctl_error(ctx, "%s, %s, %s: a NAT with this "
+                                      "external_ip, logical_ip and "
+                                      "gateway_port already exists",
+                                      new_external_ip, new_logical_ip,
+                                      dgw_port ? dgw_port->name : "");
                             should_return = true;
                         }
                 } else {
-                    ctl_error(ctx, "a NAT with this type (%s) and %s (%s) "
-                              "already exists",
+                    ctl_error(ctx, "a NAT with this type (%s), %s (%s) "
+                              "and gateway_port (%s) already exists",
                               nat_type,
                               is_snat ? "logical_ip" : "external_ip",
-                              is_snat ? new_logical_ip : new_external_ip);
+                              is_snat ? new_logical_ip : new_external_ip,
+                              dgw_port ? dgw_port->name : "");
                     should_return = true;
                 }
             }
@@ -4792,6 +4850,10 @@  nbctl_lr_nat_add(struct ctl_context *ctx)
     if (add_route) {
         smap_add(&nat_options, "add_route", "true");
     }
+
+    if (dgw_port) {
+        nbrec_nat_update_gateway_port_addvalue(nat, dgw_port);
+    }
     nbrec_nat_set_options(nat, &nat_options);
 
     smap_destroy(&nat_options);
@@ -4813,6 +4875,9 @@  nbctl_pre_lr_nat_del(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_type);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_ip);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
 }
 
 static void
@@ -4853,13 +4918,58 @@  nbctl_lr_nat_del(struct ctl_context *ctx)
     }
 
     char *nat_ip = normalize_prefix_str(ctx->argv[3]);
+    int is_snat = !strcmp("snat", nat_type);
+    const struct nbrec_logical_router_port *dgw_port = NULL;
+
+    if (ctx->argc == 4) {
+        if (!nat_ip) {
+            /* GATEWAY_PORT is assumed to be passed. */
+            error = lrp_by_name_or_uuid(ctx, ctx->argv[3], true, &dgw_port);
+            if (error) {
+                ctx->error = error;
+                return;
+            }
+
+            /* Deletes all NATs matching the type and gateway_port
+             * specified. */
+            for (size_t i = 0; i < lr->n_nat; i++) {
+                if (!strcmp(nat_type, lr->nat[i]->type) &&
+                    lr->nat[i]->gateway_port == dgw_port) {
+                    nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]);
+                }
+            }
+            return;
+        }
+
+        /* Remove NAT rules matching the type and IP (based on type). */
+        for (size_t i = 0; i < lr->n_nat; i++) {
+            struct nbrec_nat *nat = lr->nat[i];
+            char *old_ip = normalize_prefix_str(is_snat
+                                                ? nat->logical_ip
+                                                : nat->external_ip);
+            if (!old_ip) {
+                continue;
+            }
+            if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
+                nbrec_logical_router_update_nat_delvalue(lr, nat);
+            }
+            free(old_ip);
+        }
+        goto cleanup;
+    }
+
     if (!nat_ip) {
         ctl_error(ctx, "%s: Invalid IP address or CIDR", ctx->argv[3]);
         return;
     }
 
-    int is_snat = !strcmp("snat", nat_type);
-    /* Remove the matching NAT. */
+    error = lrp_by_name_or_uuid(ctx, ctx->argv[4], true, &dgw_port);
+    if (error) {
+        ctx->error = error;
+        goto cleanup;
+    }
+
+    /* Remove matching NAT. */
     for (size_t i = 0; i < lr->n_nat; i++) {
         struct nbrec_nat *nat = lr->nat[i];
         bool should_return = false;
@@ -4869,7 +4979,8 @@  nbctl_lr_nat_del(struct ctl_context *ctx)
         if (!old_ip) {
             continue;
         }
-        if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
+        if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip) &&
+            nat->gateway_port == dgw_port) {
             nbrec_logical_router_update_nat_delvalue(lr, nat);
             should_return = true;
         }
@@ -4880,8 +4991,10 @@  nbctl_lr_nat_del(struct ctl_context *ctx)
     }
 
     if (must_exist) {
-        ctl_error(ctx, "no matching NAT with the type (%s) and %s (%s)",
-                  nat_type, is_snat ? "logical_ip" : "external_ip", nat_ip);
+        ctl_error(ctx, "no matching NAT with the type (%s), %s (%s) and "
+                  "gateway_port (%s)", nat_type,
+                  is_snat ? "logical_ip" : "external_ip", nat_ip,
+                  ctx->argv[4]);
     }
 
 cleanup:
@@ -4900,6 +5013,9 @@  nbctl_pre_lr_nat_list(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
     ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
 }
 
 static void
@@ -4915,11 +5031,18 @@  nbctl_lr_nat_list(struct ctl_context *ctx)
     struct smap lr_nats = SMAP_INITIALIZER(&lr_nats);
     for (size_t i = 0; i < lr->n_nat; i++) {
         const struct nbrec_nat *nat = lr->nat[i];
-        char *key = xasprintf("%-17.13s%s", nat->type, nat->external_ip);
+        char *key = xasprintf("%-17.13s%-22.18s%s",
+                              nat->type,
+                              nat->gateway_port
+                              ? nat->gateway_port->name
+                              : "",
+                              nat->external_ip);
         if (nat->external_mac && nat->logical_port) {
-            smap_add_format(&lr_nats, key, "%-17.13s%-22.18s%-21.17s%s",
+            smap_add_format(&lr_nats, key,
+                            "%-17.13s%-20.16s%-21.17s%s",
                             nat->external_port_range,
-                            nat->logical_ip, nat->external_mac,
+                            nat->logical_ip,
+                            nat->external_mac,
                             nat->logical_port);
         } else {
             smap_add_format(&lr_nats, key, "%-17.13s%s",
@@ -4932,12 +5055,12 @@  nbctl_lr_nat_list(struct ctl_context *ctx)
     const struct smap_node **nodes = smap_sort(&lr_nats);
     if (nodes) {
         ds_put_format(&ctx->output,
-                "%-17.13s%-19.15s%-17.13s%-22.18s%-21.17s%s\n",
-                "TYPE", "EXTERNAL_IP", "EXTERNAL_PORT", "LOGICAL_IP",
-                "EXTERNAL_MAC", "LOGICAL_PORT");
+                "%-17.13s%-22.18s%-19.15s%-17.13s%-20.16s%-21.17s%s\n",
+                "TYPE", "GATEWAY_PORT", "EXTERNAL_IP", "EXTERNAL_PORT",
+                "LOGICAL_IP", "EXTERNAL_MAC", "LOGICAL_PORT");
         for (size_t i = 0; i < smap_count(&lr_nats); i++) {
             const struct smap_node *node = nodes[i];
-            ds_put_format(&ctx->output, "%-36.32s%s\n",
+            ds_put_format(&ctx->output, "%-58.54s%s\n",
                     node->key, node->value);
         }
         free(nodes);
@@ -7109,8 +7232,9 @@  static const struct ctl_command_syntax nbctl_commands[] = {
       "ROUTER TYPE EXTERNAL_IP LOGICAL_IP"
       "[LOGICAL_PORT EXTERNAL_MAC] [EXTERNAL_PORT_RANGE]",
       nbctl_pre_lr_nat_add, nbctl_lr_nat_add,
-      NULL, "--may-exist,--stateless,--portrange,--add-route", RW },
-    { "lr-nat-del", 1, 3, "ROUTER [TYPE [IP]]",
+      NULL, "--may-exist,--stateless,--portrange,--add-route,"
+      "--gateway-port=", RW },
+    { "lr-nat-del", 1, 4, "ROUTER [TYPE [IP] [GATEWAY_PORT]]",
       nbctl_pre_lr_nat_del, nbctl_lr_nat_del, NULL, "--if-exists", RW },
     { "lr-nat-list", 1, 1, "ROUTER", nbctl_pre_lr_nat_list,
       nbctl_lr_nat_list, NULL, "", RO },