Blob Blame History Raw
From afab54226927711c719e8012c43333273f581ac7 Mon Sep 17 00:00:00 2001
From: Numan Siddique <numans@ovn.org>
Date: Sat, 1 Feb 2020 16:23:43 +0530
Subject: [PATCH] ovn-northd: Address scale issues with DNAT flows.

When the commit [1] added Distributed NAT support in OVN, it didn't address
the requirement of making East/West NAT traffic distributed. The E/W NAT
traffic was still centralized. Later a couple of patches [2], addressed this
requirement. But the approach taken in [2] resulted in a lot of logical flows
as number of dnat_and_snat entries increase, as reported in @Reported-at.

This patch
  - reverts the approch taken in [2].
  - removing the flows which does the NAT direct (REGBIT_NAT_REDIRECT) to
    the gateway chassis.
  - and to solve the E/W centralized NAT it does the following:
     * Since for each NAT entry we know the MAC binding to be used for the
       external_ip - either the external_mac if set or the MAC of the
       distributed gateway router port, this patch adds the flows in the
       S_ROUTER_IN_ARP_RESOLVE stage to set the eth.dst to the MAC if the
       IP destination is external_ip.
     * The existing flows in the S_ROUTER_OUT_EGR_LOOP are now added by additional
       match -  is_chassis_resident('P') - where 'P' is logical_port of the NAT entry
       if set, otherwise it is the chassis resident port of distributed router port.
       With this additional match, the packet will be loopbacked to apply the unSNAT/DNAT
       rules on the relevant chassis.

Suppose if a logical port 'P' with IP 'A' has a dnat_and_snat entry with external_mac/logical_port
set, and if the packet's IP destination is one of the DNAT IP - then the packet will be sent out
of the local chassis, since eth.dst is resolved in the S_ROUTER_IN_ARP_RESOLVE stage.
If the external_mac/logical_port is not in NAT entry, then the packet will be redirected to
the gateway chassis.

With this patch, for the logical resource reported in @Reported-at, the number of logical
flows come down to around 45k from 650k.

[1] - ceacd9d49316("ovn: distributed NAT flows")

[2] - 551e3d989557("OVN: fix DVR Floating IP support")
      8244c6b6bd88("OVN: do not distribute traffic for local FIP")

Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2020-January/049714.html
Reported-by: Daniel Alvarez Sanchez <dalvarez@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
Acked-by: Dumitru Ceara <dceara@redhat.com>
Tested-By: Daniel Alvarez Sanchez <dalvarez@redhat.com>
Acked-By: Daniel Alvarez Sanchez <dalvarez@redhat.com>

(cherry-picked from upstream commit 2dc7869436de32205f60128172196b3a207ab265)
Conflicts:
	ovn/northd/ovn-northd.c

Change-Id: I7684c7f5114ba7c800293e843d5d4b856dedbb96
---
 ovn/northd/ovn-northd.8.xml | 191 ++++++++------------------
 ovn/northd/ovn-northd.c     | 263 +++++-------------------------------
 tests/ovn-northd.at         |   8 +-
 3 files changed, 98 insertions(+), 364 deletions(-)

diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index d94d9aef9..a42a67c19 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -1487,6 +1487,24 @@ next;
     </p>
 
     <ul>
+      <li>
+        <p>
+          For each NAT entry of a distributed logical router  (with
+          distributed gateway router port) of type <code>snat</code>,
+          a priorirty-120 flow with the match <code>inport == <var>P</var>
+          &amp;&amp; ip4.src == <var>A</var></code> advances the packet to
+          the next pipeline, where <var>P</var> is the distributed logical
+          router port and <var>A</var> is the <code>external_ip</code> set
+          in the NAT entry. If <var>A</var> is an IPv6 address, then
+          <code>ip6.src</code> is used for the match.
+        </p>
+
+        <p>
+          The above flow is required to handle the routing of the East/west NAT
+          traffic.
+        </p>
+      </li>
+
       <li>
         <p>
           L3 admission control: A priority-100 flow drops packets that match
@@ -1977,21 +1995,6 @@ icmp6 {
           <code>redirect-chassis</code>.
         </p>
 
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the source IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-50 flow matches
-          <code>ip &amp;&amp; ip4.dst == <var>B</var></code> or
-          <code>ip &amp;&amp; ip6.dst == <var>B</var></code>
-          with an action
-          <code>REGBIT_NAT_REDIRECT = 1; next;</code>.  This flow is for
-          east/west traffic to a NAT destination IPv4/IPv6 address.  By
-          setting the <code>REGBIT_NAT_REDIRECT</code> flag, in the
-          ingress table <code>Gateway Redirect</code> this will trigger a
-          redirect to the instance of the gateway port on the
-          <code>redirect-chassis</code>.
-        </p>
-
         <p>
           A priority-0 logical flow with match <code>1</code> has actions
           <code>next;</code>.
@@ -2147,20 +2150,6 @@ icmp6 {
           <code>redirect-chassis</code>.
         </p>
 
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the destination IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-50 flow matches <code>ip &amp;&amp;
-          ip4.dst == <var>B</var></code> or <code>ip &amp;&amp;
-          ip6.dst == <var>B</var></code> with an action
-          <code>REGBIT_NAT_REDIRECT = 1; next;</code>.  This flow is for
-          east/west traffic to a NAT destination IPv4/IPv6 address.  By
-          setting the <code>REGBIT_NAT_REDIRECT</code> flag, in the
-          ingress table <code>Gateway Redirect</code> this will trigger a
-          redirect to the instance of the gateway port on the
-          <code>redirect-chassis</code>.
-        </p>
-
         <p>
           A priority-0 logical flow with match <code>1</code> has actions
           <code>next;</code>.
@@ -2285,54 +2274,6 @@ output;
         </p>
       </li>
 
-      <li>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-400
-          logical flow for each ip source/destination couple that matches the
-          <code>dnat_and_snat</code> NAT rules configured. These flows will
-          allow to properly forward traffic to the external connections if
-          available and avoid sending it through the tunnel.
-          Assuming the two following NAT rules have been configured:
-        </p>
-
-        <pre>
-external_ip{0,1} = <var>EIP{0,1}</var>;
-external_mac{0,1} = <var>MAC{0,1}</var>;
-logical_ip{0,1} = <var>LIP{0,1}</var>;
-        </pre>
-
-        <p>
-            the following action will be applied:
-        </p>
-
-        <pre>
-eth.dst = <var>MAC0</var>;
-eth.src = <var>MAC1</var>;
-reg0 = ip4.dst; /* xxreg0 = ip6.dst; in the IPv6 case */
-reg1 = <var>EIP1</var>; /* xxreg1 in the IPv6 case */
-outport = <code>redirect-chassis-port</code>;
-<code>REGBIT_DISTRIBUTED_NAT = 1; next;</code>.
-        </pre>
-
-        <p>
-            Morover a priority-400 logical flow is configured for each
-            <code>dnat_and_snat</code> NAT rule configured in order to
-            not send traffic for local FIP through the overlay tunnels
-            but manage it in the local hypervisor
-        </p>
-      </li>
-
-      <li>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-300
-          logical flow with match <code>REGBIT_NAT_REDIRECT == 1</code> has
-          actions <code>ip.ttl--; next;</code>.  The <code>outport</code>
-          will be set later in the Gateway Redirect table.
-        </p>
-      </li>
-
       <li>
         <p>
           IPv4 routing table.  For each route to IPv4 network <var>N</var> with
@@ -2427,23 +2368,6 @@ next;
         </p>
       </li>
 
-      <li>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-400
-          logical flow with match <code>REGBIT_DISTRIBUTED_NAT == 1</code>
-          has action <code>next;</code>
-        </p>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-200
-          logical flow with match <code>REGBIT_NAT_REDIRECT == 1</code> has
-          actions <code>eth.dst = <var>E</var>; next;</code>, where
-          <var>E</var> is the ethernet address of the router's distributed
-          gateway port.
-        </p>
-      </li>
-
       <li>
         <p>
           Static MAC bindings.  MAC bindings can be known statically based on
@@ -2518,6 +2442,35 @@ next;
         </p>
       </li>
 
+      <li>
+        <p>
+          Static MAC bindings from NAT entries.  MAC bindings can also be known
+          for the entries in the <code>NAT</code> table. Below flows are
+          programmed for distributed logical routers i.e with a distributed
+          router port.
+        </p>
+
+        <p>
+          For each row in the <code>NAT</code> table with IPv4 address
+          <var>A</var> in the <ref column="external_ip"
+          table="NAT" db="OVN_Northbound"/> column of
+          <ref table="NAT" db="OVN_Northbound"/> table, a priority-100
+          flow with the match <code>outport === <var>P</var> &amp;&amp;
+          reg0 == <var>A</var></code> has actions <code>eth.dst = <var>E</var>;
+          next;</code>, where <code>P</code> is the distributed logical router
+          port, <var>E</var> is the Ethernet address if set in the
+          <ref column="external_mac" table="NAT" db="OVN_Northbound"/> column
+          of <ref table="NAT" db="OVN_Northbound"/> table for of type
+          <code>dnat_and_snat</code>, otherwise the Ethernet address of the
+          distributed logical router port.
+        </p>
+
+        <p>
+          For IPv6 NAT entries, same flows are added, but using the register
+          <code>xxreg0</code> for the match.
+        </p>
+      </li>
+
       <li>
         <p>
           Dynamic MAC bindings.  These flows resolve MAC-to-IP bindings
@@ -2640,20 +2593,6 @@ icmp4 {
     </p>
 
     <ul>
-      <li>
-        A priority-300 logical flow with match
-        <code>REGBIT_DISTRIBUTED_NAT == 1</code> has action
-        <code>next;</code>
-      </li>
-      <li>
-        A priority-200 logical flow with match
-        <code>REGBIT_NAT_REDIRECT == 1</code> has actions
-        <code>outport = <var>CR</var>; next;</code>, where <var>CR</var>
-        is the <code>chassisredirect</code> port representing the instance
-        of the logical router distributed gateway port on the
-        <code>redirect-chassis</code>.
-      </li>
-
       <li>
         A priority-150 logical flow with match
         <code>outport == <var>GW</var> &amp;&amp;
@@ -2945,19 +2884,6 @@ nd_ns {
       ports specifies a <code>redirect-chassis</code>.
     </p>
 
-    <p>
-      Earlier in the ingress pipeline, some east-west traffic was
-      redirected to the <code>chassisredirect</code> port, based on
-      flows in the <code>UNSNAT</code> and <code>DNAT</code> ingress
-      tables setting the <code>REGBIT_NAT_REDIRECT</code> flag, which
-      then triggered a match to a flow in the
-      <code>Gateway Redirect</code> ingress table.  The intention was
-      not to actually send traffic out the distributed gateway port
-      instance on the <code>redirect-chassis</code>.  This traffic was
-      sent to the distributed gateway port instance in order for DNAT
-      and/or SNAT processing to be applied.
-    </p>
-
     <p>
       While UNDNAT and SNAT processing have already occurred by this
       point, this traffic needs to be forced through egress loopback on
@@ -2973,23 +2899,20 @@ nd_ns {
 
     <ul>
       <li>
-        <p>
-          For each <code>dnat_and_snat</code> NAT rule couple in the
-          OVN Northbound database on a distributed router,
-          a priority-200 logical with match
-          <code>ip4.dst == <var>external_ip0</var> &amp;&amp;
-          ip4.src == <var>external_ip1</var></code>, has action
-          <code>next;</code>
-        </p>
-
         <p>
           For each NAT rule in the OVN Northbound database on a
           distributed router, a priority-100 logical flow with match
           <code>ip4.dst == <var>E</var> &amp;&amp;
-          outport == <var>GW</var></code>, where <var>E</var> is the
-          external IP address specified in the NAT rule, and <var>GW</var>
-          is the logical router distributed gateway port, with the
-          following actions:
+          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.
+          If <ref column="logical_port"
+          table="NAT" db="OVN_Northbound"/> column of
+          <ref table="NAT" db="OVN_Northbound"/> table is NOT set, then
+          <var>P</var> is the <code>chassisredirect port</code> of
+          <var>GW</var> with the following actions:
         </p>
 
         <pre>
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 58213742d..e2df94f4b 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -205,17 +205,16 @@ enum ovn_stage {
 #define REGBIT_ND_RA_OPTS_RESULT "reg0[5]"
 
 /* Register definitions for switches and routers. */
-#define REGBIT_NAT_REDIRECT     "reg9[0]"
+
 /* Indicate that this packet has been recirculated using egress
  * loopback.  This allows certain checks to be bypassed, such as a
  * logical router dropping packets with source IP address equals
  * one of the logical router's own IP addresses. */
-#define REGBIT_EGRESS_LOOPBACK  "reg9[1]"
-#define REGBIT_DISTRIBUTED_NAT  "reg9[2]"
+#define REGBIT_EGRESS_LOOPBACK  "reg9[0]"
 /* Register to store the result of check_pkt_larger action. */
-#define REGBIT_PKT_LARGER        "reg9[3]"
-#define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[4]"
-#define REGBIT_SKIP_LOOKUP_NEIGHBOR "reg9[5]"
+#define REGBIT_PKT_LARGER        "reg9[1]"
+#define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]"
+#define REGBIT_SKIP_LOOKUP_NEIGHBOR "reg9[3]"
 
 #define FLAGBIT_NOT_VXLAN "flags[1] == 0"
 
@@ -6599,128 +6598,6 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od,
     ds_destroy(&actions);
 }
 
-static void
-add_distributed_nat_routes(struct hmap *lflows, const struct ovn_port *op)
-{
-    struct ds actions = DS_EMPTY_INITIALIZER;
-    struct ds match = DS_EMPTY_INITIALIZER;
-
-    if (!op->od->l3dgw_port) {
-        return;
-    }
-
-    if (!op->peer || !op->peer->od->nbs) {
-        return;
-    }
-
-    for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-        const struct nbrec_nat *nat = op->od->nbr->nat[i];
-        bool found = false;
-        struct eth_addr mac;
-
-        if (strcmp(nat->type, "dnat_and_snat") ||
-                !nat->external_mac ||
-                !eth_addr_from_string(nat->external_mac, &mac) ||
-                !nat->external_ip || !nat->logical_port) {
-            continue;
-        }
-
-        const struct ovn_datapath *peer_dp = op->peer->od;
-        for (size_t j = 0; j < peer_dp->nbs->n_ports; j++) {
-            if (!strcmp(peer_dp->nbs->ports[j]->name, nat->logical_port)) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            continue;
-        }
-
-        /* Determine if we need to create IPv4 or IPv6 flows */
-        ovs_be32 ip;
-        struct in6_addr ipv6;
-        int family = AF_INET;
-        if (!ip_parse(nat->external_ip, &ip) || !ip) {
-            family = AF_INET6;
-            if (!ipv6_parse(nat->external_ip, &ipv6)) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
-                             "for router %s", nat->external_ip, op->key);
-                /* We'll create IPv6 flows anyway, but the address
-                 * is probably bogus ... */
-            }
-        }
-
-        ds_put_format(&match, "inport == %s && "
-                      "ip%s.src == %s && ip%s.dst == %s",
-                       op->json_key,
-                       family == AF_INET ? "4" : "6",
-                       nat->logical_ip,
-                       family == AF_INET ? "4" : "6",
-                       nat->external_ip);
-        ds_put_format(&actions, "outport = %s; eth.dst = %s; "
-                      REGBIT_DISTRIBUTED_NAT" = 1; "
-                      REGBIT_NAT_REDIRECT" = 0; next;",
-                      op->od->l3dgw_port->json_key,
-                      nat->external_mac);
-        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400,
-                      ds_cstr(&match), ds_cstr(&actions));
-        ds_clear(&match);
-        ds_clear(&actions);
-
-        for (size_t j = 0; j < op->od->nbr->n_nat; j++) {
-            const struct nbrec_nat *nat2 = op->od->nbr->nat[j];
-            struct eth_addr mac2;
-
-            if (nat == nat2 || strcmp(nat2->type, "dnat_and_snat") ||
-                    !nat2->external_mac ||
-                    !eth_addr_from_string(nat2->external_mac, &mac2) ||
-                    !nat2->external_ip) {
-                continue;
-            }
-
-            family = AF_INET;
-            if (!ip_parse(nat2->external_ip, &ip) || !ip) {
-                family = AF_INET6;
-                if (!ipv6_parse(nat2->external_ip, &ipv6)) {
-                    static struct vlog_rate_limit rl =
-                        VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
-                                 "for router %s", nat2->external_ip, op->key);
-                    /* We'll create IPv6 flows anyway, but the address
-                     * is probably bogus ... */
-                }
-            }
-
-            ds_put_format(&match, "inport == %s && "
-                          "ip%s.src == %s && ip%s.dst == %s",
-                          op->json_key,
-                          family == AF_INET ? "4" : "6",
-                          nat->logical_ip,
-                          family == AF_INET ? "4" : "6",
-                          nat2->external_ip);
-            ds_put_format(&actions, "outport = %s; "
-                          "eth.src = %s; eth.dst = %s; "
-                          "%sreg0 = ip%s.dst; %sreg1 = %s; "
-                          REGBIT_DISTRIBUTED_NAT" = 1; "
-                          REGBIT_NAT_REDIRECT" = 0; next;",
-                          op->od->l3dgw_port->json_key,
-                          op->od->l3dgw_port->lrp_networks.ea_s,
-                          nat2->external_mac,
-                          family == AF_INET ? "" : "xx",
-                          family == AF_INET ? "4" : "6",
-                          family == AF_INET ? "" : "xx",
-                          nat->external_ip);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400,
-                          ds_cstr(&match), ds_cstr(&actions));
-            ds_clear(&match);
-            ds_clear(&actions);
-        }
-    }
-    ds_destroy(&match);
-    ds_destroy(&actions);
-}
-
 static void
 add_route(struct hmap *lflows, const struct ovn_port *op,
           const char *lrp_addr_s, const char *network_s, int plen,
@@ -8144,17 +8021,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
 
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
                                   ds_cstr(&match), ds_cstr(&actions));
-
-                    /* Traffic received on other router ports must be
-                     * redirected to the central instance of the l3dgw_port
-                     * for NAT processing. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip%s.dst == %s",
-                                  is_v6 ? "6" : "4",
-                                  nat->external_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 50,
-                                  ds_cstr(&match),
-                                  REGBIT_NAT_REDIRECT" = 1; next;");
                 }
             }
 
@@ -8220,18 +8086,33 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
 
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
                                   ds_cstr(&match), ds_cstr(&actions));
+                }
+            }
 
-                    /* Traffic received on other router ports must be
-                     * redirected to the central instance of the l3dgw_port
-                     * for NAT processing. */
+            /* ARP resolve for NAT IPs. */
+            if (od->l3dgw_port) {
+                if (!strcmp(nat->type, "snat")) {
                     ds_clear(&match);
-                    ds_put_format(&match, "ip && ip%s.dst == %s",
-                                  is_v6 ? "6" : "4",
-                                  nat->external_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-                                  ds_cstr(&match),
-                                  REGBIT_NAT_REDIRECT" = 1; next;");
+                    ds_put_format(
+                        &match, "inport == %s && %s == %s",
+                        od->l3dgw_port->json_key,
+                        is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
+                    ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 120,
+                                  ds_cstr(&match), "next;");
                 }
+
+                ds_clear(&match);
+                ds_put_format(
+                    &match, "outport == %s && %s == %s",
+                    od->l3dgw_port->json_key,
+                    is_v6 ? "xxreg0" : "reg0", nat->external_ip);
+                ds_clear(&actions);
+                ds_put_format(
+                    &actions, "eth.dst = %s; next;",
+                    distributed ? nat->external_mac :
+                    od->l3dgw_port->lrp_networks.ea_s);
+                ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 100,
+                                ds_cstr(&match), ds_cstr(&actions));
             }
 
             /* Egress UNDNAT table: It is for already established connections'
@@ -8378,49 +8259,19 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
              * ingress pipeline with inport = outport. */
             if (od->l3dgw_port) {
                 /* Distributed router. */
-                if (!strcmp(nat->type, "dnat_and_snat") &&
-                        nat->external_mac && nat->external_ip &&
-                        eth_addr_from_string(nat->external_mac, &mac)) {
-                    for (int j = 0; j < od->nbr->n_nat; j++) {
-                        const struct nbrec_nat *nat2 = od->nbr->nat[j];
-
-                        if (nat2 == nat ||
-                            strcmp(nat2->type, "dnat_and_snat") ||
-                            !nat2->external_mac || !nat2->external_ip) {
-                            continue;
-                        }
-
-                        ds_clear(&match);
-                        ds_put_format(&match, "is_chassis_resident(\"%s\") && "
-                                      "ip%s.src == %s && ip%s.dst == %s",
-                                      nat->logical_port,
-                                      is_v6 ? "6" : "4", nat2->external_ip,
-                                      is_v6 ? "6" : "4", nat->external_ip);
-                        ds_clear(&actions);
-                        ds_put_format(&actions,
-                                      "inport = outport; outport = \"\"; "
-                                      "flags = 0; flags.loopback = 1; "
-                                      REGBIT_EGRESS_LOOPBACK" = 1; "
-                                      "next(pipeline=ingress, table=0); ");
-                        ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 300,
-                                      ds_cstr(&match),  ds_cstr(&actions));
-
-                        ds_clear(&match);
-                        ds_put_format(&match,
-                                      "ip%s.src == %s && ip%s.dst == %s",
-                                      is_v6 ? "6" : "4", nat2->external_ip,
-                                      is_v6 ? "6" : "4", nat->external_ip);
-                        ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 200,
-                                      ds_cstr(&match), "next;");
-                        ds_clear(&match);
-                    }
-                }
-
                 ds_clear(&match);
                 ds_put_format(&match, "ip%s.dst == %s && outport == %s",
                               is_v6 ? "6" : "4",
                               nat->external_ip,
                               od->l3dgw_port->json_key);
+                if (!distributed) {
+                    ds_put_format(&match, " && is_chassis_resident(%s)",
+                                  od->l3redirect_port->json_key);
+                } else {
+                    ds_put_format(&match, " && is_chassis_resident(\"%s\")",
+                                  nat->logical_port);
+                }
+
                 ds_clear(&actions);
                 ds_put_format(&actions,
                               "clone { ct_clear; "
@@ -8491,40 +8342,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             * we can do it here, saving a future re-circulation. */
             ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
                           "ip", "flags.loopback = 1; ct_dnat;");
-        } else {
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 400,
-                          REGBIT_DISTRIBUTED_NAT" == 1", "next;");
-
-            /* For NAT on a distributed router, add flows to Ingress
-             * IP Routing table, Ingress ARP Resolution table, and
-             * Ingress Gateway Redirect Table that are not specific to a
-             * NAT rule. */
-
-            /* The highest priority IN_IP_ROUTING rule matches packets
-             * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages),
-             * with action "ip.ttl--; next;".  The IN_GW_REDIRECT table
-             * will take care of setting the outport. */
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 300,
-                          REGBIT_NAT_REDIRECT" == 1", "ip.ttl--; next;");
-
-            /* The highest priority IN_ARP_RESOLVE rule matches packets
-             * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages),
-             * then sets eth.dst to the distributed gateway port's
-             * ethernet address. */
-            ds_clear(&actions);
-            ds_put_format(&actions, "eth.dst = %s; next;",
-                          od->l3dgw_port->lrp_networks.ea_s);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 200,
-                          REGBIT_NAT_REDIRECT" == 1", ds_cstr(&actions));
-
-            /* The highest priority IN_GW_REDIRECT rule redirects packets
-             * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages) to
-             * the central instance of the l3dgw_port for NAT processing. */
-            ds_clear(&actions);
-            ds_put_format(&actions, "outport = %s; next;",
-                          od->l3redirect_port->json_key);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 200,
-                          REGBIT_NAT_REDIRECT" == 1", ds_cstr(&actions));
         }
 
         /* Load balancing and packet defrag are only valid on
@@ -8720,9 +8537,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
-        /* create logical flows for DVR floating IPs */
-        add_distributed_nat_routes(lflows, op);
-
         for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
             add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
                       op->lrp_networks.ipv4_addrs[i].network_s,
@@ -9252,9 +9066,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
         if (od->l3dgw_port && od->l3redirect_port) {
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 300,
-                          REGBIT_DISTRIBUTED_NAT" == 1", "next;");
-
             /* For traffic with outport == l3dgw_port, if the
              * packet did not match any higher priority redirect
              * rule, then the traffic is redirected to the central
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index da566f900..3e4120ac5 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -990,7 +990,7 @@ echo "CR-LRP UUID is: " $uuid
 # IPV4
 ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
 
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
 wc -l`])
 
 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
@@ -1008,7 +1008,7 @@ AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0
 ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
 
 ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
 wc -l`])
 
 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0
@@ -1027,7 +1027,7 @@ ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
 # IPV6
 ovn-nbctl lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
 
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
 wc -l`])
 
 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
@@ -1045,7 +1045,7 @@ AT_CHECK([ovn-sbctl dump-flows R1 | grep ip6.src=| wc -l], [0], [0
 ovn-nbctl lr-nat-del R1 dnat_and_snat  fd01::1
 ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
 
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \
 wc -l`])
 
 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0
-- 
2.24.1