Since commit f2e8130d0a42 ("northd: Explicitly handle SNAT for ICMP
need frag."), if LRP.gw_mtu is set, ovn-northd adds a priority 160
logical flow in the lr_in_larger_pkts router pipeline that matches
on "ct.trk && ct.rpl && ct.dnat" for egress traffic that's larger
than the configured MTU (if REGBIT_PKT_LARGER] == 1):

  table=23(lr_in_larger_pkts  ), priority=160,
  match=(inport == "lrp1" && outport == "lrp0" && ip4 &&
         reg9[1] && reg9[0] == 0 && ct.trk && ct.rpl && ct.dnat),
  action=(icmp4_error {...};)

The lr_in_larger_pkts logical pipeline stage (23) is translated by
ovn-controller to OpenFlow table 31.

 table=31, priority=160,
   ct_state=+rpl+trk+dnat,ip,reg9=0x2/0x3,reg14=0x2,reg15=0x1,metadata=0x3
   actions=controller(...)

Due to the way ovs-vswitchd translates OF rules to datapath flows,
all OpenFlow rules in table 31 that have a priority lower than 160
automatically get an implicit match on the inverse ct_state match
that's generated for the priority 160 OF rule, that is:

ct_state=-trk-rpl-dnat

On specific NICs, such matches (on dnat/snat ct_state) are not
offloadable to hardware.

However, in OVN, logical switches and logical routers pipelines both
get translated to the same set of OpenFlow tables. In theory that's
fine because the logical datapath distinction happens thanks to
additional metadata field matches (the metadata OF field is actually
logical datapath id).  But in this specific case it means that all
logical switch traffic that hits OpenFlow table 31 will also generate
a datapath flow that matches on ct_state=-trk-rpl-dnat, making it
non-offloadable.

E.g., in an OVN cluster with a logical switch that has no stateful
config (no stateful ACLs or LBs) traffic hitting the following
logical switch pipeline stage will also fail to be offloaded to
hardware if there's a logical router with gw_mtu configured:

  table=23(ls_in_dhcp_options ), priority=0, match=(1), action=(next;)

That is essentially all traffic forwarded on that logical switch.

Change the way we match on large packets that have been DNATed and
instead of directly matching on ct_state first save that into a
register (using the new ct_state_save() logical action).

That means that with the configuration mentioned above:
- all routed traffic that is less than MTU will now be forwarded by
  a datapath flow matching on ct_state=-trk
- all switched traffic will be forwarded by a datapath flow matching on
  ct_state=-trk

In order to not disrupt upgrades on stable branches generate the new
logical flows only if the ct_state_save() action is supported by all
ovn-controllers.

Reported-at: https://issues.redhat.com/browse/FDP-1271
Fixes: f2e8130d0a42 ("northd: Explicitly handle SNAT for ICMP need frag.")
Signed-off-by: Dumitru Ceara <dce...@redhat.com>
---
V2:
- Addressed Ales' comment:
  - simplified code that generates the ct_state match and removed not so
    obvious micro-optimization
---
 northd/northd.c          |  52 ++++++++++++----
 northd/ovn-northd.8.xml  |   8 ++-
 tests/ovn-northd.at      | 128 ++++++++++++++++++++++-----------------
 tests/system-ovn-kmod.at |  49 ++++++++++++---
 4 files changed, 160 insertions(+), 77 deletions(-)

diff --git a/northd/northd.c b/northd/northd.c
index 762e4e057d..1f9340e555 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -213,6 +213,19 @@ BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
 /* Register used for storing persistent ACL IDs */
 #define REG_ACL_ID "reg2[16..31]"
 
+/* Register used for storing the ct_state in the router pipeline and
+ * ct_saved_state helpers, matching the ct_state bits definitions from
+ * ovs-fields(7). */
+#define REG_CT_STATE "reg4[0..7]"
+
+static const char *reg_ct_state[] = {
+#define CS_STATE(ENUM, INDEX, NAME) \
+    [CS_##ENUM] = "reg4[" #INDEX "]",
+
+    CS_STATES
+#undef CS_STATE
+};
+
 /* Register used for temporarily store ECMP eth.src to avoid masked ct_label
  * access. It doesn't really occupy registers because the content of the
  * register is saved to stack and then restored in the same flow.
@@ -282,10 +295,13 @@ BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
  * | R3  |        UNUSED             | 1 |                 |   |               
                     |
  * |     |                           |   |                 |   |               
                     |
  * 
+-----+---------------------------+---+-----------------+---+------------------------------------+
- * | R4  |        REG_LB_IPV4        | X |                 |   |               
                     |
- * |     |  (>= IN_LB_AFF_CHECK &&   | R |                 |   |               
                     |
- * |     |   <= IN_LB_AFF_LEARN)     | R |                 |   |               
                     |
- * +-----+---------------------------+ E |     UNUSED      | X |               
                     |
+ * |     |        REG_LB_IPV4        |   |                 |   |               
                     |
+ * | R4  |  (>= IN_LB_AFF_CHECK &&   |   |                 |   |               
                     |
+ * |     |   <= IN_LB_AFF_LEARN)     |   |                 |   |               
                     |
+ * |     |        REG_CT_STATE       | X |                 |   |               
                     |
+ * |     |  (>= IN_CHK_PKT_LEN &&    | R |     UNUSED      |   |               
                     |
+ * |     |   <= IN_LARGER_PKTS)      | R |                 |   |               
                     |
+ * +-----+---------------------------+ E |                 | X |               
                     |
  * | R5  |  SRC_IPV4 for ARP-REQ     | G |                 | X |            
REG_LB_IPV6             |
  * |     |      (>= IP_INPUT)        | 2 |                 | R |        (>= 
IN_LB_AFF_CHECK &&      |
  * +-----+---------------------------+---+-----------------+ E |         <= 
IN_LB_AFF_LEARN)        |
@@ -14690,6 +14706,7 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int 
mtu,
                             const struct shash *meter_groups, struct ds *match,
                             struct ds *actions, enum ovn_stage stage,
                             struct ovn_port *outport,
+                            const char *ct_state_match,
                             struct lflow_ref *lflow_ref)
 {
     const char *ipv4_meter = copp_meter_get(COPP_ICMP4_ERR, op->od->nbr->copp,
@@ -14701,15 +14718,17 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int 
mtu,
     ds_put_format(match, "inport == %s", op->json_key);
 
     if (outport) {
+        ovs_assert(ct_state_match);
+
         ds_put_format(match, " && outport == %s", outport->json_key);
 
         create_icmp_need_frag_lflow(op, mtu, actions, match, ipv4_meter,
                                     lflows, lflow_ref, stage, 160, false,
-                                    "ct.trk && ct.rpl && ct.dnat",
+                                    ct_state_match,
                                     "flags.icmp_snat = 1; ");
         create_icmp_need_frag_lflow(op, mtu, actions, match, ipv6_meter,
                                     lflows, lflow_ref, stage, 160, true,
-                                    "ct.trk && ct.rpl && ct.dnat",
+                                    ct_state_match,
                                     "flags.icmp_snat = 1; ");
     }
 
@@ -14735,15 +14754,25 @@ build_check_pkt_len_flows_for_lrp(struct ovn_port *op,
 
     ds_clear(match);
     ds_put_format(match, "outport == %s", op->json_key);
+    const char *mtu_flow_action = features->ct_state_save
+                                  ? REG_CT_STATE " = ct_state_save(); next;"
+                                  : "next;";
     build_gateway_mtu_flow(lflows, op, S_ROUTER_IN_CHK_PKT_LEN, 50, 55,
-                           match, actions, &op->nbrp->header_,
-                           lflow_ref, "next;");
+                           match, actions, &op->nbrp->header_, lflow_ref,
+                           "%s", mtu_flow_action);
 
     /* ingress traffic */
     build_icmperr_pkt_big_flows(op, gw_mtu, lflows, meter_groups,
                                 match, actions, S_ROUTER_IN_IP_INPUT,
-                                NULL, lflow_ref);
-
+                                NULL, NULL, lflow_ref);
+
+    /* Additional match at egress on tracked and reply and dnat-ed traffic. */
+    char *ct_match = features->ct_state_save
+                     ? xasprintf("%s && %s && %s",
+                                 reg_ct_state[CS_TRACKED],
+                                 reg_ct_state[CS_REPLY_DIR],
+                                 reg_ct_state[CS_DST_NAT])
+                     : xstrdup("ct.trk && ct.rpl && ct.dnat");
     for (size_t i = 0; i < op->od->nbr->n_ports; i++) {
         struct ovn_port *rp = ovn_port_find(lr_ports,
                                             op->od->nbr->ports[i]->name);
@@ -14754,8 +14783,9 @@ build_check_pkt_len_flows_for_lrp(struct ovn_port *op,
         /* egress traffic */
         build_icmperr_pkt_big_flows(rp, gw_mtu, lflows, meter_groups,
                                     match, actions, S_ROUTER_IN_LARGER_PKTS,
-                                    op, lflow_ref);
+                                    op, ct_match, lflow_ref);
     }
+    free(ct_match);
 
     if (features->ct_commit_nat_v2) {
         ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_OUT_POST_SNAT, 100,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 1a70ba579d..e087b6f59f 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -4899,12 +4899,14 @@ output;
       integer value, this table adds a priority-50 logical flow with
       the match <code>outport == <var>GW_PORT</var></code> where
       <var>GW_PORT</var> is the gateway router port and applies the
-      action <code>check_pkt_larger</code> and advances the packet to
-      the next table.
+      actions <code>check_pkt_larger</code> and <code>ct_state_save</code>
+      and then advances the packet to the next table.
     </p>
 
     <pre>
-REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next;
+REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>);
+REG_CT_STATE = ct_state_save();
+next;
     </pre>
 
     <p>
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 8ee3c977f2..12d6611b69 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -6654,12 +6654,17 @@ AT_CHECK([ovn-sbctl --columns=tags list logical_flow | 
grep lsp0 -c], [0], [dnl
 AT_CLEANUP
 ])
 
-OVN_FOR_EACH_NORTHD_NO_HV([
-AT_SETUP([ovn -- gateway mtu check pkt larger flows])
+m4_define([GATEWAY_MTU_FLOWS_TEST],
+[OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ovn -- gateway mtu check pkt larger flows - ct-state-save=$2])
 ovn_start
 
-check ovn-sbctl chassis-add ch1 geneve 127.0.0.1 --\
-                set chassis ch1 other_config:ct-commit-nat-v2=true
+AS_BOX([$1])
+use_ct_save=$2
+
+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1 --                     \
+                set chassis ch1 other_config:ct-commit-nat-v2=true --   \
+                set chassis ch1 other_config:ct-state-save=$use_ct_save
 
 check ovn-nbctl ls-add sw0
 check ovn-nbctl ls-add sw1
@@ -6700,18 +6705,26 @@ check ovn-nbctl --wait=sb set logical_router_port 
lr0-public options:gateway_mtu
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CAPTURE_FILE([lr0flows])
 
-AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
+if [[ "$use_ct_save" == "true" ]]; then
+    ct_save_action='reg4[[0..7]] = ct_state_save(); next'
+    larger_pkts_ct_match='reg4[[5]] && reg4[[3]] && reg4[[7]]'
+else
+    ct_save_action='next'
+    larger_pkts_ct_match='ct.trk && ct.rpl && ct.dnat'
+fi
+
+AT_CHECK_UNQUOTED([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); $ct_save_action;)
   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
 ])
 
 AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | 
ovn_strip_lflows], [0], [dnl
@@ -6739,18 +6752,18 @@ check ovn-nbctl --wait=sb set logical_router lr0 
options:chassis=ch1
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CAPTURE_FILE([lr0flows])
 
-AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
+AT_CHECK_UNQUOTED([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); $ct_save_action;)
   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
 ])
 
 AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | 
ovn_strip_lflows], [0], [dnl
@@ -6775,19 +6788,19 @@ check ovn-nbctl --wait=sb set logical_router_port 
lr0-public options:gateway_mtu
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CAPTURE_FILE([lr0flows])
 
-AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
+AT_CHECK_UNQUOTED([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
-  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == 
"lr0-public" && (tcp)), action=(next;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); $ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == 
"lr0-public" && (tcp)), action=($ct_save_action;)
   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
 ])
 
 AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e 
"tcp" | ovn_strip_lflows], [0], [dnl
@@ -6803,11 +6816,11 @@ check ovn-nbctl --wait=sb set logical_router_port 
lr0-sw0 options:gateway_mtu=14
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CAPTURE_FILE([lr0flows])
 
-AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
+AT_CHECK_UNQUOTED([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), 
action=(reg9[[1]] = check_pkt_larger(1414); next;)
-  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == 
"lr0-public" && (tcp)), action=(next;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); $ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), 
action=(reg9[[1]] = check_pkt_larger(1414); $ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == 
"lr0-public" && (tcp)), action=($ct_save_action;)
   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && 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=??); };)
@@ -6817,14 +6830,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" 
lr0flows | ovn_strip_lflo
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl 
&& ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl 
&& ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
 ])
 
 AT_CHECK([grep "lr_in_admission.*check_pkt_larger" lr0flows | 
ovn_strip_lflows], [0], [dnl
@@ -6853,12 +6866,12 @@ check ovn-nbctl --wait=sb set logical_router_port 
lr0-sw0 options:gateway_mtu_by
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CAPTURE_FILE([lr0flows])
 
-AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
+AT_CHECK_UNQUOTED([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), 
action=(reg9[[1]] = check_pkt_larger(1414); next;)
-  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == 
"lr0-public" && (tcp)), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-sw0" 
&& (tcp)), action=(next;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == 
"lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); $ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), 
action=(reg9[[1]] = check_pkt_larger(1414); $ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == 
"lr0-public" && (tcp)), action=($ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-sw0" 
&& (tcp)), action=($ct_save_action;)
   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && 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=??); };)
@@ -6868,14 +6881,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" 
lr0flows | ovn_strip_lflo
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl 
&& ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl 
&& ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
 ])
 
 AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e 
"tcp" | ovn_strip_lflows], [0], [dnl
@@ -6894,19 +6907,19 @@ check ovn-nbctl --wait=sb clear logical_router_port 
lr0-public options
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CAPTURE_FILE([lr0flows])
 
-AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
+AT_CHECK_UNQUOTED([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
-  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), 
action=(reg9[[1]] = check_pkt_larger(1414); next;)
-  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-sw0" 
&& (tcp)), action=(next;)
+  table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), 
action=(reg9[[1]] = check_pkt_larger(1414); $ct_save_action;)
+  table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-sw0" 
&& (tcp)), action=($ct_save_action;)
   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && 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=??); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && 
ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl 
&& ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
-  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl 
&& ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" 
&& outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp4_error {flags.icmp_snat = 1; 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=??); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && 
outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && 
$larger_pkts_ct_match), action=(icmp6_error {flags.icmp_snat = 1; 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=??); };)
 ])
 
 check ovn-nbctl --wait=sb clear logical_router_port lr0-sw0 options
@@ -6946,7 +6959,10 @@ ovn-sbctl dump-flows lr0 > lr0flows
 AT_CHECK([grep -E "lr_out_post_snat.*ct_commit_nat" lr0flows], [1])
 
 AT_CLEANUP
-])
+])])
+
+GATEWAY_MTU_FLOWS_TEST([Simulate hv that does not support 'ct-state-save'], 
[false])
+GATEWAY_MTU_FLOWS_TEST([Simulate hv that supports 'ct-state-save'], [true])
 
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ovn -- static routes flows])
diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
index df00f99f7d..f0d4de1f8e 100644
--- a/tests/system-ovn-kmod.at
+++ b/tests/system-ovn-kmod.at
@@ -922,9 +922,11 @@ dnl Logical network:
 dnl 2 logical switches "public" (192.168.10.0/24 and fd10::/64)
 dnl and "internal" (192.168.20.0/24 and fd20::/64) connected to a router lr.
 dnl internal has a client.
-dnl server is connected through localnet.
+dnl Server1 is connected through localnet.
+dnl Server2 is connected to the same switch as the client.
 dnl
-dnl Server IP 192.168.10.2 fd10:2
+dnl Server1 IP 192.168.10.2 fd10:2
+dnl Server1 IP 192.168.20.3 fd20:3
 dnl Client IP 192.168.20.2 fd20:2
 dnl
 dnl SNAT for internal 192.168.20.0/24 with router ip 192.168.10.1.
@@ -960,17 +962,25 @@ check ovn-nbctl lsp-add public ln_port \
                 -- lsp-set-type ln_port localnet \
                 -- lsp-set-options ln_port network_name=phynet
 
-ADD_NAMESPACES(server)
-ADD_VETH(server, server, br-ext, "fd10::2/64", "f0:00:00:01:02:03", "fd10::1",
+ADD_NAMESPACES(server1)
+ADD_VETH(server1, server1, br-ext, "fd10::2/64", "f0:00:00:01:02:03", 
"fd10::1",
          "nodad", "192.168.10.2/24", "192.168.10.1")
-NS_EXEC([server], [ip a show dev server])
+NS_EXEC([server1], [ip a show dev server1])
 
 ADD_NAMESPACES(client)
 ADD_VETH(client, client, br-int, "fd20::2/64", "f0:00:0f:01:02:03", "fd20::1",
          "nodad", "192.168.20.2/24", "192.168.20.1")
 NS_EXEC([client], [ip a show dev client])
+
+ADD_NAMESPACES(server2)
+ADD_VETH(server2, server2, br-int, "fd20::3/64", "f0:00:0f:01:02:04", 
"fd20::1",
+         "nodad", "192.168.20.3/24", "192.168.20.1")
+NS_EXEC([server2], [ip a show dev server2])
+
 check ovn-nbctl lsp-add internal client \
   -- lsp-set-addresses client "f0:00:0f:01:02:03 192.168.20.2 fd20::2"
+check ovn-nbctl lsp-add internal server2 \
+  -- lsp-set-addresses server2 "f0:00:0f:01:02:04 192.168.20.3 fd20::3"
 
 check ovn-nbctl set logical_router lr options:chassis=hv1
 check ovn-nbctl set logical_router_port lr-internal options:gateway_mtu=1300
@@ -979,6 +989,7 @@ check ovn-nbctl lr-nat-add lr snat 192.168.10.1 
192.168.20.0/24
 check ovn-nbctl lr-nat-add lr snat fd10::1 fd20::/64
 
 OVN_POPULATE_ARP
+wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
 ovn-nbctl show
@@ -994,7 +1005,7 @@ NS_CHECK_EXEC([client], [ping -c 1 -W 2 -s 1400 
192.168.10.2 | FORMAT_PING],
 ])
 
 NS_CHECK_EXEC([client], [ip r get 192.168.10.2 | grep -q "mtu 1300"])
-NS_CHECK_EXEC([server], [ip r get 192.168.10.1 | grep -q "mtu 1300"])
+NS_CHECK_EXEC([server1], [ip r get 192.168.10.1 | grep -q "mtu 1300"])
 
 AS_BOX([IPv6])
 NS_CHECK_EXEC([client], [ping -c 1 -W 2 -s 1400 fd10::2 | grep -q "Packet too 
big: mtu=1300"])
@@ -1005,7 +1016,31 @@ NS_CHECK_EXEC([client], [ping -c 1 -W 2 -s 1400 fd10::2 
| FORMAT_PING],
 ])
 
 NS_CHECK_EXEC([client], [ip r get fd10::2 | grep -q "mtu 1300"])
-NS_CHECK_EXEC([server], [ip r get fd10::1 | grep -q "mtu 1300"])
+NS_CHECK_EXEC([server1], [ip r get fd10::1 | grep -q "mtu 1300"])
+
+AS_BOX([Switched traffic - smaller than MTU - connect to server2])
+check ovs-appctl revalidator/purge
+NS_CHECK_EXEC([client], [ping -c 1 -W 2 192.168.20.3 | FORMAT_PING],
+[0], [dnl
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+])
+NS_CHECK_EXEC([client], [ping -c 1 -W 2 fd20::3 | FORMAT_PING],
+[0], [dnl
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+])
+
+dnl While the traffic is only switched, OpenFlow tables are shared between
+dnl router and switch datapaths, making it such that stateful configurations
+dnl in the router pipeline can cause ct_state matches to "leak" to switch
+dnl specific datapath flows.
+dnl
+dnl Ensure datapath flows don't match on any dnat and/or snat conntrack states.
+dnl Those are known to not be offloadable to hardware.
+ovs-appctl dpctl/dump-flows > dp-flows
+AT_CAPTURE_FILE([dp-flows])
+AT_CHECK([grep 'ct_state(.*nat.*)' -c dp-flows], [1], [dnl
+0
+])
 
 ovn-appctl -t ovn-controller vlog/set info
 
-- 
2.49.0

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

Reply via email to