Blob Blame History Raw
diff --git a/NEWS b/NEWS
index ef000ef7b..0056bb175 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+OVN v23.06.2 - xx xxx xxxx
+--------------------------
+
 OVN v23.06.1 - 29 Aug 2023
 --------------------------
   - Bug fixes
diff --git a/configure.ac b/configure.ac
index fc4ba024a..3ed093ebe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 AC_PREREQ(2.63)
-AC_INIT(ovn, 23.06.1, bugs@openvswitch.org)
+AC_INIT(ovn, 23.06.2, bugs@openvswitch.org)
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
diff --git a/controller/lflow.c b/controller/lflow.c
index 22faaf013..718b4a27a 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -2513,10 +2513,10 @@ build_in_port_sec_default_flows(const struct sbrec_port_binding *pb,
      *       investigation.
      *
      * Eg.  If there are below OF rules in the same table
-     * (1) priority=90,icmp6,reg14=0x1,metadata=0x1,nw_ttl=225,icmp_type=135,
+     * (1) priority=90,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135,
      *     icmp_code=0,nd_sll=fa:16:3e:94:05:98
      *     actions=load:0->NXM_NX_REG10[12]
-     * (2) priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=225,icmp_type=135,
+     * (2) priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135,
      *     icmp_code=0 actions=load:1->NXM_NX_REG10[12]
      *
      * An IPv6 NS packet with nd_sll = fa:16:3e:94:05:98 is matching on the
@@ -2801,7 +2801,7 @@ build_in_port_sec_nd_flows(const struct sbrec_port_binding *pb,
     reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
     match_set_dl_type(m, htons(ETH_TYPE_IPV6));
     match_set_nw_proto(m, IPPROTO_ICMPV6);
-    match_set_nw_ttl(m, 225);
+    match_set_nw_ttl(m, 255);
     match_set_icmp_type(m, 135);
     match_set_icmp_code(m, 0);
 
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index 64a444ff6..a1676a788 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -16,6 +16,7 @@
 #include <config.h>
 #include "bitmap.h"
 #include "byte-order.h"
+#include "coverage.h"
 #include "dirs.h"
 #include "dp-packet.h"
 #include "flow.h"
@@ -55,6 +56,8 @@
 
 VLOG_DEFINE_THIS_MODULE(ofctrl);
 
+COVERAGE_DEFINE(ofctrl_msg_too_long);
+
 /* An OpenFlow flow. */
 struct ovn_flow {
     /* Key. */
@@ -1273,6 +1276,37 @@ ofctrl_add_flow_metered(struct ovn_desired_flow_table *desired_flows,
                                       meter_id, as_info, true);
 }
 
+struct ofpact_ref {
+    struct hmap_node hmap_node;
+    struct ofpact *ofpact;
+};
+
+static struct ofpact_ref *
+ofpact_ref_find(const struct hmap *refs, const struct ofpact *ofpact)
+{
+    uint32_t hash = hash_bytes(ofpact, ofpact->len, 0);
+
+    struct ofpact_ref *ref;
+    HMAP_FOR_EACH_WITH_HASH (ref, hmap_node, hash, refs) {
+        if (ofpacts_equal(ref->ofpact, ref->ofpact->len,
+                          ofpact, ofpact->len)) {
+            return ref;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ofpact_refs_destroy(struct hmap *refs)
+{
+    struct ofpact_ref *ref;
+    HMAP_FOR_EACH_POP (ref, hmap_node, refs) {
+        free(ref);
+    }
+    hmap_destroy(refs);
+}
+
 /* Either add a new flow, or append actions on an existing flow. If the
  * flow existed, a new link will also be created between the new sb_uuid
  * and the existing flow. */
@@ -1292,6 +1326,21 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
                            meter_id);
     existing = desired_flow_lookup_conjunctive(desired_flows, &f->flow);
     if (existing) {
+        struct hmap existing_conj = HMAP_INITIALIZER(&existing_conj);
+
+        struct ofpact *ofpact;
+        OFPACT_FOR_EACH (ofpact, existing->flow.ofpacts,
+                         existing->flow.ofpacts_len) {
+            if (ofpact->type != OFPACT_CONJUNCTION) {
+                continue;
+            }
+
+            struct ofpact_ref *ref = xmalloc(sizeof *ref);
+            ref->ofpact = ofpact;
+            uint32_t hash = hash_bytes(ofpact, ofpact->len, 0);
+            hmap_insert(&existing_conj, &ref->hmap_node, hash);
+        }
+
         /* There's already a flow with this particular match and action
          * 'conjunction'. Append the action to that flow rather than
          * adding a new flow.
@@ -1301,7 +1350,15 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
         ofpbuf_use_stub(&compound, compound_stub, sizeof(compound_stub));
         ofpbuf_put(&compound, existing->flow.ofpacts,
                    existing->flow.ofpacts_len);
-        ofpbuf_put(&compound, f->flow.ofpacts, f->flow.ofpacts_len);
+
+        OFPACT_FOR_EACH (ofpact, f->flow.ofpacts, f->flow.ofpacts_len) {
+            if (ofpact->type != OFPACT_CONJUNCTION ||
+                !ofpact_ref_find(&existing_conj, ofpact)) {
+                ofpbuf_put(&compound, ofpact, OFPACT_ALIGN(ofpact->len));
+            }
+        }
+
+        ofpact_refs_destroy(&existing_conj);
 
         mem_stats.desired_flow_usage -= desired_flow_size(existing);
         free(existing->flow.ofpacts);
@@ -1769,6 +1826,18 @@ ovn_flow_log(const struct ovn_flow *f, const char *action)
     }
 }
 
+static void
+ovn_flow_log_size_err(const struct ovn_flow *f)
+{
+    COVERAGE_INC(ofctrl_msg_too_long);
+
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+
+    char *s = ovn_flow_to_string(f);
+    VLOG_ERR_RL(&rl, "The FLOW_MOD message is too big: %s", s);
+    free(s);
+}
+
 static void
 ovn_flow_uninit(struct ovn_flow *f)
 {
@@ -1888,15 +1957,27 @@ encode_bundle_add(struct ofpbuf *msg, struct ofputil_bundle_ctrl_msg *bc)
     return ofputil_encode_bundle_add(OFP15_VERSION, &bam);
 }
 
-static void
+static bool
 add_flow_mod(struct ofputil_flow_mod *fm,
              struct ofputil_bundle_ctrl_msg *bc,
              struct ovs_list *msgs)
 {
     struct ofpbuf *msg = encode_flow_mod(fm);
     struct ofpbuf *bundle_msg = encode_bundle_add(msg, bc);
+
+    uint32_t flow_mod_len = msg->size;
+    uint32_t bundle_len = bundle_msg->size;
+
     ofpbuf_delete(msg);
+
+    if (flow_mod_len > UINT16_MAX || bundle_len > UINT16_MAX) {
+        ofpbuf_delete(bundle_msg);
+
+        return false;
+    }
+
     ovs_list_push_back(msgs, &bundle_msg->list_node);
+    return true;
 }
 
 /* group_table. */
@@ -2235,7 +2316,10 @@ installed_flow_add(struct ovn_flow *d,
         .new_cookie = htonll(d->cookie),
         .command = OFPFC_ADD,
     };
-    add_flow_mod(&fm, bc, msgs);
+
+    if (!add_flow_mod(&fm, bc, msgs)) {
+        ovn_flow_log_size_err(d);
+    }
 }
 
 static void
@@ -2259,7 +2343,7 @@ installed_flow_mod(struct ovn_flow *i, struct ovn_flow *d,
         /* Use OFPFC_ADD so that cookie can be updated. */
         fm.command = OFPFC_ADD;
     }
-    add_flow_mod(&fm, bc, msgs);
+    bool result = add_flow_mod(&fm, bc, msgs);
 
     /* Replace 'i''s actions and cookie by 'd''s. */
     mem_stats.installed_flow_usage -= i->ofpacts_len - d->ofpacts_len;
@@ -2267,6 +2351,10 @@ installed_flow_mod(struct ovn_flow *i, struct ovn_flow *d,
     i->ofpacts = xmemdup(d->ofpacts, d->ofpacts_len);
     i->ofpacts_len = d->ofpacts_len;
     i->cookie = d->cookie;
+
+    if (!result) {
+        ovn_flow_log_size_err(i);
+    }
 }
 
 static void
@@ -2280,7 +2368,10 @@ installed_flow_del(struct ovn_flow *i,
         .table_id = i->table_id,
         .command = OFPFC_DELETE_STRICT,
     };
-    add_flow_mod(&fm, bc, msgs);
+
+    if (!add_flow_mod(&fm, bc, msgs)) {
+        ovn_flow_log_size_err(i);
+    }
 }
 
 static void
diff --git a/debian/changelog b/debian/changelog
index e1d505a7e..e147d113c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+OVN (23.06.2-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Tue, 29 Aug 2023 11:28:59 -0400
+
 OVN (23.06.1-1) unstable; urgency=low
    [ OVN team ]
    * New upstream version
diff --git a/northd/northd.c b/northd/northd.c
index af871e260..3d6449c88 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -259,7 +259,6 @@ enum ovn_stage {
 #define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]"
 #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
 #define REGBIT_DST_NAT_IP_LOCAL "reg9[4]"
-#define REGBIT_KNOWN_ECMP_NH    "reg9[5]"
 #define REGBIT_KNOWN_LB_SESSION "reg9[6]"
 
 /* Register to store the eth address associated to a router port for packets
@@ -367,8 +366,7 @@ enum ovn_stage {
  * |     |   EGRESS_LOOPBACK/        | G |     UNUSED      |
  * | R9  |   PKT_LARGER/             | 4 |                 |
  * |     |   LOOKUP_NEIGHBOR_RESULT/ |   |                 |
- * |     |   SKIP_LOOKUP_NEIGHBOR/   |   |                 |
- * |     |   KNOWN_ECMP_NH}          |   |                 |
+ * |     |   SKIP_LOOKUP_NEIGHBOR}   |   |                 |
  * |     |                           |   |                 |
  * |     | REG_ORIG_TP_DPORT_ROUTER  |   |                 |
  * |     |                           |   |                 |
@@ -10326,15 +10324,13 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
                   cidr);
     free(cidr);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100,
-            ds_cstr(&base_match),
-            REGBIT_KNOWN_ECMP_NH" = chk_ecmp_nh_mac(); ct_next;",
-            &st_route->header_);
+                             ds_cstr(&base_match), "ct_next;",
+                             &st_route->header_);
 
     /* And packets that go out over an ECMP route need conntrack */
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100,
-            ds_cstr(route_match),
-            REGBIT_KNOWN_ECMP_NH" = chk_ecmp_nh(); ct_next;",
-            &st_route->header_);
+                             ds_cstr(route_match), "ct_next;",
+                             &st_route->header_);
 
     /* Save src eth and inport in ct_label for packets that arrive over
      * an ECMP route.
@@ -10347,9 +10343,8 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
-            "commit_ecmp_nh(ipv6 = %s, proto = tcp); next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
-            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
+            "next;",
+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
                             ds_cstr(&match), ds_cstr(&actions),
                             &st_route->header_);
@@ -10360,9 +10355,8 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
-            "commit_ecmp_nh(ipv6 = %s, proto = udp); next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
-            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
+            "next;",
+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
                             ds_cstr(&match), ds_cstr(&actions),
                             &st_route->header_);
@@ -10373,53 +10367,49 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
-            "commit_ecmp_nh(ipv6 = %s, proto = sctp); next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
-            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
+            "next;",
+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
                             ds_cstr(&match), ds_cstr(&actions),
                             &st_route->header_);
 
     ds_clear(&match);
     ds_put_format(&match,
-            "%s && (!ct.rpl && ct.est) && tcp && "REGBIT_KNOWN_ECMP_NH" == 0",
+            "%s && (!ct.rpl && ct.est) && tcp",
             ds_cstr(&base_match));
     ds_clear(&actions);
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
-            "commit_ecmp_nh(ipv6 = %s, proto = tcp); next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
-            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
+            "next;",
+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
                             ds_cstr(&match), ds_cstr(&actions),
                             &st_route->header_);
 
     ds_clear(&match);
     ds_put_format(&match,
-            "%s && (!ct.rpl && ct.est) && udp && "REGBIT_KNOWN_ECMP_NH" == 0",
+            "%s && (!ct.rpl && ct.est) && udp",
             ds_cstr(&base_match));
     ds_clear(&actions);
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
-            "commit_ecmp_nh(ipv6 = %s, proto = udp); next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
-            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
+            "next;",
+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
                             ds_cstr(&match), ds_cstr(&actions),
                             &st_route->header_);
     ds_clear(&match);
     ds_put_format(&match,
-            "%s && (!ct.rpl && ct.est) && sctp && "REGBIT_KNOWN_ECMP_NH" == 0",
+            "%s && (!ct.rpl && ct.est) && sctp",
             ds_cstr(&base_match));
     ds_clear(&actions);
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
-            "commit_ecmp_nh(ipv6 = %s, proto = sctp); next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
-            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
+            "next;",
+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
                             ds_cstr(&match), ds_cstr(&actions),
                             &st_route->header_);
@@ -10428,7 +10418,7 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
      * for where to route the packet.
      */
     ds_put_format(&ecmp_reply,
-                  "ct.rpl && "REGBIT_KNOWN_ECMP_NH" == 1 && %s == %"PRId64,
+                  "ct.rpl && %s == %"PRId64,
                   ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
     ds_clear(&match);
     ds_put_format(&match, "%s && %s", ds_cstr(&ecmp_reply),
@@ -10853,6 +10843,10 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
 {
     bool ipv4 = lb_vip->address_family == AF_INET;
     const char *ip_match = ipv4 ? "ip4" : "ip6";
+    char *aff_action[LROUTER_NAT_LB_FLOW_MAX] = {
+        [LROUTER_NAT_LB_FLOW_SKIP_SNAT]  = "flags.skip_snat_for_lb = 1; ",
+        [LROUTER_NAT_LB_FLOW_FORCE_SNAT] = "flags.force_snat_for_lb = 1; ",
+    };
 
     int prio = 110;
 
@@ -10925,15 +10919,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
     ctx.new_action[LROUTER_NAT_LB_FLOW_SKIP_SNAT] = ds_cstr(&skip_snat_act);
     ctx.new_action[LROUTER_NAT_LB_FLOW_FORCE_SNAT] = ds_cstr(&force_snat_act);
 
-    enum {
-        LROUTER_NAT_LB_AFF            = LROUTER_NAT_LB_FLOW_MAX,
-        LROUTER_NAT_LB_AFF_FORCE_SNAT = LROUTER_NAT_LB_FLOW_MAX + 1,
-    };
-    unsigned long *dp_bitmap[LROUTER_NAT_LB_FLOW_MAX + 2];
+    unsigned long *gw_dp_bitmap[LROUTER_NAT_LB_FLOW_MAX];
+    unsigned long *aff_dp_bitmap[LROUTER_NAT_LB_FLOW_MAX];
 
     size_t bitmap_len = ods_size(lr_datapaths);
-    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
-        dp_bitmap[i] = bitmap_allocate(bitmap_len);
+    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX; i++) {
+        gw_dp_bitmap[i] = bitmap_allocate(bitmap_len);
+        aff_dp_bitmap[i] = bitmap_allocate(bitmap_len);
     }
 
     /* Group gw router since we do not have datapath dependency in
@@ -10954,18 +10946,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
         }
 
         if (!od->n_l3dgw_ports) {
-            bitmap_set1(dp_bitmap[type], index);
+            bitmap_set1(gw_dp_bitmap[type], index);
         } else {
             build_distr_lrouter_nat_flows_for_lb(&ctx, type, od);
         }
 
         if (lb->affinity_timeout) {
-            if (!lport_addresses_is_empty(&od->lb_force_snat_addrs) ||
-                od->lb_force_snat_router_ip) {
-                bitmap_set1(dp_bitmap[LROUTER_NAT_LB_AFF_FORCE_SNAT], index);
-            } else {
-                bitmap_set1(dp_bitmap[LROUTER_NAT_LB_AFF], index);
-            }
+            bitmap_set1(aff_dp_bitmap[type], index);
         }
 
         if (sset_contains(&od->external_ips, lb_vip->vip_str)) {
@@ -10988,33 +10975,20 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
 
     for (size_t type = 0; type < LROUTER_NAT_LB_FLOW_MAX; type++) {
         build_gw_lrouter_nat_flows_for_lb(&ctx, type, lr_datapaths,
-                                          dp_bitmap[type]);
+                                          gw_dp_bitmap[type]);
+        build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match),
+                                   aff_action[type], aff_dp_bitmap[type],
+                                   lr_datapaths);
     }
 
-    /* LB affinity flows for datapaths where CMS has specified
-     * force_snat_for_lb floag option.
-     */
-    build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match),
-                               "flags.force_snat_for_lb = 1; ",
-                               dp_bitmap[LROUTER_NAT_LB_AFF_FORCE_SNAT],
-                               lr_datapaths);
-
-    /* LB affinity flows for datapaths where CMS has specified
-     * skip_snat_for_lb floag option or regular datapaths.
-     */
-    char *lb_aff_action =
-        lb->skip_snat ? "flags.skip_snat_for_lb = 1; " : NULL;
-    build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match),
-                               lb_aff_action, dp_bitmap[LROUTER_NAT_LB_AFF],
-                               lr_datapaths);
-
     ds_destroy(&unsnat_match);
     ds_destroy(&undnat_match);
     ds_destroy(&skip_snat_act);
     ds_destroy(&force_snat_act);
 
-    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
-        bitmap_free(dp_bitmap[i]);
+    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX; i++) {
+        bitmap_free(gw_dp_bitmap[i]);
+        bitmap_free(aff_dp_bitmap[i]);
     }
 }
 
@@ -11761,7 +11735,13 @@ build_gateway_get_l2_hdr_size(struct ovn_port *op)
             struct ovn_port *localnet_port = peer->od->localnet_ports[i];
             const struct nbrec_logical_switch_port *nbsp = localnet_port->nbsp;
 
-            if (nbsp && nbsp->n_tag_request > 0) {
+            if (!nbsp || !nbsp->tag_request) {
+                continue;
+            }
+
+            if (nbsp->tag_request[0] ||
+                (nbsp->parent_name && nbsp->parent_name[0])) {
+                /* Valid tag. */
                 return VLAN_ETH_HEADER_LEN;
             }
         }
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 35c79cd69..60cb947c4 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -6193,6 +6193,23 @@ AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort],
   table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
 ])
 
+# tag 0 requires a parent port
+check ovn-nbctl --wait=sb set Logical_Switch_Port ext-port tag_request=0
+
+ovn-sbctl dump-flows lr0 > lr0flows
+AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], [0], [dnl
+  table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:20:20:12:13 && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
+  table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
+])
+
+check ovn-nbctl --wait=sb set Logical_Switch_Port ext-port parent_name=ext-parent-port
+
+ovn-sbctl dump-flows lr0 > lr0flows
+AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], [0], [dnl
+  table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:20:20:12:13 && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
+  table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
+])
+
 AT_CLEANUP
 ])
 
@@ -6236,24 +6253,16 @@ AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.
   table=??(lr_in_ip_routing_ecmp), priority=150  , match=(reg8[[0..15]] == 0), action=(next;)
 ])
 
-AT_CHECK([grep -e "lr_in_ecmp_stateful".*commit_ecmp_nh lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
-  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && sctp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = sctp); next;)
-  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && tcp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = tcp); next;)
-  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && udp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = udp); next;)
-  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && sctp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = sctp); next;)
-  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && tcp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = tcp); next;)
-  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && udp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = udp); next;)
-])
-
-AT_CHECK([grep -e "lr_in_defrag".*chk_ecmp_nh* lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
-  table=??(lr_in_defrag       ), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1), action=(reg9[[5]] = chk_ecmp_nh_mac(); ct_next;)
-  table=??(lr_in_defrag       ), priority=100  , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(reg9[[5]] = chk_ecmp_nh(); ct_next;)
+AT_CHECK([grep -e "lr_in_defrag" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+  table=??(lr_in_defrag       ), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1), action=(ct_next;)
+  table=??(lr_in_defrag       ), priority=100  , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ct_next;)
 ])
 
 dnl The chassis was created with other_config:ct-no-masked-label=false, the flows
 dnl should be using ct_label.ecmp_reply_port.
 AT_CHECK([grep -e "lr_in_arp_resolve.*ecmp" lr0flows | sed 's/table=../table=??/'], [0], [dnl
-  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && reg9[[5]] == 1 && ct_label.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
+  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && ct_label.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
 ])
 
 dnl Simulate an ovn-controller upgrade to a version that supports
@@ -6263,7 +6272,7 @@ check ovn-sbctl set chassis ch1 other_config:ct-no-masked-label=true
 check ovn-nbctl --wait=sb sync
 ovn-sbctl dump-flows lr0 > lr0flows
 AT_CHECK([grep -e "lr_in_arp_resolve.*ecmp" lr0flows | sed 's/table=../table=??/'], [0], [dnl
-  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && reg9[[5]] == 1 && ct_mark.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
+  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && ct_mark.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
 ])
 
 # add ecmp route with wrong nexthop
@@ -8807,6 +8816,26 @@ AT_CHECK([grep "lr_in_dnat " R1flows_force_snat | sed 's/table=../table=??/' | s
   table=??(lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
 ])
 
+AS_BOX([Test LR flows - lb_force_snat_ip="172.16.0.1" + skip_snat=true])
+check ovn-nbctl --wait=sb set logical_router R1 options:lb_force_snat_ip="172.16.0.1"
+check ovn-nbctl --wait=sb set load_balancer lb0 options:skip_snat=true
+
+ovn-sbctl dump-flows R1 > R1flows_force_skip_snat
+AT_CAPTURE_FILE([R1flows_force_skip_snat])
+
+AT_CHECK([grep "lr_in_dnat " R1flows_force_skip_snat | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+  table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+  table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; next;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+])
+
 AT_CLEANUP
 ])
 
diff --git a/tests/ovn.at b/tests/ovn.at
index 48dbc3347..acda8514e 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -34023,10 +34023,10 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p1_key,metadata=0x1 actions=load:0x
  table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135 actions=load:0->NXM_NX_REG10[[12]]
  table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136 actions=load:0x1->NXM_NX_REG10[[12]]
  table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:03,arp_sha=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
 
 check_port_sec_offlows hv1 74
 
@@ -34060,12 +34060,12 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p1_key,metadata=0x1 actions=load:0x
  table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136 actions=load:0x1->NXM_NX_REG10[[12]]
  table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:03,arp_spa=10.0.0.3,arp_sha=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
  table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:13,arp_spa=10.0.0.13,arp_sha=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
 
 check_port_sec_offlows hv1 74
 
@@ -34144,13 +34144,13 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p2_key,metadata=0x1 actions=load:0x
  table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:04,icmp_type=136,icmp_code=0,nd_target=2000::/64,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]
  table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:13,icmp_type=136,icmp_code=0,nd_target=aef0::4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
  table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:13,icmp_type=136,icmp_code=0,nd_target=aef0::4,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]" > hv2_t74_flows.expected
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]" > hv2_t74_flows.expected
 
 check_port_sec_offlows hv2 74
 
diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
index da3224bbc..5467d604d 100755
--- a/utilities/checkpatch.py
+++ b/utilities/checkpatch.py
@@ -196,7 +196,7 @@ skip_signoff_check = False
 #
 # Python isn't checked as flake8 performs these checks during build.
 line_length_blacklist = re.compile(
-    r'\.(am|at|etc|in|m4|mk|patch|py)$|^debian/.*$')
+    r'\.(am|at|etc|in|m4|mk|patch|py|yml)$|^debian/.*$')
 
 # Don't enforce a requirement that leading whitespace be all spaces on
 # files that include these characters in their name, since these kinds