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 <[email protected]>
---
 northd/ovn-northd.8.xml |  60 +++++++++++--
 northd/ovn-northd.c     | 184 +++++++++++++++++++++++-----------------
 northd/ovn_northd.dl    | 157 +++++++++++++++++++++++++++++++---
 tests/ovn-northd.at     |  40 ++++-----
 tests/ovn.at            | 135 ++++++++++++++++++++++++++++-
 5 files changed, 458 insertions(+), 118 deletions(-)

diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 5f2b43cd1..290de7540 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1940,6 +1940,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>
@@ -2164,6 +2173,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
@@ -3705,12 +3754,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>
@@ -3723,6 +3771,7 @@ icmp4 {
     ip4.src = <var>I</var>;
     ip.ttl = 255;
     REGBIT_EGRESS_LOOPBACK = 1;
+    REGBIT_PKT_LARGER = 0;
     next(pipeline=ingress, table=0);
 };
 
@@ -3735,6 +3784,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 20f8d7b73..9d6f52c2e 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -9952,6 +9952,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.
@@ -9979,6 +9983,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);
 
@@ -10914,33 +10920,121 @@ build_arp_resolve_flows_for_lrouter_port(
 
 }
 
+static void
+build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
+                            struct shash *meter_groups, 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),
+                                  NULL,
+                                  copp_meter_get(
+                                        COPP_ICMP4_ERR,
+                                        op->od->nbr->copp,
+                                        meter_groups),
+                                  &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),
+                                  NULL,
+                                  copp_meter_get(
+                                        COPP_ICMP6_ERR,
+                                        op->od->nbr->copp,
+                                        meter_groups),
+                                  &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 shash *meter_groups, 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, meter_groups,
+                                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);
@@ -10948,73 +11042,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),
-                                      NULL,
-                                      copp_meter_get(
-                                            COPP_ICMP4_ERR,
-                                            rp->od->nbr->copp,
-                                            meter_groups),
-                                      &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),
-                                      NULL,
-                                      copp_meter_get(
-                                            COPP_ICMP6_ERR,
-                                            rp->od->nbr->copp,
-                                            meter_groups),
-                                      &rp->nbrp->header_);
-        }
+        /* egress traffic */
+        build_icmperr_pkt_big_flows(rp, gw_mtu, lflows, meter_groups,
+                                    match, actions, S_ROUTER_IN_LARGER_PKTS);
     }
 }
 
@@ -12121,6 +12151,8 @@ 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);
 
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 4c7bf386a..63553be30 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -4728,7 +4728,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,
@@ -7558,11 +7563,12 @@ Flow(.logical_datapath = lr_uuid,
     var mtu = gw_mtu + vLAN_ETH_HEADER_LEN().
 MeteredFlow(.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}; "
@@ -7585,13 +7591,46 @@ MeteredFlow(.logical_datapath = lr_uuid,
     rp in &RouterPort(.router = r),
     rp.lrp != l3dgw_port,
     Some{var first_ipv4} = rp.networks.ipv4_addrs.nth(0).
+
+MeteredFlow(.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); "
+                                "};",
+            .tags             = map_empty(),
+            .controller_meter = r.copp.get(cOPP_ICMP4_ERR()),
+            .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).
+
 MeteredFlow(.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}; "
@@ -7615,6 +7654,37 @@ MeteredFlow(.logical_datapath = lr_uuid,
     rp.lrp != l3dgw_port,
     Some{var first_ipv6} = rp.networks.ipv6_addrs.nth(0).
 
+MeteredFlow(.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); "
+                                "};",
+            .tags             = map_empty(),
+            .controller_meter = r.copp.get(cOPP_ICMP6_ERR()),
+            .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(),
@@ -7631,11 +7701,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}; "
@@ -7656,13 +7727,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}; "
@@ -7684,6 +7786,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 56d8be7cc..f0a047d56 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -4865,10 +4865,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
@@ -4892,10 +4892,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
@@ -4909,14 +4909,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
@@ -4928,10 +4928,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 6af62aab0..49741f45b 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16637,6 +16637,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
 
@@ -16652,7 +16698,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
@@ -16669,7 +16715,7 @@ 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}
@@ -16691,6 +16737,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
 
@@ -16723,7 +16816,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])
@@ -16733,6 +16826,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
@@ -16770,7 +16880,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])
@@ -16780,6 +16890,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
 ])
-- 
2.31.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to