diff mbox series

[ovs-dev,v4,3/3] northd: add check_pkt_larger lflows for ingress traffic

Message ID 41e871c07e4abf9bb06c9dde33a2590cfa398ed8.1624436738.git.lorenzo.bianconi@redhat.com
State Superseded
Headers show
Series Introduce check_pkt_larger for ingress traffic | expand

Commit Message

Lorenzo Bianconi June 23, 2021, 8:58 a.m. UTC
Introduce check_pkt_larger action for ingress traffic
entering the cluster from a distributed gw router port
or from a gw router. This patch enables pMTU discovery
for ingress traffic.

Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 northd/ovn-northd.8.xml |  60 +++++++++++++--
 northd/ovn-northd.c     | 166 ++++++++++++++++++++++++----------------
 northd/ovn_northd.dl    | 153 ++++++++++++++++++++++++++++++++----
 tests/ovn-northd.at     |  40 +++++-----
 tests/ovn.at            | 137 +++++++++++++++++++++++++++++++--
 5 files changed, 446 insertions(+), 110 deletions(-)
diff mbox series

Patch

diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index aff189ad5..bfb048710 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1923,6 +1923,15 @@  output;
           <code>eth.dst == <var>E</var></code> is only programmed on
           the gateway port instance on the gateway chassis.
         </p>
+
+        <p>
+          For a distributed logical router or for gateway router where
+          the port is configured with <code>options:gateway_mtu</code>
+          the action of the above flow is modified adding
+          <code>check_pkt_larger</code> in order to mark the packet
+          setting <code>REGBIT_PKT_LARGER</code> if the size is greater
+          than the MTU
+        </p>
       </li>
 
       <li>
@@ -2147,6 +2156,46 @@  next;
     </p>
 
     <ul>
+      <li>
+        <p>
+          For distributed logical routers or gateway routers with gateway port
+          configured with <code>options:gateway_mtu</code> to a valid integer
+          value, a priority-150 flow with the match <code>inport ==
+          <var>LRP</var> &amp;&amp; REGBIT_PKT_LARGER &amp;&amp;
+          REGBIT_EGRESS_LOOPBACK == 0</code>, where <var>LRP</var> is the
+          logical router port and applies the following action for ipv4
+          and ipv6 respectively:
+        </p>
+
+    <pre>
+icmp4 {
+    icmp4.type = 3; /* Destination Unreachable. */
+    icmp4.code = 4;  /* Frag Needed and DF was Set. */
+    icmp4.frag_mtu = <var>M</var>;
+    eth.dst = <var>E</var>;
+    ip4.dst = ip4.src;
+    ip4.src = <var>I</var>;
+    ip.ttl = 255;
+    REGBIT_EGRESS_LOOPBACK = 1;
+    REGBIT_PKT_LARGER 0;
+    next(pipeline=ingress, table=0);
+};
+
+icmp6 {
+    icmp6.type = 2;
+    icmp6.code = 0;
+    icmp6.frag_mtu = <var>M</var>;
+    eth.dst = <var>E</var>;
+    ip6.dst = ip6.src;
+    ip6.src = <var>I</var>;
+    ip.ttl = 255;
+    REGBIT_EGRESS_LOOPBACK = 1;
+    REGBIT_PKT_LARGER 0;
+    next(pipeline=ingress, table=0);
+};
+    </pre>
+      </li>
+
       <li>
         <p>
           For each NAT entry of a distributed logical router  (with
@@ -3631,12 +3680,11 @@  REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next;
     <p>
       For distributed logical routers or gateway routers with gateway port
       configured with <code>options:gateway_mtu</code> to a valid integer
-      value, this table adds the following priority-50 logical flow for each
+      value, this table adds the following priority-150 logical flow for each
       logical router port with the match <code>inport == <var>LRP</var>
-      &amp;&amp; outport == <var>GW_PORT</var> &amp;&amp;
-      REGBIT_PKT_LARGER</code>, where <var>LRP</var> is the logical
-      router port and <var>GW_PORT</var> is the gateway router port and applies
-      the following action for ipv4 and ipv6 respectively:
+      &amp;&amp; REGBIT_PKT_LARGER &amp;&amp; !REGBIT_EGRESS_LOOPBACK</code>,
+      where <var>LRP</var> is the logical router port and applies the following
+      action for ipv4 and ipv6 respectively:
     </p>
 
     <pre>
@@ -3649,6 +3697,7 @@  icmp4 {
     ip4.src = <var>I</var>;
     ip.ttl = 255;
     REGBIT_EGRESS_LOOPBACK = 1;
+    REGBIT_PKT_LARGER = 0;
     next(pipeline=ingress, table=0);
 };
 
@@ -3661,6 +3710,7 @@  icmp6 {
     ip6.src = <var>I</var>;
     ip.ttl = 255;
     REGBIT_EGRESS_LOOPBACK = 1;
+    REGBIT_PKT_LARGER = 0;
     next(pipeline=ingress, table=0);
 };
     </pre>
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index f0eb715bf..8c76508cf 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -9509,6 +9509,10 @@  build_adm_ctrl_flows_for_lrouter(
     }
 }
 
+static void
+build_check_pkt_len_action_string(struct ovn_port *op, int *pmtu,
+                                  struct ds *actions);
+
 /* Logical router ingress Table 0: L2 Admission Control
  * This table drops packets that the router shouldn’t see at all based
  * on their Ethernet headers.
@@ -9536,6 +9540,8 @@  build_adm_ctrl_flows_for_lrouter_port(
          * the pipeline.
          */
         ds_clear(actions);
+
+        build_check_pkt_len_action_string(op, NULL, actions);
         ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
                       op->lrp_networks.ea_s);
 
@@ -10430,32 +10436,110 @@  build_arp_resolve_flows_for_lrouter_port(
 
 }
 
+static void
+build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
+                            struct ds *match, struct ds *actions,
+                            enum ovn_stage stage)
+{
+    if (op->lrp_networks.ipv4_addrs) {
+        ds_clear(match);
+        ds_put_format(match,
+                      "inport == %s && ip4 && "REGBIT_PKT_LARGER
+                      " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key);
+
+        ds_clear(actions);
+        /* Set icmp4.frag_mtu to gw_mtu */
+        ds_put_format(actions,
+            "icmp4_error {"
+            REGBIT_EGRESS_LOOPBACK" = 1; "
+            REGBIT_PKT_LARGER" = 0; "
+            "eth.dst = %s; "
+            "ip4.dst = ip4.src; "
+            "ip4.src = %s; "
+            "ip.ttl = 255; "
+            "icmp4.type = 3; /* Destination Unreachable. */ "
+            "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
+            "icmp4.frag_mtu = %d; "
+            "next(pipeline=ingress, table=%d); };",
+            op->lrp_networks.ea_s,
+            op->lrp_networks.ipv4_addrs[0].addr_s,
+            mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
+        ovn_lflow_add_with_hint(lflows, op->od, stage, 150,
+                                ds_cstr(match), ds_cstr(actions),
+                                &op->nbrp->header_);
+    }
+
+    if (op->lrp_networks.ipv6_addrs) {
+        ds_clear(match);
+        ds_put_format(match, "inport == %s && ip6 && "REGBIT_PKT_LARGER
+                      " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key);
+
+        ds_clear(actions);
+        /* Set icmp6.frag_mtu to gw_mtu */
+        ds_put_format(actions,
+            "icmp6_error {"
+            REGBIT_EGRESS_LOOPBACK" = 1; "
+            REGBIT_PKT_LARGER" = 0; "
+            "eth.dst = %s; "
+            "ip6.dst = ip6.src; "
+            "ip6.src = %s; "
+            "ip.ttl = 255; "
+            "icmp6.type = 2; /* Packet Too Big. */ "
+            "icmp6.code = 0; "
+            "icmp6.frag_mtu = %d; "
+            "next(pipeline=ingress, table=%d); };",
+            op->lrp_networks.ea_s,
+            op->lrp_networks.ipv6_addrs[0].addr_s,
+            mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
+        ovn_lflow_add_with_hint(lflows, op->od, stage, 150,
+                                ds_cstr(match), ds_cstr(actions),
+                                &op->nbrp->header_);
+    }
+}
+
+static void
+build_check_pkt_len_action_string(struct ovn_port *op, int *pmtu,
+                                  struct ds *actions)
+{
+    int gw_mtu = smap_get_int(&op->nbrp->options, "gateway_mtu", 0);
+
+    if (gw_mtu > 0) {
+        /* Add the flows only if gateway_mtu is configured. */
+        ds_put_format(actions,
+                      REGBIT_PKT_LARGER" = check_pkt_larger(%d); ",
+                      gw_mtu + VLAN_ETH_HEADER_LEN);
+    }
+    if (pmtu) {
+        *pmtu = gw_mtu;
+    }
+}
+
 static void
 build_check_pkt_len_flows_for_lrp(struct ovn_port *op,
                                   struct hmap *lflows, struct hmap *ports,
                                   struct ds *match, struct ds *actions)
 {
-    int gw_mtu = 0;
+    int gw_mtu;
 
-    if (op->nbrp) {
-         gw_mtu = smap_get_int(&op->nbrp->options, "gateway_mtu", 0);
-    }
-    /* Add the flows only if gateway_mtu is configured. */
+    ds_clear(actions);
+    build_check_pkt_len_action_string(op, &gw_mtu, actions);
     if (gw_mtu <= 0) {
         return;
     }
 
+    ds_put_format(actions, "next;");
+
     ds_clear(match);
     ds_put_format(match, "outport == %s", op->json_key);
 
-    ds_clear(actions);
-    ds_put_format(actions,
-                  REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
-                  " next;", gw_mtu + VLAN_ETH_HEADER_LEN);
     ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_CHK_PKT_LEN, 50,
                             ds_cstr(match), ds_cstr(actions),
                             &op->nbrp->header_);
 
+    /* ingress traffic */
+    build_icmperr_pkt_big_flows(op, gw_mtu, lflows, match, actions,
+                                S_ROUTER_IN_IP_INPUT);
+
     for (size_t i = 0; i < op->od->nbr->n_ports; i++) {
         struct ovn_port *rp = ovn_port_find(ports,
                                             op->od->nbr->ports[i]->name);
@@ -10463,63 +10547,9 @@  build_check_pkt_len_flows_for_lrp(struct ovn_port *op,
             continue;
         }
 
-        if (rp->lrp_networks.ipv4_addrs) {
-            ds_clear(match);
-            ds_put_format(match, "inport == %s && outport == %s"
-                          " && ip4 && "REGBIT_PKT_LARGER,
-                          rp->json_key, op->json_key);
-
-            ds_clear(actions);
-            /* Set icmp4.frag_mtu to gw_mtu */
-            ds_put_format(actions,
-                "icmp4_error {"
-                REGBIT_EGRESS_LOOPBACK" = 1; "
-                "eth.dst = %s; "
-                "ip4.dst = ip4.src; "
-                "ip4.src = %s; "
-                "ip.ttl = 255; "
-                "icmp4.type = 3; /* Destination Unreachable. */ "
-                "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-                "icmp4.frag_mtu = %d; "
-                "next(pipeline=ingress, table=%d); };",
-                rp->lrp_networks.ea_s,
-                rp->lrp_networks.ipv4_addrs[0].addr_s,
-                gw_mtu,
-                ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-            ovn_lflow_add_with_hint(lflows, op->od,
-                                    S_ROUTER_IN_LARGER_PKTS, 50,
-                                    ds_cstr(match), ds_cstr(actions),
-                                    &rp->nbrp->header_);
-        }
-
-        if (rp->lrp_networks.ipv6_addrs) {
-            ds_clear(match);
-            ds_put_format(match, "inport == %s && outport == %s"
-                          " && ip6 && "REGBIT_PKT_LARGER,
-                          rp->json_key, op->json_key);
-
-            ds_clear(actions);
-            /* Set icmp6.frag_mtu to gw_mtu */
-            ds_put_format(actions,
-                "icmp6_error {"
-                REGBIT_EGRESS_LOOPBACK" = 1; "
-                "eth.dst = %s; "
-                "ip6.dst = ip6.src; "
-                "ip6.src = %s; "
-                "ip.ttl = 255; "
-                "icmp6.type = 2; /* Packet Too Big. */ "
-                "icmp6.code = 0; "
-                "icmp6.frag_mtu = %d; "
-                "next(pipeline=ingress, table=%d); };",
-                rp->lrp_networks.ea_s,
-                rp->lrp_networks.ipv6_addrs[0].addr_s,
-                gw_mtu,
-                ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-            ovn_lflow_add_with_hint(lflows, op->od,
-                                    S_ROUTER_IN_LARGER_PKTS, 50,
-                                    ds_cstr(match), ds_cstr(actions),
-                                    &rp->nbrp->header_);
-        }
+        /* egress traffic */
+        build_icmperr_pkt_big_flows(rp, gw_mtu, lflows, match, actions,
+                                    S_ROUTER_IN_LARGER_PKTS);
     }
 }
 
@@ -11579,8 +11609,10 @@  build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
         * down in the pipeline.
         */
         ds_clear(actions);
+
+        build_check_pkt_len_action_string(od->l3dgw_port, NULL, actions);
         ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-                    od->l3dgw_port->lrp_networks.ea_s);
+                      od->l3dgw_port->lrp_networks.ea_s);
 
         ds_clear(match);
         ds_put_format(match,
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 0b704b524..e0a468469 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -4474,7 +4474,12 @@  for (&RouterPort(.lrp = lrp,
      * This will save us from having to match on inport further down in
      * the pipeline.
      */
-    var actions = "${rEG_INPORT_ETH_ADDR()} = ${lrp_networks.ea}; next;" in {
+    var gw_mtu = lrp.options.get_int_def("gateway_mtu", 0) in
+    var mtu = gw_mtu + vLAN_ETH_HEADER_LEN() in
+    var actions = "${rEG_INPORT_ETH_ADDR()} = ${lrp_networks.ea}; " ++
+    if (gw_mtu > 0) {
+        "${rEGBIT_PKT_LARGER()} = check_pkt_larger(${mtu}); next;"
+    } else { "next;" } in {
         Flow(.logical_datapath = router._uuid,
              .stage            = s_ROUTER_IN_ADMISSION(),
              .priority         = 50,
@@ -7166,11 +7171,12 @@  Flow(.logical_datapath = lr_uuid,
     var mtu = gw_mtu + vLAN_ETH_HEADER_LEN().
 Flow(.logical_datapath = lr_uuid,
      .stage            = s_ROUTER_IN_LARGER_PKTS(),
-     .priority         = 50,
-     .__match          = "inport == ${rp.json_name} && outport == ${l3dgw_port_json_name} && "
-                         "ip4 && ${rEGBIT_PKT_LARGER()}",
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip4 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
      .actions          = "icmp4_error {"
                          "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
                          "eth.dst = ${rp.networks.ea}; "
                          "ip4.dst = ip4.src; "
                          "ip4.src = ${first_ipv4.addr}; "
@@ -7191,13 +7197,44 @@  Flow(.logical_datapath = lr_uuid,
     rp in &RouterPort(.router = r),
     rp.lrp != l3dgw_port,
     Some{var first_ipv4} = rp.networks.ipv4_addrs.nth(0).
+
+Flow(.logical_datapath = lr_uuid,
+     .stage            = s_ROUTER_IN_IP_INPUT(),
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip4 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
+     .actions          = "icmp4_error {"
+                         "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
+                         "eth.dst = ${rp.networks.ea}; "
+                         "ip4.dst = ip4.src; "
+                         "ip4.src = ${first_ipv4.addr}; "
+                         "ip.ttl = 255; "
+                         "icmp4.type = 3; /* Destination Unreachable. */ "
+                         "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
+                         /* Set icmp4.frag_mtu to gw_mtu */
+                         "icmp4.frag_mtu = ${gw_mtu}; "
+                         "next(pipeline=ingress, table=0); "
+                         "};",
+     .external_ids     = stage_hint(rp.lrp._uuid)) :-
+    r in &Router(._uuid = lr_uuid),
+    Some{var l3dgw_port} = r.l3dgw_port,
+    var l3dgw_port_json_name = json_string_escape(l3dgw_port.name),
+    r.redirect_port_name != "",
+    var gw_mtu = l3dgw_port.options.get_int_def("gateway_mtu", 0),
+    gw_mtu > 0,
+    rp in &RouterPort(.router = r),
+    rp.lrp == l3dgw_port,
+    Some{var first_ipv4} = rp.networks.ipv4_addrs.nth(0).
+
 Flow(.logical_datapath = lr_uuid,
      .stage            = s_ROUTER_IN_LARGER_PKTS(),
-     .priority         = 50,
-     .__match          = "inport == ${rp.json_name} && outport == ${l3dgw_port_json_name} && "
-                         "ip6 && ${rEGBIT_PKT_LARGER()}",
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip6 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
      .actions          = "icmp6_error {"
                          "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
                          "eth.dst = ${rp.networks.ea}; "
                          "ip6.dst = ip6.src; "
                          "ip6.src = ${first_ipv6.addr}; "
@@ -7219,6 +7256,35 @@  Flow(.logical_datapath = lr_uuid,
     rp.lrp != l3dgw_port,
     Some{var first_ipv6} = rp.networks.ipv6_addrs.nth(0).
 
+Flow(.logical_datapath = lr_uuid,
+     .stage            = s_ROUTER_IN_IP_INPUT(),
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip6 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
+     .actions          = "icmp6_error {"
+                         "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
+                         "eth.dst = ${rp.networks.ea}; "
+                         "ip6.dst = ip6.src; "
+                         "ip6.src = ${first_ipv6.addr}; "
+                         "ip.ttl = 255; "
+                         "icmp6.type = 2; /* Packet Too Big. */ "
+                         "icmp6.code = 0; "
+                         /* Set icmp6.frag_mtu to gw_mtu */
+                         "icmp6.frag_mtu = ${gw_mtu}; "
+                         "next(pipeline=ingress, table=0); "
+                         "};",
+     .external_ids     = stage_hint(rp.lrp._uuid)) :-
+    r in &Router(._uuid = lr_uuid),
+    Some{var l3dgw_port} = r.l3dgw_port,
+    var l3dgw_port_json_name = json_string_escape(l3dgw_port.name),
+    r.redirect_port_name != "",
+    var gw_mtu = l3dgw_port.options.get_int_def("gateway_mtu", 0),
+    gw_mtu > 0,
+    rp in &RouterPort(.router = r),
+    rp.lrp == l3dgw_port,
+    Some{var first_ipv6} = rp.networks.ipv6_addrs.nth(0).
+
 /* Gateway routers. */
 Flow(.logical_datapath = lr_uuid,
      .stage            = s_ROUTER_IN_CHK_PKT_LEN(),
@@ -7235,11 +7301,12 @@  Flow(.logical_datapath = lr_uuid,
     var mtu = gw_mtu + vLAN_ETH_HEADER_LEN().
 Flow(.logical_datapath = lr_uuid,
      .stage            = s_ROUTER_IN_LARGER_PKTS(),
-     .priority         = 50,
-     .__match          = "inport == ${rp.json_name} && outport == ${gw_mtu_rp.json_name} && "
-                         "ip4 && ${rEGBIT_PKT_LARGER()}",
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip4 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
      .actions          = "icmp4_error {"
                          "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
                          "eth.dst = ${rp.networks.ea}; "
                          "ip4.dst = ip4.src; "
                          "ip4.src = ${first_ipv4.addr}; "
@@ -7260,13 +7327,44 @@  Flow(.logical_datapath = lr_uuid,
     rp in &RouterPort(.router = r),
     rp.lrp != gw_mtu_rp.lrp,
     Some{var first_ipv4} = rp.networks.ipv4_addrs.nth(0).
+
+Flow(.logical_datapath = lr_uuid,
+     .stage            = s_ROUTER_IN_IP_INPUT(),
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip4 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
+     .actions          = "icmp4_error {"
+                         "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
+                         "eth.dst = ${rp.networks.ea}; "
+                         "ip4.dst = ip4.src; "
+                         "ip4.src = ${first_ipv4.addr}; "
+                         "ip.ttl = 255; "
+                         "icmp4.type = 3; /* Destination Unreachable. */ "
+                         "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
+                         /* Set icmp4.frag_mtu to gw_mtu */
+                         "icmp4.frag_mtu = ${gw_mtu}; "
+                         "next(pipeline=ingress, table=0); "
+                         "};",
+     .external_ids     = stage_hint(rp.lrp._uuid)) :-
+    r in &Router(._uuid = lr_uuid),
+    r.is_gateway,
+    gw_mtu_rp in &RouterPort(.router = r),
+    var gw_mtu = gw_mtu_rp.lrp.options.get_int_def("gateway_mtu", 0),
+    gw_mtu > 0,
+    var mtu = gw_mtu + vLAN_ETH_HEADER_LEN(),
+    rp in &RouterPort(.router = r),
+    rp.lrp == gw_mtu_rp.lrp,
+    Some{var first_ipv4} = rp.networks.ipv4_addrs.nth(0).
+
 Flow(.logical_datapath = lr_uuid,
      .stage            = s_ROUTER_IN_LARGER_PKTS(),
-     .priority         = 50,
-     .__match          = "inport == ${rp.json_name} && outport == ${gw_mtu_rp.json_name} && "
-                         "ip6 && ${rEGBIT_PKT_LARGER()}",
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip6 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
      .actions          = "icmp6_error {"
                          "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
                          "eth.dst = ${rp.networks.ea}; "
                          "ip6.dst = ip6.src; "
                          "ip6.src = ${first_ipv6.addr}; "
@@ -7288,6 +7386,35 @@  Flow(.logical_datapath = lr_uuid,
     rp.lrp != gw_mtu_rp.lrp,
     Some{var first_ipv6} = rp.networks.ipv6_addrs.nth(0).
 
+Flow(.logical_datapath = lr_uuid,
+     .stage            = s_ROUTER_IN_IP_INPUT(),
+     .priority         = 150,
+     .__match          = "inport == ${rp.json_name} && ip6 && "
+                         "${rEGBIT_PKT_LARGER()} && ${rEGBIT_EGRESS_LOOPBACK()} == 0",
+     .actions          = "icmp6_error {"
+                         "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                         "${rEGBIT_PKT_LARGER()} = 0; "
+                         "eth.dst = ${rp.networks.ea}; "
+                         "ip6.dst = ip6.src; "
+                         "ip6.src = ${first_ipv6.addr}; "
+                         "ip.ttl = 255; "
+                         "icmp6.type = 2; /* Packet Too Big. */ "
+                         "icmp6.code = 0; "
+                         /* Set icmp6.frag_mtu to gw_mtu */
+                         "icmp6.frag_mtu = ${gw_mtu}; "
+                         "next(pipeline=ingress, table=0); "
+                         "};",
+     .external_ids     = stage_hint(rp.lrp._uuid)) :-
+    r in &Router(._uuid = lr_uuid),
+    r.is_gateway,
+    gw_mtu_rp in &RouterPort(.router = r),
+    var gw_mtu = gw_mtu_rp.lrp.options.get_int_def("gateway_mtu", 0),
+    gw_mtu > 0,
+    var mtu = gw_mtu + vLAN_ETH_HEADER_LEN(),
+    rp in &RouterPort(.router = r),
+    rp.lrp == gw_mtu_rp.lrp,
+    Some{var first_ipv6} = rp.networks.ipv6_addrs.nth(0).
+
 /* Logical router ingress table GW_REDIRECT: Gateway redirect.
  *
  * For traffic with outport equal to the l3dgw_port
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 66894b172..e0f3bd635 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -3788,10 +3788,10 @@  AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sort], [0], [d
   table=15(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
   table=15(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); next;)
   table=16(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
 # Clear the gateway-chassis for lr0-public
@@ -3815,10 +3815,10 @@  AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sort], [0], [d
   table=15(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
   table=15(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); next;)
   table=16(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
 # Set gateway_mtu option on lr0-sw0
@@ -3832,14 +3832,14 @@  AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sort], [0], [d
   table=15(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); next;)
   table=15(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), action=(reg9[[1]] = check_pkt_larger(1418); next;)
   table=16(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
 # Clear gateway_mtu option on lr0-public
@@ -3851,10 +3851,10 @@  AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sort], [0], [d
   table=15(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
   table=15(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), action=(reg9[[1]] = check_pkt_larger(1418); next;)
   table=16(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]]), action=(icmp4_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
-  table=16(lr_in_larger_pkts  ), priority=50   , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]]), action=(icmp6_error {reg9[[0]] = 1; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=16(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
 ])
 
 AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
index 16897507d..f8bb579bb 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16319,6 +16319,52 @@  test_ip_packet_larger() {
     fi
 }
 
+test_ip_packet_larger_ext() {
+    local mtu=$1
+
+    # Send ip packet from sw0-port1 to outside
+    src_mac="00000012af11" # external mac
+    dst_mac="000020201213" # lr0-public mac
+    src_ip=`ip_to_hex 172 168 0 4`
+    dst_ip=`ip_to_hex 172 168 0 100`
+    # Set the packet length to 118.
+    pkt_len=0076
+    packet=${dst_mac}${src_mac}08004500${pkt_len}00000000400120cf
+    orig_packet_l3=${src_ip}${dst_ip}0900000000000000
+    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+    packet=${packet}${orig_packet_l3}
+
+    gw_ip_garp=ffffffffffff00002020121308060001080006040001000020201213aca80064000000000000aca80064
+    ext_ip_garp=ffffffffffff00000012af110806000108000604000100000012af11aca80004000000000000aca80004
+
+    src_ip=`ip_to_hex 172 168 0 100`
+    dst_ip=`ip_to_hex 172 168 0 4`
+    # pkt len should be 146 (28 (icmp packet) + 118 (orig ip + payload))
+    reply_pkt_len=0092
+    ip_csum=f397
+    icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe0122b2
+    icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000$(printf "%04x" $mtu)
+    icmp_reply=${icmp_reply}4500${pkt_len}00000000400120cf
+    icmp_reply=${icmp_reply}${orig_packet_l3}
+    echo $icmp_reply > br-phys_n1.expected
+
+    echo $gw_ip_garp >> br-phys_n1.expected
+
+    as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+    as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+
+    check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $ext_ip_garp
+    sleep 1
+    # Send packet from sw0-port1 to outside
+    check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $packet
+
+    OVN_CHECK_PACKETS([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+}
+
 test_ip6_packet_larger() {
     local mtu=$1
 
@@ -16334,7 +16380,7 @@  test_ip6_packet_larger() {
     local payload=${payload}0000000000000000000000000000000000000000
     local payload=${payload}0000000000000000000000000000000000000000
 
-    local ip6_hdr=6000000000583aff${ipv6_src}${ipv6_dst}
+    local ip6_hdr=6000000000583afe${ipv6_src}${ipv6_dst}
     local packet=${eth_dst}${eth_src}86dd${ip6_hdr}8000ec7662f00001${payload}
 
     as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
@@ -16351,11 +16397,11 @@  test_ip6_packet_larger() {
     mtu_needed=$(expr ${packet_bytes} - 18)
     if test $mtu -lt $mtu_needed; then
         # First construct the inner IPv6 packet.
-        inner_ip6=6000000000583afe${ipv6_src}${ipv6_dst}
+        inner_ip6=6000000000583afd${ipv6_src}${ipv6_dst}
         inner_icmp6=8000000062f00001
         inner_icmp6_and_payload=$(icmp6_csum_inplace ${inner_icmp6}${payload} ${inner_ip6})
         inner_packet=${inner_ip6}${inner_icmp6_and_payload}
-        
+
         # Then the outer.
         outer_ip6=6000000000883afe${ipv6_rt}${ipv6_src}
         outer_icmp6_and_payload=$(icmp6_csum_inplace 020000000000$(printf "%04x" $mtu)${inner_packet} $outer_ip6)
@@ -16373,6 +16419,53 @@  test_ip6_packet_larger() {
     fi
 }
 
+test_ip6_packet_larger_ext() {
+    local mtu=$1
+
+    local eth_src=00000012af11
+    local eth_dst=000020201213
+
+    local ipv6_src=20000000000000000000000000000004
+    local ipv6_dst=20000000000000000000000000000001
+
+    local payload=0000000000000000000000000000000000000000
+    local payload=${payload}0000000000000000000000000000000000000000
+    local payload=${payload}0000000000000000000000000000000000000000
+    local payload=${payload}0000000000000000000000000000000000000000
+
+    local ip6_hdr=6000000000583afe${ipv6_src}${ipv6_dst}
+    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}9000cc7662f00001${payload}
+
+    local ns=ffffffffffff00002020121308060001080006040001000020201213aca80064000000000000aca80064
+    echo $ns > br-phys_n1.expected
+
+    as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+    as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+
+    local na_ip6_hdr=6000000000203aff${ipv6_src}${ipv6_dst}
+    local na=${eth_dst}${eth_src}86dd${na_ip6_hdr}8800d78440000000${ipv6_src}0201${eth_src}
+    check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $na
+    sleep 1
+    check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $packet
+    AT_CAPTURE_FILE([trace-$mtu])
+
+    # First construct the inner IPv6 packet.
+    inner_ip6=6000000000583afe${ipv6_src}${ipv6_dst}
+    inner_icmp6=9000000062f00001
+    inner_icmp6_and_payload=$(icmp6_csum_inplace ${inner_icmp6}${payload} ${inner_ip6})
+    inner_packet=${inner_ip6}${inner_icmp6_and_payload}
+
+    # Then the outer.
+    outer_ip6=6000000000883afe${ipv6_dst}${ipv6_src}
+    outer_icmp6_and_payload=$(icmp6_csum_inplace 020000000000$(printf "%04x" $mtu)${inner_packet} $outer_ip6)
+    outer_packet=${outer_ip6}${outer_icmp6_and_payload}
+
+    icmp6_reply=${eth_src}${eth_dst}86dd${outer_packet}
+    echo $icmp6_reply >> br-phys_n1.expected
+
+    OVN_CHECK_PACKETS([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+}
+
 wait_for_ports_up
 ovn-nbctl --wait=hv sync
 
@@ -16405,7 +16498,7 @@  for mtu in 100 500 118; do
     OVS_WAIT_FOR_OUTPUT([
         as hv1 ovs-ofctl dump-flows br-int > br-int-flows-$mtu
         AT_CAPTURE_FILE([br-int-flows-$mtu])
-        grep "check_pkt_larger($(expr $mtu + 18))" br-int-flows-$mtu | wc -l], [0], [1
+        grep "check_pkt_larger($(expr $mtu + 18))" br-int-flows-$mtu | wc -l], [0], [3
 ])
 
     AS_BOX([testing mtu $mtu - IPv4])
@@ -16415,6 +16508,23 @@  for mtu in 100 500 118; do
     test_ip6_packet_larger $mtu
 done
 
+AS_BOX([testing mtu $mtu])
+check ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=100
+ovn-sbctl dump-flows > ext-sbflows-100
+AT_CAPTURE_FILE([ext-sbflows-$mtu])
+
+OVS_WAIT_FOR_OUTPUT([
+    as hv1 ovs-ofctl dump-flows br-int > ext-br-int-flows-100
+    AT_CAPTURE_FILE([ext-br-int-flows-100])
+    grep "check_pkt_larger(118)" ext-br-int-flows-100 | wc -l], [0], [3
+])
+
+AS_BOX([testing ext mtu 100 - IPv4])
+test_ip_packet_larger_ext 100
+
+AS_BOX([testing mtu 100 - IPv6])
+test_ip6_packet_larger_ext 100
+
 ovn-nbctl lsp-del sw0-lr0
 
 ovn-nbctl lr-del lr0
@@ -16452,7 +16562,7 @@  for mtu in 100 500 118; do
     OVS_WAIT_FOR_OUTPUT([
         as hv1 ovs-ofctl dump-flows br-int > br-int-gw-flows-$mtu
         AT_CAPTURE_FILE([br-int-gw-flows-$mtu])
-        grep "check_pkt_larger($(expr $mtu + 18))" br-int-gw-flows-$mtu | wc -l], [0], [1
+        grep "check_pkt_larger($(expr $mtu + 18))" br-int-gw-flows-$mtu | wc -l], [0], [3
 ])
 
     AS_BOX([testing gw mtu $mtu - IPv4])
@@ -16462,6 +16572,23 @@  for mtu in 100 500 118; do
     test_ip6_packet_larger $mtu
 done
 
+AS_BOX([testing gw mtu $mtu])
+check ovn-nbctl --wait=hv set logical_router_port lr1-public options:gateway_mtu=100
+ovn-sbctl dump-flows > ext-gw-sbflows-100
+AT_CAPTURE_FILE([ext-gw-sbflows-$mtu])
+
+OVS_WAIT_FOR_OUTPUT([
+    as hv1 ovs-ofctl dump-flows br-int > ext-br-int-gw-flows-100
+    AT_CAPTURE_FILE([ext-br-int-gw-flows-100])
+    grep "check_pkt_larger(118)" ext-br-int-gw-flows-100 | wc -l], [0], [3
+])
+
+AS_BOX([testing gw ext mtu 100 - IPv4])
+test_ip_packet_larger_ext 100
+
+AS_BOX([testing gw mtu 100 - IPv6])
+test_ip6_packet_larger_ext 100
+
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])