diff --git a/SOURCES/openvswitch-2.17.0.patch b/SOURCES/openvswitch-2.17.0.patch
index 7720d72..c5bcbb1 100644
--- a/SOURCES/openvswitch-2.17.0.patch
+++ b/SOURCES/openvswitch-2.17.0.patch
@@ -233,6 +233,19 @@ index c4300cd53e..a297aadac8 100644
    (https://git.kernel.org/pub/scm/devel/sparse/sparse.git/).
  
  - GNU make.
+diff --git a/Documentation/ref/ovs-actions.7.rst b/Documentation/ref/ovs-actions.7.rst
+index b59b7634fa..d138956556 100644
+--- a/Documentation/ref/ovs-actions.7.rst
++++ b/Documentation/ref/ovs-actions.7.rst
+@@ -1380,7 +1380,7 @@ The ``delete_field`` action
+   | ``delete_field:``\ *field*
+ 
+ The ``delete_field`` action deletes a *field* in the syntax described under
+-`Field Specifications`_ above.  Currently, only the ``tun_metadta`` fields are
++`Field Specifications`_ above.  Currently, only the ``tun_metadata`` fields are
+ supported.
+ 
+ This action was added in Open vSwitch 2.14.
 diff --git a/Documentation/topics/dpdk/phy.rst b/Documentation/topics/dpdk/phy.rst
 index 937f4c40e5..90c8691e32 100644
 --- a/Documentation/topics/dpdk/phy.rst
@@ -51026,7 +51039,7 @@ index cc43e70e31..c3742f3de2 100644
                  VLOG_INFO("%s: Received no CCM from RMP %"PRIu64" in the last"
                            " %lldms", cfm->name, rmp->mpid,
 diff --git a/lib/classifier.c b/lib/classifier.c
-index c4790ee6ba..0a89626cc3 100644
+index c4790ee6ba..18dbfc83ad 100644
 --- a/lib/classifier.c
 +++ b/lib/classifier.c
 @@ -916,9 +916,9 @@ free_conjunctive_matches(struct hmap *matches,
@@ -51041,6 +51054,62 @@ index c4790ee6ba..0a89626cc3 100644
              if (!(cm >= cm_stubs && cm < &cm_stubs[n_cm_stubs])) {
                  free(cm);
              }
+@@ -1695,6 +1695,8 @@ find_match_wc(const struct cls_subtable *subtable, ovs_version_t version,
+     const struct cls_match *rule = NULL;
+     struct flowmap stages_map = FLOWMAP_EMPTY_INITIALIZER;
+     unsigned int mask_offset = 0;
++    bool adjust_ports_mask = false;
++    ovs_be32 ports_mask;
+     int i;
+ 
+     /* Try to finish early by checking fields in segments. */
+@@ -1722,6 +1724,9 @@ find_match_wc(const struct cls_subtable *subtable, ovs_version_t version,
+                     subtable->index_maps[i], flow, wc)) {
+         goto no_match;
+     }
++    /* Accumulate the map used so far. */
++    stages_map = flowmap_or(stages_map, subtable->index_maps[i]);
++
+     hash = flow_hash_in_minimask_range(flow, &subtable->mask,
+                                        subtable->index_maps[i],
+                                        &mask_offset, &basis);
+@@ -1731,14 +1736,16 @@ find_match_wc(const struct cls_subtable *subtable, ovs_version_t version,
+          * unwildcarding all the ports bits, use the ports trie to figure out a
+          * smaller set of bits to unwildcard. */
+         unsigned int mbits;
+-        ovs_be32 value, plens, mask;
++        ovs_be32 value, plens;
+ 
+-        mask = miniflow_get_ports(&subtable->mask.masks);
+-        value = ((OVS_FORCE ovs_be32 *)flow)[TP_PORTS_OFS32] & mask;
++        ports_mask = miniflow_get_ports(&subtable->mask.masks);
++        value = ((OVS_FORCE ovs_be32 *) flow)[TP_PORTS_OFS32] & ports_mask;
+         mbits = trie_lookup_value(&subtable->ports_trie, &value, &plens, 32);
+ 
+-        ((OVS_FORCE ovs_be32 *)&wc->masks)[TP_PORTS_OFS32] |=
+-            mask & be32_prefix_mask(mbits);
++        ports_mask &= be32_prefix_mask(mbits);
++        ports_mask |= ((OVS_FORCE ovs_be32 *) &wc->masks)[TP_PORTS_OFS32];
++
++        adjust_ports_mask = true;
+ 
+         goto no_match;
+     }
+@@ -1751,6 +1758,14 @@ no_match:
+     /* Unwildcard the bits in stages so far, as they were used in determining
+      * there is no match. */
+     flow_wildcards_fold_minimask_in_map(wc, &subtable->mask, stages_map);
++    if (adjust_ports_mask) {
++        /* This has to be done after updating flow wildcards to overwrite
++         * the ports mask back.  We can't simply disable the corresponding bit
++         * in the stages map, because it has 64-bit resolution, i.e. one
++         * bit covers not only tp_src/dst, but also ct_tp_src/dst, which are
++         * not covered by the trie. */
++        ((OVS_FORCE ovs_be32 *) &wc->masks)[TP_PORTS_OFS32] = ports_mask;
++    }
+     return NULL;
+ }
+ 
 diff --git a/lib/cmap.c b/lib/cmap.c
 index c9eef3f4ae..8ca893b0b2 100644
 --- a/lib/cmap.c
@@ -51131,10 +51200,328 @@ index d3b4601858..7f3e63c384 100644
  
  static bool
 diff --git a/lib/conntrack.c b/lib/conntrack.c
-index 33a1a92953..0103fb5396 100644
+index 33a1a92953..fff8e77db1 100644
 --- a/lib/conntrack.c
 +++ b/lib/conntrack.c
-@@ -1526,14 +1526,14 @@ set_label(struct dp_packet *pkt, struct conn *conn,
+@@ -723,109 +723,59 @@ handle_alg_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,
+ }
+ 
+ static void
+-pat_packet(struct dp_packet *pkt, const struct conn *conn)
++pat_packet(struct dp_packet *pkt, const struct conn_key *key)
+ {
+-    if (conn->nat_action & NAT_ACTION_SRC) {
+-        if (conn->key.nw_proto == IPPROTO_TCP) {
+-            struct tcp_header *th = dp_packet_l4(pkt);
+-            packet_set_tcp_port(pkt, conn->rev_key.dst.port, th->tcp_dst);
+-        } else if (conn->key.nw_proto == IPPROTO_UDP) {
+-            struct udp_header *uh = dp_packet_l4(pkt);
+-            packet_set_udp_port(pkt, conn->rev_key.dst.port, uh->udp_dst);
+-        }
+-    } else if (conn->nat_action & NAT_ACTION_DST) {
+-        if (conn->key.nw_proto == IPPROTO_TCP) {
+-            packet_set_tcp_port(pkt, conn->rev_key.dst.port,
+-                                conn->rev_key.src.port);
+-        } else if (conn->key.nw_proto == IPPROTO_UDP) {
+-            packet_set_udp_port(pkt, conn->rev_key.dst.port,
+-                                conn->rev_key.src.port);
+-        }
++    if (key->nw_proto == IPPROTO_TCP) {
++        packet_set_tcp_port(pkt, key->dst.port, key->src.port);
++    } else if (key->nw_proto == IPPROTO_UDP) {
++        packet_set_udp_port(pkt, key->dst.port, key->src.port);
+     }
+ }
+ 
+-static void
+-nat_packet(struct dp_packet *pkt, const struct conn *conn, bool related)
++static uint16_t
++nat_action_reverse(uint16_t nat_action)
+ {
+-    if (conn->nat_action & NAT_ACTION_SRC) {
+-        pkt->md.ct_state |= CS_SRC_NAT;
+-        if (conn->key.dl_type == htons(ETH_TYPE_IP)) {
+-            struct ip_header *nh = dp_packet_l3(pkt);
+-            packet_set_ipv4_addr(pkt, &nh->ip_src,
+-                                 conn->rev_key.dst.addr.ipv4);
+-        } else {
+-            struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
+-            packet_set_ipv6_addr(pkt, conn->key.nw_proto,
+-                                 nh6->ip6_src.be32,
+-                                 &conn->rev_key.dst.addr.ipv6, true);
+-        }
+-        if (!related) {
+-            pat_packet(pkt, conn);
+-        }
+-    } else if (conn->nat_action & NAT_ACTION_DST) {
+-        pkt->md.ct_state |= CS_DST_NAT;
+-        if (conn->key.dl_type == htons(ETH_TYPE_IP)) {
+-            struct ip_header *nh = dp_packet_l3(pkt);
+-            packet_set_ipv4_addr(pkt, &nh->ip_dst,
+-                                 conn->rev_key.src.addr.ipv4);
+-        } else {
+-            struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
+-            packet_set_ipv6_addr(pkt, conn->key.nw_proto,
+-                                 nh6->ip6_dst.be32,
+-                                 &conn->rev_key.src.addr.ipv6, true);
+-        }
+-        if (!related) {
+-            pat_packet(pkt, conn);
+-        }
++    if (nat_action & NAT_ACTION_SRC) {
++        nat_action ^= NAT_ACTION_SRC;
++        nat_action |= NAT_ACTION_DST;
++    } else if (nat_action & NAT_ACTION_DST) {
++        nat_action ^= NAT_ACTION_DST;
++        nat_action |= NAT_ACTION_SRC;
+     }
++    return nat_action;
+ }
+ 
+ static void
+-un_pat_packet(struct dp_packet *pkt, const struct conn *conn)
++nat_packet_ipv4(struct dp_packet *pkt, const struct conn_key *key,
++                uint16_t nat_action)
+ {
+-    if (conn->nat_action & NAT_ACTION_SRC) {
+-        if (conn->key.nw_proto == IPPROTO_TCP) {
+-            struct tcp_header *th = dp_packet_l4(pkt);
+-            packet_set_tcp_port(pkt, th->tcp_src, conn->key.src.port);
+-        } else if (conn->key.nw_proto == IPPROTO_UDP) {
+-            struct udp_header *uh = dp_packet_l4(pkt);
+-            packet_set_udp_port(pkt, uh->udp_src, conn->key.src.port);
+-        }
+-    } else if (conn->nat_action & NAT_ACTION_DST) {
+-        if (conn->key.nw_proto == IPPROTO_TCP) {
+-            packet_set_tcp_port(pkt, conn->key.dst.port, conn->key.src.port);
+-        } else if (conn->key.nw_proto == IPPROTO_UDP) {
+-            packet_set_udp_port(pkt, conn->key.dst.port, conn->key.src.port);
+-        }
++    struct ip_header *nh = dp_packet_l3(pkt);
++
++    if (nat_action & NAT_ACTION_SRC) {
++        packet_set_ipv4_addr(pkt, &nh->ip_src, key->dst.addr.ipv4);
++    } else if (nat_action & NAT_ACTION_DST) {
++        packet_set_ipv4_addr(pkt, &nh->ip_dst, key->src.addr.ipv4);
+     }
+ }
+ 
+ static void
+-reverse_pat_packet(struct dp_packet *pkt, const struct conn *conn)
++nat_packet_ipv6(struct dp_packet *pkt, const struct conn_key *key,
++                uint16_t nat_action)
+ {
+-    if (conn->nat_action & NAT_ACTION_SRC) {
+-        if (conn->key.nw_proto == IPPROTO_TCP) {
+-            struct tcp_header *th_in = dp_packet_l4(pkt);
+-            packet_set_tcp_port(pkt, conn->key.src.port,
+-                                th_in->tcp_dst);
+-        } else if (conn->key.nw_proto == IPPROTO_UDP) {
+-            struct udp_header *uh_in = dp_packet_l4(pkt);
+-            packet_set_udp_port(pkt, conn->key.src.port,
+-                                uh_in->udp_dst);
+-        }
+-    } else if (conn->nat_action & NAT_ACTION_DST) {
+-        if (conn->key.nw_proto == IPPROTO_TCP) {
+-            packet_set_tcp_port(pkt, conn->key.src.port,
+-                                conn->key.dst.port);
+-        } else if (conn->key.nw_proto == IPPROTO_UDP) {
+-            packet_set_udp_port(pkt, conn->key.src.port,
+-                                conn->key.dst.port);
+-        }
++    struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
++
++    if (nat_action & NAT_ACTION_SRC) {
++        packet_set_ipv6_addr(pkt, key->nw_proto, nh6->ip6_src.be32,
++                             &key->dst.addr.ipv6, true);
++    } else if (nat_action & NAT_ACTION_DST) {
++        packet_set_ipv6_addr(pkt, key->nw_proto, nh6->ip6_dst.be32,
++                             &key->src.addr.ipv6, true);
+     }
+ }
+ 
+ static void
+-reverse_nat_packet(struct dp_packet *pkt, const struct conn *conn)
++nat_inner_packet(struct dp_packet *pkt, struct conn_key *key,
++                 uint16_t nat_action)
+ {
+     char *tail = dp_packet_tail(pkt);
+     uint16_t pad = dp_packet_l2_pad_size(pkt);
+@@ -834,98 +784,77 @@ reverse_nat_packet(struct dp_packet *pkt, const struct conn *conn)
+     uint16_t orig_l3_ofs = pkt->l3_ofs;
+     uint16_t orig_l4_ofs = pkt->l4_ofs;
+ 
+-    if (conn->key.dl_type == htons(ETH_TYPE_IP)) {
+-        struct ip_header *nh = dp_packet_l3(pkt);
+-        struct icmp_header *icmp = dp_packet_l4(pkt);
+-        struct ip_header *inner_l3 = (struct ip_header *) (icmp + 1);
+-        /* This call is already verified to succeed during the code path from
+-         * 'conn_key_extract()' which calls 'extract_l4_icmp()'. */
+-        extract_l3_ipv4(&inner_key, inner_l3, tail - ((char *)inner_l3) - pad,
++    void *l3 = dp_packet_l3(pkt);
++    void *l4 = dp_packet_l4(pkt);
++    void *inner_l3;
++    /* These calls are already verified to succeed during the code path from
++     * 'conn_key_extract()' which calls
++     * 'extract_l4_icmp()'/'extract_l4_icmp6()'. */
++    if (key->dl_type == htons(ETH_TYPE_IP)) {
++        inner_l3 = (char *) l4 + sizeof(struct icmp_header);
++        extract_l3_ipv4(&inner_key, inner_l3, tail - ((char *) inner_l3) - pad,
+                         &inner_l4, false);
+-        pkt->l3_ofs += (char *) inner_l3 - (char *) nh;
+-        pkt->l4_ofs += inner_l4 - (char *) icmp;
++    } else {
++        inner_l3 = (char *) l4 + sizeof(struct icmp6_data_header);
++        extract_l3_ipv6(&inner_key, inner_l3, tail - ((char *) inner_l3) - pad,
++                        &inner_l4);
++    }
++    pkt->l3_ofs += (char *) inner_l3 - (char *) l3;
++    pkt->l4_ofs += inner_l4 - (char *) l4;
+ 
+-        if (conn->nat_action & NAT_ACTION_SRC) {
+-            packet_set_ipv4_addr(pkt, &inner_l3->ip_src,
+-                                 conn->key.src.addr.ipv4);
+-        } else if (conn->nat_action & NAT_ACTION_DST) {
+-            packet_set_ipv4_addr(pkt, &inner_l3->ip_dst,
+-                                 conn->key.dst.addr.ipv4);
+-        }
++    /* Reverse the key for inner packet. */
++    struct conn_key rev_key = *key;
++    conn_key_reverse(&rev_key);
++
++    pat_packet(pkt, &rev_key);
++
++    if (key->dl_type == htons(ETH_TYPE_IP)) {
++        nat_packet_ipv4(pkt, &rev_key, nat_action);
+ 
+-        reverse_pat_packet(pkt, conn);
++        struct icmp_header *icmp = (struct icmp_header *) l4;
+         icmp->icmp_csum = 0;
+         icmp->icmp_csum = csum(icmp, tail - (char *) icmp - pad);
+     } else {
+-        struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
+-        struct icmp6_data_header *icmp6 = dp_packet_l4(pkt);
+-        struct ovs_16aligned_ip6_hdr *inner_l3_6 =
+-            (struct ovs_16aligned_ip6_hdr *) (icmp6 + 1);
+-        /* This call is already verified to succeed during the code path from
+-         * 'conn_key_extract()' which calls 'extract_l4_icmp6()'. */
+-        extract_l3_ipv6(&inner_key, inner_l3_6,
+-                        tail - ((char *)inner_l3_6) - pad,
+-                        &inner_l4);
+-        pkt->l3_ofs += (char *) inner_l3_6 - (char *) nh6;
+-        pkt->l4_ofs += inner_l4 - (char *) icmp6;
+-
+-        if (conn->nat_action & NAT_ACTION_SRC) {
+-            packet_set_ipv6_addr(pkt, conn->key.nw_proto,
+-                                 inner_l3_6->ip6_src.be32,
+-                                 &conn->key.src.addr.ipv6, true);
+-        } else if (conn->nat_action & NAT_ACTION_DST) {
+-            packet_set_ipv6_addr(pkt, conn->key.nw_proto,
+-                                 inner_l3_6->ip6_dst.be32,
+-                                 &conn->key.dst.addr.ipv6, true);
+-        }
+-        reverse_pat_packet(pkt, conn);
++        nat_packet_ipv6(pkt, &rev_key, nat_action);
++
++        struct icmp6_data_header *icmp6 = (struct icmp6_data_header *) l4;
+         icmp6->icmp6_base.icmp6_cksum = 0;
+-        icmp6->icmp6_base.icmp6_cksum = packet_csum_upperlayer6(nh6, icmp6,
+-            IPPROTO_ICMPV6, tail - (char *) icmp6 - pad);
++        icmp6->icmp6_base.icmp6_cksum =
++            packet_csum_upperlayer6(l3, icmp6, IPPROTO_ICMPV6,
++                                    tail - (char *) icmp6 - pad);
+     }
++
+     pkt->l3_ofs = orig_l3_ofs;
+     pkt->l4_ofs = orig_l4_ofs;
+ }
+ 
+ static void
+-un_nat_packet(struct dp_packet *pkt, const struct conn *conn,
+-              bool related)
++nat_packet(struct dp_packet *pkt, struct conn *conn, bool reply, bool related)
+ {
+-    if (conn->nat_action & NAT_ACTION_SRC) {
+-        pkt->md.ct_state |= CS_DST_NAT;
+-        if (conn->key.dl_type == htons(ETH_TYPE_IP)) {
+-            struct ip_header *nh = dp_packet_l3(pkt);
+-            packet_set_ipv4_addr(pkt, &nh->ip_dst,
+-                                 conn->key.src.addr.ipv4);
+-        } else {
+-            struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
+-            packet_set_ipv6_addr(pkt, conn->key.nw_proto,
+-                                 nh6->ip6_dst.be32,
+-                                 &conn->key.src.addr.ipv6, true);
+-        }
++    struct conn_key *key = reply ? &conn->key : &conn->rev_key;
++    uint16_t nat_action = reply ? nat_action_reverse(conn->nat_action)
++                                : conn->nat_action;
+ 
+-        if (OVS_UNLIKELY(related)) {
+-            reverse_nat_packet(pkt, conn);
+-        } else {
+-            un_pat_packet(pkt, conn);
+-        }
+-    } else if (conn->nat_action & NAT_ACTION_DST) {
++    /* Update ct_state. */
++    if (nat_action & NAT_ACTION_SRC) {
+         pkt->md.ct_state |= CS_SRC_NAT;
+-        if (conn->key.dl_type == htons(ETH_TYPE_IP)) {
+-            struct ip_header *nh = dp_packet_l3(pkt);
+-            packet_set_ipv4_addr(pkt, &nh->ip_src,
+-                                 conn->key.dst.addr.ipv4);
+-        } else {
+-            struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
+-            packet_set_ipv6_addr(pkt, conn->key.nw_proto,
+-                                 nh6->ip6_src.be32,
+-                                 &conn->key.dst.addr.ipv6, true);
+-        }
++    } else if (nat_action & NAT_ACTION_DST) {
++        pkt->md.ct_state |= CS_DST_NAT;
++    }
++
++    /* Reverse the key for outer header. */
++    if (key->dl_type == htons(ETH_TYPE_IP)) {
++        nat_packet_ipv4(pkt, key, nat_action);
++    } else {
++        nat_packet_ipv6(pkt, key, nat_action);
++    }
+ 
++    if (nat_action & NAT_ACTION_SRC || nat_action & NAT_ACTION_DST) {
+         if (OVS_UNLIKELY(related)) {
+-            reverse_nat_packet(pkt, conn);
++            nat_action = nat_action_reverse(nat_action);
++            nat_inner_packet(pkt, key, nat_action);
+         } else {
+-            un_pat_packet(pkt, conn);
++            pat_packet(pkt, key);
+         }
+     }
+ }
+@@ -1044,7 +973,7 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
+                 memcpy(nc, nat_conn, sizeof *nc);
+             }
+ 
+-            nat_packet(pkt, nc, ctx->icmp_related);
++            nat_packet(pkt, nc, false, ctx->icmp_related);
+             memcpy(&nat_conn->key, &nc->rev_key, sizeof nat_conn->key);
+             memcpy(&nat_conn->rev_key, &nc->key, sizeof nat_conn->rev_key);
+             nat_conn->conn_type = CT_CONN_TYPE_UN_NAT;
+@@ -1148,11 +1077,8 @@ handle_nat(struct dp_packet *pkt, struct conn *conn,
+         if (pkt->md.ct_state & (CS_SRC_NAT | CS_DST_NAT)) {
+             pkt->md.ct_state &= ~(CS_SRC_NAT | CS_DST_NAT);
+         }
+-        if (reply) {
+-            un_nat_packet(pkt, conn, related);
+-        } else {
+-            nat_packet(pkt, conn, related);
+-        }
++
++        nat_packet(pkt, conn, reply, related);
+     }
+ }
+ 
+@@ -1526,14 +1452,14 @@ set_label(struct dp_packet *pkt, struct conn *conn,
  static long long
  ct_sweep(struct conntrack *ct, long long now, size_t limit)
  {
@@ -51151,7 +51538,7 @@ index 33a1a92953..0103fb5396 100644
              ovs_mutex_lock(&conn->lock);
              if (now < conn->expiration || count >= limit) {
                  min_expiration = MIN(min_expiration, conn->expiration);
-@@ -2242,7 +2242,7 @@ nat_range_hash(const struct conn *conn, uint32_t basis,
+@@ -2242,7 +2168,7 @@ nat_range_hash(const struct conn *conn, uint32_t basis,
      hash = ct_addr_hash_add(hash, &nat_info->min_addr);
      hash = ct_addr_hash_add(hash, &nat_info->max_addr);
      hash = hash_add(hash,
@@ -51160,7 +51547,7 @@ index 33a1a92953..0103fb5396 100644
                      | nat_info->min_port);
      hash = ct_endpoint_hash_add(hash, &conn->key.src);
      hash = ct_endpoint_hash_add(hash, &conn->key.dst);
-@@ -2265,8 +2265,16 @@ set_sport_range(const struct nat_action_info_t *ni, const struct conn_key *k,
+@@ -2265,8 +2191,16 @@ set_sport_range(const struct nat_action_info_t *ni, const struct conn_key *k,
      if (((ni->nat_action & NAT_ACTION_SNAT_ALL) == NAT_ACTION_SRC) ||
          ((ni->nat_action & NAT_ACTION_DST))) {
          *curr = ntohs(k->src.port);
@@ -51179,7 +51566,7 @@ index 33a1a92953..0103fb5396 100644
      } else {
          *min = ni->min_port;
          *max = ni->max_port;
-@@ -2389,6 +2397,26 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
+@@ -2389,6 +2323,26 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
      return exhausted;
  }
  
@@ -51206,7 +51593,7 @@ index 33a1a92953..0103fb5396 100644
  /* This function tries to get a unique tuple.
   * Every iteration checks that the reverse tuple doesn't
   * collide with any existing one.
-@@ -2403,9 +2431,11 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
+@@ -2403,9 +2357,11 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
   *
   * In case of DNAT:
   *    - For each dst IP address in the range (if any).
@@ -51221,7 +51608,7 @@ index 33a1a92953..0103fb5396 100644
   *
   * If none can be found, return exhaustion to the caller. */
  static bool
-@@ -2436,6 +2466,11 @@ nat_get_unique_tuple(struct conntrack *ct, const struct conn *conn,
+@@ -2436,6 +2392,11 @@ nat_get_unique_tuple(struct conntrack *ct, const struct conn *conn,
      set_dport_range(nat_info, &conn->key, hash, &curr_dport,
                      &min_dport, &max_dport);
  
@@ -51233,7 +51620,7 @@ index 33a1a92953..0103fb5396 100644
  another_round:
      store_addr_to_key(&curr_addr, &nat_conn->rev_key,
                        nat_info->nat_action);
-@@ -2449,15 +2484,19 @@ another_round:
+@@ -2449,15 +2410,19 @@ another_round:
          goto next_addr;
      }
  
@@ -51262,7 +51649,7 @@ index 33a1a92953..0103fb5396 100644
      }
  
      /* Check if next IP is in range and respin. Otherwise, notify
-@@ -2857,8 +2896,8 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key)
+@@ -2857,8 +2822,8 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key)
  {
      ovs_rwlock_wrlock(&ct->resources_lock);
  
@@ -51327,6 +51714,44 @@ index d344514343..1afcc65adb 100644
          return;
      }
  
+diff --git a/lib/dpctl.c b/lib/dpctl.c
+index 29041fa3e3..742fbce2d9 100644
+--- a/lib/dpctl.c
++++ b/lib/dpctl.c
+@@ -1727,26 +1727,23 @@ dpctl_flush_conntrack(int argc, const char *argv[],
+ 
+     /* Report error if there are more than one unparsed argument. */
+     if (args > 1) {
+-        ds_put_cstr(&ds, "invalid arguments");
+         error = EINVAL;
+-        goto error;
++        dpctl_error(dpctl_p, error, "invalid arguments: %s", ds_cstr(&ds));
++        goto out;
+     }
+ 
+     error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+     if (error) {
+-        return error;
++        goto out;
+     }
+ 
+     error = ct_dpif_flush(dpif, pzone, ptuple);
+-    if (!error) {
+-        dpif_close(dpif);
+-        return 0;
+-    } else {
+-        ds_put_cstr(&ds, "failed to flush conntrack");
++    if (error) {
++        dpctl_error(dpctl_p, error, "failed to flush conntrack: %s",
++                    ds_cstr(&ds));
+     }
+ 
+-error:
+-    dpctl_error(dpctl_p, error, "%s", ds_cstr(&ds));
++out:
+     ds_destroy(&ds);
+     dpif_close(dpif);
+     return error;
 diff --git a/lib/dpif-netdev-avx512.c b/lib/dpif-netdev-avx512.c
 index b7131ba3f1..82a4138184 100644
 --- a/lib/dpif-netdev-avx512.c
@@ -58015,16 +58440,165 @@ index ed58de17de..aad9f9c77a 100644
              free(nf_flow);
          }
 diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
-index 9280e008ea..742eed3998 100644
+index 9280e008ea..f13478a884 100644
 --- a/ofproto/ofproto-dpif-ipfix.c
 +++ b/ofproto/ofproto-dpif-ipfix.c
-@@ -926,17 +926,21 @@ dpif_ipfix_bridge_exporter_destroy(struct dpif_ipfix_bridge_exporter *exporter)
+@@ -124,11 +124,18 @@ struct dpif_ipfix_port {
+     uint32_t ifindex;
+ };
+ 
++struct dpif_ipfix_domain {
++    struct hmap_node hmap_node; /* In struct dpif_ipfix_exporter's domains. */
++    time_t last_template_set_time;
++};
++
+ struct dpif_ipfix_exporter {
+     uint32_t exporter_id; /* Exporting Process identifier */
+-    struct collectors *collectors;
+     uint32_t seq_number;
+-    time_t last_template_set_time;
++    struct collectors *collectors;
++    struct hmap domains; /* Contains struct dpif_ipfix_domain indexed by
++                            observation domain id. */
++    time_t last_stats_sent_time;
+     struct hmap cache_flow_key_map;  /* ipfix_flow_cache_entry. */
+     struct ovs_list cache_flow_start_timestamp_list;  /* ipfix_flow_cache_entry. */
+     uint32_t cache_active_timeout;  /* In seconds. */
+@@ -617,6 +624,9 @@ static void get_export_time_now(uint64_t *, uint32_t *);
+ 
+ static void dpif_ipfix_cache_expire_now(struct dpif_ipfix_exporter *, bool);
+ 
++static void dpif_ipfix_exporter_del_domain(struct dpif_ipfix_exporter *,
++                                           struct dpif_ipfix_domain *);
++
+ static bool
+ ofproto_ipfix_bridge_exporter_options_equal(
+     const struct ofproto_ipfix_bridge_exporter_options *a,
+@@ -697,13 +707,14 @@ dpif_ipfix_exporter_init(struct dpif_ipfix_exporter *exporter)
+     exporter->exporter_id = ++exporter_total_count;
+     exporter->collectors = NULL;
+     exporter->seq_number = 1;
+-    exporter->last_template_set_time = 0;
++    exporter->last_stats_sent_time = 0;
+     hmap_init(&exporter->cache_flow_key_map);
+     ovs_list_init(&exporter->cache_flow_start_timestamp_list);
+     exporter->cache_active_timeout = 0;
+     exporter->cache_max_flows = 0;
+     exporter->virtual_obs_id = NULL;
+     exporter->virtual_obs_len = 0;
++    hmap_init(&exporter->domains);
+ 
+     memset(&exporter->ipfix_global_stats, 0,
+            sizeof(struct dpif_ipfix_global_stats));
+@@ -711,6 +722,7 @@ dpif_ipfix_exporter_init(struct dpif_ipfix_exporter *exporter)
+ 
+ static void
+ dpif_ipfix_exporter_clear(struct dpif_ipfix_exporter *exporter)
++    OVS_REQUIRES(mutex)
+ {
+     /* Flush the cache with flow end reason "forced end." */
+     dpif_ipfix_cache_expire_now(exporter, true);
+@@ -719,22 +731,29 @@ dpif_ipfix_exporter_clear(struct dpif_ipfix_exporter *exporter)
+     exporter->exporter_id = 0;
+     exporter->collectors = NULL;
+     exporter->seq_number = 1;
+-    exporter->last_template_set_time = 0;
++    exporter->last_stats_sent_time = 0;
+     exporter->cache_active_timeout = 0;
+     exporter->cache_max_flows = 0;
+     free(exporter->virtual_obs_id);
+     exporter->virtual_obs_id = NULL;
+     exporter->virtual_obs_len = 0;
+ 
++    struct dpif_ipfix_domain *dom;
++    HMAP_FOR_EACH_SAFE (dom, hmap_node, &exporter->domains) {
++        dpif_ipfix_exporter_del_domain(exporter, dom);
++    }
++
+     memset(&exporter->ipfix_global_stats, 0,
+            sizeof(struct dpif_ipfix_global_stats));
+ }
+ 
+ static void
+ dpif_ipfix_exporter_destroy(struct dpif_ipfix_exporter *exporter)
++    OVS_REQUIRES(mutex)
+ {
+     dpif_ipfix_exporter_clear(exporter);
+     hmap_destroy(&exporter->cache_flow_key_map);
++    hmap_destroy(&exporter->domains);
+ }
+ 
+ static bool
+@@ -742,7 +761,7 @@ dpif_ipfix_exporter_set_options(struct dpif_ipfix_exporter *exporter,
+                                 const struct sset *targets,
+                                 const uint32_t cache_active_timeout,
+                                 const uint32_t cache_max_flows,
+-                                const char *virtual_obs_id)
++                                const char *virtual_obs_id) OVS_REQUIRES(mutex)
+ {
+     size_t virtual_obs_len;
+     collectors_destroy(exporter->collectors);
+@@ -769,6 +788,37 @@ dpif_ipfix_exporter_set_options(struct dpif_ipfix_exporter *exporter,
+     return true;
+ }
+ 
++static struct dpif_ipfix_domain *
++dpif_ipfix_exporter_find_domain(const struct dpif_ipfix_exporter *exporter,
++                                uint32_t domain_id) OVS_REQUIRES(mutex)
++{
++    struct dpif_ipfix_domain *dom;
++    HMAP_FOR_EACH_WITH_HASH (dom, hmap_node, hash_int(domain_id, 0),
++                             &exporter->domains) {
++        return dom;
++    }
++    return NULL;
++}
++
++static struct dpif_ipfix_domain *
++dpif_ipfix_exporter_insert_domain(struct dpif_ipfix_exporter *exporter,
++                                  const uint32_t domain_id) OVS_REQUIRES(mutex)
++{
++    struct dpif_ipfix_domain *dom = xmalloc(sizeof *dom);
++    dom->last_template_set_time = 0;
++    hmap_insert(&exporter->domains, &dom->hmap_node, hash_int(domain_id, 0));
++    return dom;
++}
++
++static void
++dpif_ipfix_exporter_del_domain(struct dpif_ipfix_exporter *exporter,
++                               struct dpif_ipfix_domain *dom)
++    OVS_REQUIRES(mutex)
++{
++    hmap_remove(&exporter->domains, &dom->hmap_node);
++    free(dom);
++}
++
+ static struct dpif_ipfix_port *
+ dpif_ipfix_find_port(const struct dpif_ipfix *di,
+                      odp_port_t odp_port) OVS_REQUIRES(mutex)
+@@ -909,6 +959,7 @@ dpif_ipfix_bridge_exporter_init(struct dpif_ipfix_bridge_exporter *exporter)
+ 
+ static void
+ dpif_ipfix_bridge_exporter_clear(struct dpif_ipfix_bridge_exporter *exporter)
++    OVS_REQUIRES(mutex)
+ {
+     dpif_ipfix_exporter_clear(&exporter->exporter);
+     ofproto_ipfix_bridge_exporter_options_destroy(exporter->options);
+@@ -918,6 +969,7 @@ dpif_ipfix_bridge_exporter_clear(struct dpif_ipfix_bridge_exporter *exporter)
+ 
+ static void
+ dpif_ipfix_bridge_exporter_destroy(struct dpif_ipfix_bridge_exporter *exporter)
++    OVS_REQUIRES(mutex)
+ {
+     dpif_ipfix_bridge_exporter_clear(exporter);
+     dpif_ipfix_exporter_destroy(&exporter->exporter);
+@@ -926,17 +978,21 @@ dpif_ipfix_bridge_exporter_destroy(struct dpif_ipfix_bridge_exporter *exporter)
  static void
  dpif_ipfix_bridge_exporter_set_options(
      struct dpif_ipfix_bridge_exporter *exporter,
 -    const struct ofproto_ipfix_bridge_exporter_options *options)
 +    const struct ofproto_ipfix_bridge_exporter_options *options,
-+    bool *options_changed)
++    bool *options_changed) OVS_REQUIRES(mutex)
  {
 -    bool options_changed;
 -
@@ -58045,7 +58619,7 @@ index 9280e008ea..742eed3998 100644
          !exporter->options
          || !ofproto_ipfix_bridge_exporter_options_equal(
              options, exporter->options));
-@@ -945,7 +949,7 @@ dpif_ipfix_bridge_exporter_set_options(
+@@ -945,7 +1001,7 @@ dpif_ipfix_bridge_exporter_set_options(
       * shortchanged in collectors (which indicates that opening one or
       * more of the configured collectors failed, so that we should
       * retry). */
@@ -58054,7 +58628,7 @@ index 9280e008ea..742eed3998 100644
          || collectors_count(exporter->exporter.collectors)
              < sset_count(&options->targets)) {
          if (!dpif_ipfix_exporter_set_options(
-@@ -957,7 +961,7 @@ dpif_ipfix_bridge_exporter_set_options(
+@@ -957,7 +1013,7 @@ dpif_ipfix_bridge_exporter_set_options(
      }
  
      /* Avoid reconfiguring if options didn't change. */
@@ -58063,13 +58637,29 @@ index 9280e008ea..742eed3998 100644
          return;
      }
  
-@@ -1015,17 +1019,21 @@ dpif_ipfix_flow_exporter_destroy(struct dpif_ipfix_flow_exporter *exporter)
+@@ -999,6 +1055,7 @@ dpif_ipfix_flow_exporter_init(struct dpif_ipfix_flow_exporter *exporter)
+ 
+ static void
+ dpif_ipfix_flow_exporter_clear(struct dpif_ipfix_flow_exporter *exporter)
++    OVS_REQUIRES(mutex)
+ {
+     dpif_ipfix_exporter_clear(&exporter->exporter);
+     ofproto_ipfix_flow_exporter_options_destroy(exporter->options);
+@@ -1007,6 +1064,7 @@ dpif_ipfix_flow_exporter_clear(struct dpif_ipfix_flow_exporter *exporter)
+ 
+ static void
+ dpif_ipfix_flow_exporter_destroy(struct dpif_ipfix_flow_exporter *exporter)
++    OVS_REQUIRES(mutex)
+ {
+     dpif_ipfix_flow_exporter_clear(exporter);
+     dpif_ipfix_exporter_destroy(&exporter->exporter);
+@@ -1015,17 +1073,21 @@ dpif_ipfix_flow_exporter_destroy(struct dpif_ipfix_flow_exporter *exporter)
  static bool
  dpif_ipfix_flow_exporter_set_options(
      struct dpif_ipfix_flow_exporter *exporter,
 -    const struct ofproto_ipfix_flow_exporter_options *options)
 +    const struct ofproto_ipfix_flow_exporter_options *options,
-+    bool *options_changed)
++    bool *options_changed) OVS_REQUIRES(mutex)
  {
 -    bool options_changed;
 -
@@ -58090,7 +58680,7 @@ index 9280e008ea..742eed3998 100644
          !exporter->options
          || !ofproto_ipfix_flow_exporter_options_equal(
              options, exporter->options));
-@@ -1034,7 +1042,7 @@ dpif_ipfix_flow_exporter_set_options(
+@@ -1034,7 +1096,7 @@ dpif_ipfix_flow_exporter_set_options(
       * shortchanged in collectors (which indicates that opening one or
       * more of the configured collectors failed, so that we should
       * retry). */
@@ -58099,7 +58689,7 @@ index 9280e008ea..742eed3998 100644
          || collectors_count(exporter->exporter.collectors)
              < sset_count(&options->targets)) {
          if (!dpif_ipfix_exporter_set_options(
-@@ -1046,7 +1054,7 @@ dpif_ipfix_flow_exporter_set_options(
+@@ -1046,7 +1108,7 @@ dpif_ipfix_flow_exporter_set_options(
      }
  
      /* Avoid reconfiguring if options didn't change. */
@@ -58108,7 +58698,14 @@ index 9280e008ea..742eed3998 100644
          return true;
      }
  
-@@ -1069,7 +1077,7 @@ remove_flow_exporter(struct dpif_ipfix *di,
+@@ -1063,13 +1125,14 @@ dpif_ipfix_flow_exporter_set_options(
+ static void
+ remove_flow_exporter(struct dpif_ipfix *di,
+                      struct dpif_ipfix_flow_exporter_map_node *node)
++                     OVS_REQUIRES(mutex)
+ {
+     hmap_remove(&di->flow_exporter_map, &node->node);
+     dpif_ipfix_flow_exporter_destroy(&node->exporter);
      free(node);
  }
  
@@ -58117,7 +58714,7 @@ index 9280e008ea..742eed3998 100644
  dpif_ipfix_set_options(
      struct dpif_ipfix *di,
      const struct ofproto_ipfix_bridge_exporter_options *bridge_exporter_options,
-@@ -1077,16 +1085,19 @@ dpif_ipfix_set_options(
+@@ -1077,16 +1140,19 @@ dpif_ipfix_set_options(
      size_t n_flow_exporters_options) OVS_EXCLUDED(mutex)
  {
      int i;
@@ -58139,7 +58736,7 @@ index 9280e008ea..742eed3998 100644
      for (i = 0; i < n_flow_exporters_options; i++) {
          node = dpif_ipfix_find_flow_exporter_map_node(
              di, options->collector_set_id);
-@@ -1095,15 +1106,19 @@ dpif_ipfix_set_options(
+@@ -1095,15 +1161,19 @@ dpif_ipfix_set_options(
              dpif_ipfix_flow_exporter_init(&node->exporter);
              hmap_insert(&di->flow_exporter_map, &node->node,
                          hash_int(options->collector_set_id, 0));
@@ -58161,7 +58758,7 @@ index 9280e008ea..742eed3998 100644
          /* This is slow but doesn't take any extra memory, and
           * this table is not supposed to contain many rows anyway. */
          options = (struct ofproto_ipfix_flow_exporter_options *)
-@@ -1117,10 +1132,12 @@ dpif_ipfix_set_options(
+@@ -1117,10 +1187,12 @@ dpif_ipfix_set_options(
          }
          if (i == n_flow_exporters_options) {  /* Not found. */
              remove_flow_exporter(di, node);
@@ -58174,7 +58771,7 @@ index 9280e008ea..742eed3998 100644
  }
  
  struct dpif_ipfix *
-@@ -1215,7 +1232,7 @@ static void
+@@ -1215,7 +1287,7 @@ static void
  dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
  {
      struct dpif_ipfix_flow_exporter_map_node *exp_node;
@@ -58183,7 +58780,7 @@ index 9280e008ea..742eed3998 100644
  
      dpif_ipfix_bridge_exporter_clear(&di->bridge_exporter);
  
-@@ -1224,7 +1241,7 @@ dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
+@@ -1224,7 +1296,7 @@ dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
          free(exp_node);
      }
  
@@ -58192,16 +58789,55 @@ index 9280e008ea..742eed3998 100644
          dpif_ipfix_del_port__(di, dip);
      }
  }
-@@ -2799,7 +2816,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
+@@ -1983,6 +2055,7 @@ static void
+ ipfix_cache_update(struct dpif_ipfix_exporter *exporter,
+                    struct ipfix_flow_cache_entry *entry,
+                    enum ipfix_sampled_packet_type sampled_pkt_type)
++                   OVS_REQUIRES(mutex)
+ {
+     struct ipfix_flow_cache_entry *old_entry;
+     size_t current_flows = 0;
+@@ -2794,14 +2867,36 @@ dpif_ipfix_flow_sample(struct dpif_ipfix *di, const struct dp_packet *packet,
+     ovs_mutex_unlock(&mutex);
+ }
+ 
++static bool
++dpif_ipfix_should_send_template(struct dpif_ipfix_exporter *exporter,
++                                const uint32_t observation_domain_id,
++                                const uint32_t export_time_sec)
++    OVS_REQUIRES(mutex)
++{
++    struct dpif_ipfix_domain *domain;
++    domain = dpif_ipfix_exporter_find_domain(exporter,
++                                             observation_domain_id);
++    if (!domain) {
++        /* First time we see this obs_domain_id. */
++        domain = dpif_ipfix_exporter_insert_domain(exporter,
++                                                   observation_domain_id);
++    }
++
++    if ((domain->last_template_set_time + IPFIX_TEMPLATE_INTERVAL)
++        <= export_time_sec) {
++        domain->last_template_set_time = export_time_sec;
++        return true;
++    }
++    return false;
++}
++
+ static void
+ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
                          bool forced_end, const uint64_t export_time_usec,
-                         const uint32_t export_time_sec)
+-                        const uint32_t export_time_sec)
++                        const uint32_t export_time_sec) OVS_REQUIRES(mutex)
  {
 -    struct ipfix_flow_cache_entry *entry, *next_entry;
 +    struct ipfix_flow_cache_entry *entry;
      uint64_t max_flow_start_timestamp_usec;
-     bool template_msg_sent = false;
+-    bool template_msg_sent = false;
      enum ipfix_flow_end_reason flow_end_reason;
-@@ -2811,7 +2828,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
+ 
+     if (ovs_list_is_empty(&exporter->cache_flow_start_timestamp_list)) {
+@@ -2811,7 +2906,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
      max_flow_start_timestamp_usec = export_time_usec -
          1000000LL * exporter->cache_active_timeout;
  
@@ -58210,6 +58846,58 @@ index 9280e008ea..742eed3998 100644
                          &exporter->cache_flow_start_timestamp_list) {
          if (forced_end) {
              flow_end_reason = FORCED_END;
+@@ -2827,25 +2922,28 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
+             break;
+         }
+ 
+-        ovs_list_remove(&entry->cache_flow_start_timestamp_list_node);
+-        hmap_remove(&exporter->cache_flow_key_map,
+-                    &entry->flow_key_map_node);
++        /* XXX: Make frequency of the (Options) Template and Exporter Process
++         * Statistics transmission configurable.
++         * Cf. IETF RFC 5101 Section 4.3. and 10.3.6. */
++        if ((exporter->last_stats_sent_time + IPFIX_TEMPLATE_INTERVAL)
++             <= export_time_sec) {
++            exporter->last_stats_sent_time = export_time_sec;
++            ipfix_send_exporter_data_msg(exporter, export_time_sec);
++        }
+ 
+-         /* XXX: Make frequency of the (Options) Template and Exporter Process
+-          * Statistics transmission configurable.
+-          * Cf. IETF RFC 5101 Section 4.3. and 10.3.6. */
+-        if (!template_msg_sent
+-            && (exporter->last_template_set_time + IPFIX_TEMPLATE_INTERVAL)
+-                <= export_time_sec) {
++        if (dpif_ipfix_should_send_template(exporter,
++                                            entry->flow_key.obs_domain_id,
++                                            export_time_sec)) {
++            VLOG_DBG("Sending templates for ObservationDomainID %"PRIu32,
++                     entry->flow_key.obs_domain_id);
+             ipfix_send_template_msgs(exporter, export_time_sec,
+                                      entry->flow_key.obs_domain_id);
+-            exporter->last_template_set_time = export_time_sec;
+-            template_msg_sent = true;
+-
+-            /* Send Exporter Process Statistics. */
+-            ipfix_send_exporter_data_msg(exporter, export_time_sec);
+         }
+ 
++        ovs_list_remove(&entry->cache_flow_start_timestamp_list_node);
++        hmap_remove(&exporter->cache_flow_key_map,
++                    &entry->flow_key_map_node);
++
+         /* XXX: Group multiple data records for the same obs domain id
+          * into the same message. */
+         ipfix_send_data_msg(exporter, export_time_sec, entry, flow_end_reason);
+@@ -2866,7 +2964,7 @@ get_export_time_now(uint64_t *export_time_usec, uint32_t *export_time_sec)
+ 
+ static void
+ dpif_ipfix_cache_expire_now(struct dpif_ipfix_exporter *exporter,
+-                            bool forced_end)
++                            bool forced_end) OVS_REQUIRES(mutex)
+ {
+     uint64_t export_time_usec;
+     uint32_t export_time_sec;
 diff --git a/ofproto/ofproto-dpif-ipfix.h b/ofproto/ofproto-dpif-ipfix.h
 index 1f42cd5275..75c0ab81ac 100644
 --- a/ofproto/ofproto-dpif-ipfix.h
@@ -58266,7 +58954,7 @@ index 78a54c715d..109940ad2a 100644
              oftrace_node_destroy(node);
          }
 diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
-index 57f94df544..adb53f8031 100644
+index 57f94df544..59126b9014 100644
 --- a/ofproto/ofproto-dpif-upcall.c
 +++ b/ofproto/ofproto-dpif-upcall.c
 @@ -362,6 +362,10 @@ static void upcall_unixctl_dump_wait(struct unixctl_conn *conn, int argc,
@@ -58299,6 +58987,15 @@ index 57f94df544..adb53f8031 100644
              ovsrcu_postpone(ukey_delete__, old_ukey);
              transition_ukey(old_ukey, UKEY_DELETED);
              transition_ukey(new_ukey, UKEY_VISIBLE);
+@@ -2853,7 +2862,7 @@ revalidator_sweep__(struct revalidator *revalidator, bool purge)
+                 } else {
+                     struct dpif_flow_stats stats;
+                     COVERAGE_INC(revalidate_missed_dp_flow);
+-                    memset(&stats, 0, sizeof stats);
++                    memcpy(&stats, &ukey->stats, sizeof stats);
+                     result = revalidate_ukey(udpif, ukey, &stats, &odp_actions,
+                                              reval_seq, &recircs, false);
+                 }
 @@ -3099,6 +3108,31 @@ upcall_unixctl_purge(struct unixctl_conn *conn, int argc OVS_UNUSED,
      unixctl_command_reply(conn, "");
  }
@@ -58771,7 +59468,7 @@ index 851088d794..2ba90e999c 100644
  void xlate_bundle_set(struct ofproto_dpif *, struct ofbundle *,
                        const char *name, enum port_vlan_mode,
 diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
-index 8143dd965f..f9562dee87 100644
+index 8143dd965f..b3e575bcd0 100644
 --- a/ofproto/ofproto-dpif.c
 +++ b/ofproto/ofproto-dpif.c
 @@ -215,10 +215,6 @@ struct shash all_dpif_backers = SHASH_INITIALIZER(&all_dpif_backers);
@@ -58785,7 +59482,64 @@ index 8143dd965f..f9562dee87 100644
  static bool ofproto_use_tnl_push_pop = true;
  static void ofproto_unixctl_init(void);
  static void ct_zone_config_init(struct dpif_backer *backer);
-@@ -1663,7 +1659,7 @@ static int
+@@ -718,12 +714,6 @@ close_dpif_backer(struct dpif_backer *backer, bool del)
+     free(backer);
+ }
+ 
+-/* Datapath port slated for removal from datapath. */
+-struct odp_garbage {
+-    struct ovs_list list_node;
+-    odp_port_t odp_port;
+-};
+-
+ static void check_support(struct dpif_backer *backer);
+ 
+ static int
+@@ -733,8 +723,6 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
+     struct dpif_port_dump port_dump;
+     struct dpif_port port;
+     struct shash_node *node;
+-    struct ovs_list garbage_list;
+-    struct odp_garbage *garbage;
+ 
+     struct sset names;
+     char *backer_name;
+@@ -796,25 +784,23 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
+         dpif_flow_flush(backer->dpif);
+     }
+ 
+-    /* Loop through the ports already on the datapath and remove any
+-     * that we don't need anymore. */
+-    ovs_list_init(&garbage_list);
++    /* Loop through the ports already on the datapath and find ones that are
++     * not on the initial OpenFlow ports list.  These are stale ports, that we
++     * do not need anymore, or tunnel backing interfaces, that do not generally
++     * match the name of OpenFlow tunnel ports, or both.  Add all of them to
++     * the list of tunnel backers.  type_run() will garbage collect those that
++     * are not active tunnel backing interfaces during revalidation. */
+     dpif_port_dump_start(&port_dump, backer->dpif);
+     while (dpif_port_dump_next(&port_dump, &port)) {
+         node = shash_find(&init_ofp_ports, port.name);
+         if (!node && strcmp(port.name, dpif_base_name(backer->dpif))) {
+-            garbage = xmalloc(sizeof *garbage);
+-            garbage->odp_port = port.port_no;
+-            ovs_list_push_front(&garbage_list, &garbage->list_node);
++            simap_put(&backer->tnl_backers, port.name,
++                      odp_to_u32(port.port_no));
++            backer->need_revalidate = REV_RECONFIGURE;
+         }
+     }
+     dpif_port_dump_done(&port_dump);
+ 
+-    LIST_FOR_EACH_POP (garbage, list_node, &garbage_list) {
+-        dpif_port_del(backer->dpif, garbage->odp_port, false);
+-        free(garbage);
+-    }
+-
+     shash_add(&all_dpif_backers, type, backer);
+ 
+     check_support(backer);
+@@ -1663,7 +1649,7 @@ static int
  construct(struct ofproto *ofproto_)
  {
      struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
@@ -58794,7 +59548,7 @@ index 8143dd965f..f9562dee87 100644
      int error;
  
      /* Tunnel module can get used right after the udpif threads are running. */
-@@ -1701,7 +1697,7 @@ construct(struct ofproto *ofproto_)
+@@ -1701,7 +1687,7 @@ construct(struct ofproto *ofproto_)
      ofproto->ams_seqno = seq_read(ofproto->ams_seq);
  
  
@@ -58803,7 +59557,7 @@ index 8143dd965f..f9562dee87 100644
          struct iface_hint *iface_hint = node->data;
  
          if (!strcmp(iface_hint->br_name, ofproto->up.name)) {
-@@ -1720,9 +1716,6 @@ construct(struct ofproto *ofproto_)
+@@ -1720,9 +1706,6 @@ construct(struct ofproto *ofproto_)
      hmap_insert(&all_ofproto_dpifs_by_name,
                  &ofproto->all_ofproto_dpifs_by_name_node,
                  hash_string(ofproto->up.name, 0));
@@ -58813,7 +59567,7 @@ index 8143dd965f..f9562dee87 100644
      memset(&ofproto->stats, 0, sizeof ofproto->stats);
  
      ofproto_init_tables(ofproto_, N_TABLES);
-@@ -1820,8 +1813,6 @@ destruct(struct ofproto *ofproto_, bool del)
+@@ -1820,8 +1803,6 @@ destruct(struct ofproto *ofproto_, bool del)
  
      hmap_remove(&all_ofproto_dpifs_by_name,
                  &ofproto->all_ofproto_dpifs_by_name_node);
@@ -58822,7 +59576,7 @@ index 8143dd965f..f9562dee87 100644
  
      OFPROTO_FOR_EACH_TABLE (table, &ofproto->up) {
          CLS_FOR_EACH (rule, up.cr, &table->cls) {
-@@ -1857,6 +1848,8 @@ destruct(struct ofproto *ofproto_, bool del)
+@@ -1857,6 +1838,8 @@ destruct(struct ofproto *ofproto_, bool del)
  
      seq_destroy(ofproto->ams_seq);
  
@@ -58831,7 +59585,7 @@ index 8143dd965f..f9562dee87 100644
      close_dpif_backer(ofproto->backer, del);
  }
  
-@@ -1945,7 +1938,7 @@ run(struct ofproto *ofproto_)
+@@ -1945,7 +1928,7 @@ run(struct ofproto *ofproto_)
  
      new_dump_seq = seq_read(udpif_dump_seq(ofproto->backer->udpif));
      if (ofproto->dump_seq != new_dump_seq) {
@@ -58840,7 +59594,7 @@ index 8143dd965f..f9562dee87 100644
          long long now = time_msec();
  
          /* We know stats are relatively fresh, so now is a good time to do some
-@@ -1955,7 +1948,7 @@ run(struct ofproto *ofproto_)
+@@ -1955,7 +1938,7 @@ run(struct ofproto *ofproto_)
          /* Expire OpenFlow flows whose idle_timeout or hard_timeout
           * has passed. */
          ovs_mutex_lock(&ofproto_mutex);
@@ -58849,7 +59603,7 @@ index 8143dd965f..f9562dee87 100644
                              &ofproto->up.expirable) {
              rule_expire(rule_dpif_cast(rule), now);
          }
-@@ -2346,6 +2339,7 @@ set_ipfix(
+@@ -2346,6 +2329,7 @@ set_ipfix(
      struct dpif_ipfix *di = ofproto->ipfix;
      bool has_options = bridge_exporter_options || flow_exporters_options;
      bool new_di = false;
@@ -58857,7 +59611,7 @@ index 8143dd965f..f9562dee87 100644
  
      if (has_options && !di) {
          di = ofproto->ipfix = dpif_ipfix_create();
-@@ -2355,7 +2349,7 @@ set_ipfix(
+@@ -2355,7 +2339,7 @@ set_ipfix(
      if (di) {
          /* Call set_options in any case to cleanly flush the flow
           * caches in the last exporters that are to be destroyed. */
@@ -58866,7 +59620,7 @@ index 8143dd965f..f9562dee87 100644
              di, bridge_exporter_options, flow_exporters_options,
              n_flow_exporters_options);
  
-@@ -2371,6 +2365,10 @@ set_ipfix(
+@@ -2371,6 +2355,10 @@ set_ipfix(
              dpif_ipfix_unref(di);
              ofproto->ipfix = NULL;
          }
@@ -58877,7 +59631,7 @@ index 8143dd965f..f9562dee87 100644
      }
  
      return 0;
-@@ -2493,11 +2491,11 @@ set_lldp(struct ofport *ofport_,
+@@ -2493,11 +2481,11 @@ set_lldp(struct ofport *ofport_,
  {
      struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
      struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
@@ -58891,7 +59645,7 @@ index 8143dd965f..f9562dee87 100644
              ofport->lldp = lldp_create(ofport->up.netdev, ofport_->mtu, cfg);
          }
  
-@@ -2509,6 +2507,9 @@ set_lldp(struct ofport *ofport_,
+@@ -2509,6 +2497,9 @@ set_lldp(struct ofport *ofport_,
      } else if (ofport->lldp) {
          lldp_unref(ofport->lldp);
          ofport->lldp = NULL;
@@ -58901,7 +59655,7 @@ index 8143dd965f..f9562dee87 100644
          ofproto->backer->need_revalidate = REV_RECONFIGURE;
      }
  
-@@ -3106,11 +3107,11 @@ bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
+@@ -3106,11 +3097,11 @@ bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
  {
      struct ofproto_dpif *ofproto = bundle->ofproto;
      struct mac_learning *ml = ofproto->ml;
@@ -58915,7 +59669,7 @@ index 8143dd965f..f9562dee87 100644
          if (mac_entry_get_port(ml, mac) == bundle) {
              if (all_ofprotos) {
                  struct ofproto_dpif *o;
-@@ -3141,13 +3142,13 @@ bundle_move(struct ofbundle *old, struct ofbundle *new)
+@@ -3141,13 +3132,13 @@ bundle_move(struct ofbundle *old, struct ofbundle *new)
  {
      struct ofproto_dpif *ofproto = old->ofproto;
      struct mac_learning *ml = ofproto->ml;
@@ -58931,7 +59685,7 @@ index 8143dd965f..f9562dee87 100644
          if (mac_entry_get_port(ml, mac) == old) {
              mac_entry_set_port(ml, mac, new);
          }
-@@ -3244,7 +3245,7 @@ static void
+@@ -3244,7 +3235,7 @@ static void
  bundle_destroy(struct ofbundle *bundle)
  {
      struct ofproto_dpif *ofproto;
@@ -58940,7 +59694,7 @@ index 8143dd965f..f9562dee87 100644
  
      if (!bundle) {
          return;
-@@ -3257,7 +3258,7 @@ bundle_destroy(struct ofbundle *bundle)
+@@ -3257,7 +3248,7 @@ bundle_destroy(struct ofbundle *bundle)
      xlate_bundle_remove(bundle);
      xlate_txn_commit();
  
@@ -58949,7 +59703,7 @@ index 8143dd965f..f9562dee87 100644
          bundle_del_port(port);
      }
  
-@@ -3347,9 +3348,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
+@@ -3347,9 +3338,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
          }
      }
      if (!ok || ovs_list_size(&bundle->ports) != s->n_members) {
@@ -58960,7 +59714,7 @@ index 8143dd965f..f9562dee87 100644
              for (i = 0; i < s->n_members; i++) {
                  if (s->members[i] == port->up.ofp_port) {
                      goto found;
-@@ -3963,6 +3962,10 @@ port_add(struct ofproto *ofproto_, struct netdev *netdev)
+@@ -3963,6 +3952,10 @@ port_add(struct ofproto *ofproto_, struct netdev *netdev)
              simap_put(&ofproto->backer->tnl_backers,
                        dp_port_name, odp_to_u32(port_no));
          }
@@ -58971,7 +59725,7 @@ index 8143dd965f..f9562dee87 100644
      }
  
      if (netdev_get_tunnel_config(netdev)) {
-@@ -4471,12 +4474,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
+@@ -4471,12 +4464,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
                  atomic_add_relaxed(&tbl->n_matched, stats->n_packets, &orig);
              }
              if (xcache) {
@@ -58991,7 +59745,7 @@ index 8143dd965f..f9562dee87 100644
              }
              return rule;
          }
-@@ -4507,12 +4512,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
+@@ -4507,12 +4502,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
                                 stats->n_packets, &orig);
          }
          if (xcache) {
@@ -59011,7 +59765,7 @@ index 8143dd965f..f9562dee87 100644
          }
          if (rule) {
              goto out;   /* Match. */
-@@ -5550,9 +5557,9 @@ ct_zone_timeout_policy_sweep(struct dpif_backer *backer)
+@@ -5550,9 +5547,9 @@ ct_zone_timeout_policy_sweep(struct dpif_backer *backer)
  {
      if (!ovs_list_is_empty(&backer->ct_tp_kill_list)
          && time_msec() >= timeout_policy_cleanup_timer) {
@@ -59023,7 +59777,7 @@ index 8143dd965f..f9562dee87 100644
              if (!ct_dpif_del_timeout_policy(backer->dpif, ct_tp->tp_id)) {
                  ovs_list_remove(&ct_tp->list_node);
                  ct_timeout_policy_destroy(ct_tp, backer->tp_ids);
-@@ -5594,6 +5601,7 @@ ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
+@@ -5594,6 +5591,7 @@ ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
              ct_timeout_policy_unref(backer, ct_zone->ct_tp);
              ct_zone->ct_tp = ct_tp;
              ct_tp->ref_count++;
@@ -59031,7 +59785,7 @@ index 8143dd965f..f9562dee87 100644
          }
      } else {
          struct ct_zone *new_ct_zone = ct_zone_alloc(zone_id);
-@@ -5601,6 +5609,7 @@ ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
+@@ -5601,6 +5599,7 @@ ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
          cmap_insert(&backer->ct_zones, &new_ct_zone->node,
                      hash_int(zone_id, 0));
          ct_tp->ref_count++;
@@ -59039,7 +59793,7 @@ index 8143dd965f..f9562dee87 100644
      }
  }
  
-@@ -5617,6 +5626,7 @@ ct_del_zone_timeout_policy(const char *datapath_type, uint16_t zone_id)
+@@ -5617,6 +5616,7 @@ ct_del_zone_timeout_policy(const char *datapath_type, uint16_t zone_id)
      if (ct_zone) {
          ct_timeout_policy_unref(backer, ct_zone->ct_tp);
          ct_zone_remove_and_destroy(backer, ct_zone);
@@ -59047,7 +59801,7 @@ index 8143dd965f..f9562dee87 100644
      }
  }
  
-@@ -5818,15 +5828,7 @@ ofproto_dpif_lookup_by_name(const char *name)
+@@ -5818,15 +5818,7 @@ ofproto_dpif_lookup_by_name(const char *name)
  struct ofproto_dpif *
  ofproto_dpif_lookup_by_uuid(const struct uuid *uuid)
  {
@@ -61714,10 +62468,105 @@ index 2bef06f39c..922185d61d 100644
  OVS_VSWITCHD_STOP
  AT_CLEANUP
 diff --git a/tests/classifier.at b/tests/classifier.at
-index cdcd72c156..f652b59837 100644
+index cdcd72c156..de2705653e 100644
 --- a/tests/classifier.at
 +++ b/tests/classifier.at
-@@ -129,6 +129,31 @@ Datapath actions: 3
+@@ -65,6 +65,94 @@ Datapath actions: 2
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([flow classifier - lookup segmentation - final stage])
++OVS_VSWITCHD_START
++add_of_ports br0 1 2 3
++AT_DATA([flows.txt], [dnl
++table=0 in_port=1 priority=33,tcp,tp_dst=80,tcp_flags=+psh,action=output(2)
++table=0 in_port=1 priority=0,ip,action=drop
++table=0 in_port=2 priority=16,icmp6,nw_ttl=255,icmp_type=135,icmp_code=0,nd_target=1000::1 ,action=output(1)
++table=0 in_port=2 priority=0,ip,action=drop
++table=0 in_port=3 action=resubmit(,1)
++table=1 in_port=3 priority=45,ct_state=+trk+rpl,ct_nw_proto=6,ct_tp_src=3/0x1,tcp,tp_dst=80,tcp_flags=+psh,action=output(2)
++table=1 in_port=3 priority=10,ip,action=drop
++])
++AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
++
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80,tcp_flags=syn'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,tcp,in_port=1,nw_frag=no,tp_dst=80,tcp_flags=-psh
++Datapath actions: drop
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80,tcp_flags=syn|ack'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,tcp,in_port=1,nw_frag=no,tp_dst=80,tcp_flags=-psh
++Datapath actions: drop
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80,tcp_flags=ack|psh'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,tcp,in_port=1,nw_frag=no,tp_dst=80,tcp_flags=+psh
++Datapath actions: 2
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,tcp,in_port=1,nw_frag=no,tp_dst=80,tcp_flags=-psh
++Datapath actions: drop
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=79'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,tcp,in_port=1,nw_frag=no,tp_dst=0x40/0xfff0,tcp_flags=-psh
++Datapath actions: drop
++])
++
++dnl Having both the port and the tcp flags in the resulting megaflow below
++dnl is redundant, but that is how ports trie logic is implemented.
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=81'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,tcp,in_port=1,nw_frag=no,tp_dst=81,tcp_flags=-psh
++Datapath actions: drop
++])
++
++dnl nd_target is redundant in the megaflow below and it is also not relevant
++dnl for an icmp reply.  Datapath may discard that match, but it is OK as long
++dnl as we have prerequisites (icmp_type) in the match as well.
++AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=2,eth_src=f6:d2:b0:19:5e:7b,eth_dst=d2:49:19:91:78:fe,dl_type=0x86dd,ipv6_src=1000::3,ipv6_dst=1000::4,nw_proto=58,nw_ttl=255,icmpv6_type=128,icmpv6_code=0"], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,icmp6,in_port=2,nw_ttl=255,nw_frag=no,icmp_type=0x80/0xfc,nd_target=::
++Datapath actions: drop
++])
++
++AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=2,eth_src=f6:d2:b0:19:5e:7b,eth_dst=d2:49:19:91:78:fe,dl_type=0x86dd,ipv6_src=1000::3,ipv6_dst=1000::4,nw_proto=58,nw_ttl=255,icmpv6_type=135,icmpv6_code=0"], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,icmp6,in_port=2,nw_ttl=255,nw_frag=no,icmp_type=0x87/0xff,icmp_code=0x0/0xff,nd_target=::
++Datapath actions: drop
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=2,eth_src=f6:d2:b0:19:5e:7b,eth_dst=d2:49:19:91:78:fe,dl_type=0x86dd,ipv6_src=1000::3,ipv6_dst=1000::4,nw_proto=58,nw_ttl=255,icmpv6_type=135,icmpv6_code=0,nd_target=1000::1"], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,icmp6,in_port=2,nw_ttl=255,nw_frag=no,icmp_type=0x87/0xff,icmp_code=0x0/0xff,nd_target=1000::1
++Datapath actions: 1
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=2,eth_src=f6:d2:b0:19:5e:7b,eth_dst=d2:49:19:91:78:fe,dl_type=0x86dd,ipv6_src=1000::3,ipv6_dst=1000::4,nw_proto=58,nw_ttl=255,icmpv6_type=135,icmpv6_code=0,nd_target=1000::2"], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,icmp6,in_port=2,nw_ttl=255,nw_frag=no,icmp_type=0x87/0xff,icmp_code=0x0/0xff,nd_target=1000::2
++Datapath actions: drop
++])
++
++dnl Check that ports' mask doesn't affect ct ports.
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=3,ct_state=trk|rpl,ct_nw_proto=6,ct_tp_src=3,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80,tcp_flags=psh'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,ct_state=+rpl+trk,ct_nw_proto=6,ct_tp_src=0x1/0x1,eth,tcp,in_port=3,nw_frag=no,tp_dst=80,tcp_flags=+psh
++Datapath actions: 2
++])
++AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=3,ct_state=trk|rpl,ct_nw_proto=6,ct_tp_src=3,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=79,tcp_flags=psh'], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,ct_state=+rpl+trk,ct_nw_proto=6,ct_tp_src=0x1/0x1,eth,tcp,in_port=3,nw_frag=no,tp_dst=0x40/0xfff0,tcp_flags=+psh
++Datapath actions: drop
++])
++
++OVS_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_BANNER([flow classifier prefix lookup])
+ AT_SETUP([flow classifier - prefix lookup])
+ OVS_VSWITCHD_START
+@@ -129,6 +217,31 @@ Datapath actions: 3
  OVS_VSWITCHD_STOP(["/'prefixes' with incompatible field: ipv6_label/d"])
  AT_CLEANUP
  
@@ -66007,6 +66856,73 @@ index c3ee6990ca..7d2715c4a7 100644
  AT_CHECK([ovs-appctl dpif-netdev/miniflow-parser-set autovalidator], [0], [dnl
  Miniflow extract implementation set to autovalidator.
  ])
+diff --git a/tests/system-interface.at b/tests/system-interface.at
+index 784bada12c..3bf339582d 100644
+--- a/tests/system-interface.at
++++ b/tests/system-interface.at
+@@ -63,3 +63,62 @@ AT_CHECK([
+     [stdout], [Device "br-p1" does not exist.]
+ )
+ AT_CLEANUP
++
++AT_SETUP([interface - datapath ports garbage collection])
++OVS_CHECK_GENEVE()
++OVS_TRAFFIC_VSWITCHD_START()
++
++dnl Not relevant for userspace datapath.
++AT_SKIP_IF([! ovs-appctl dpctl/show | grep -q ovs-system])
++
++AT_CHECK([ovs-vsctl add-port br0 tunnel_port dnl
++            -- set Interface tunnel_port dnl
++                   type=geneve options:remote_ip=flow options:key=123])
++
++AT_CHECK([ip link add ovs-veth0 type veth peer name ovs-veth1])
++on_exit 'ip link del ovs-veth0'
++
++AT_CHECK([ovs-vsctl add-port br0 ovs-veth0])
++
++OVS_WAIT_UNTIL([ip link show | grep -q " genev_sys_[[0-9]]*: .* ovs-system "])
++
++dnl Store the output of ip link for geneve port to compare ifindex later.
++AT_CHECK([ip link show | grep " genev_sys_[[0-9]]*: .* ovs-system " > geneve.0])
++
++AT_CHECK([ovs-appctl dpctl/show | grep port], [0], [dnl
++  port 0: ovs-system (internal)
++  port 1: br0 (internal)
++  port 2: genev_sys_6081 (geneve: packet_type=ptap)
++  port 3: ovs-veth0
++])
++
++OVS_APP_EXIT_AND_WAIT_BY_TARGET([ovs-vswitchd], [ovs-vswitchd.pid])
++
++dnl Check that geneve backing interface is still in the datapath.
++AT_CHECK([ip link show | grep " genev_sys_[[0-9]]*: .* ovs-system " | diff -u - geneve.0])
++
++dnl Remove the veth port from the database while ovs-vswitchd is down.
++AT_CHECK([ovs-vsctl --no-wait del-port ovs-veth0])
++
++dnl Check that it is still tied to the OVS datapath.
++AT_CHECK([ip link show ovs-veth0 | grep -q ovs-system])
++
++dnl Bring ovs-vswitchd back up.
++AT_CHECK([ovs-vswitchd --detach --no-chdir --pidfile --log-file -vdpif:dbg],
++         [0], [], [stderr])
++
++dnl Wait for the veth port to be removed from the datapath.
++OVS_WAIT_WHILE([ip link show ovs-veth0 | grep -q ovs-system])
++
++AT_CHECK([ovs-appctl dpctl/show | grep port], [0], [dnl
++  port 0: ovs-system (internal)
++  port 1: br0 (internal)
++  port 2: genev_sys_6081 (geneve: packet_type=ptap)
++])
++
++dnl Check that geneve backing interface is still in the datapath and it wasn't
++dnl re-created, i.e. the ifindex is the same.
++AT_CHECK([ip link show | grep " genev_sys_[[0-9]]*: .* ovs-system " | diff -u - geneve.0])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
 diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
 index 86d633ac4f..f0aaae63eb 100644
 --- a/tests/system-kmod-macros.at
@@ -66147,7 +67063,7 @@ index 1714273e35..270956d13f 100644
  dnl Delete ip address.
  AT_CHECK([ip addr del 10.0.0.17/24 dev p1-route], [0], [stdout])
 diff --git a/tests/system-traffic.at b/tests/system-traffic.at
-index f22d86e466..d4c34c1291 100644
+index f22d86e466..4a37641b2f 100644
 --- a/tests/system-traffic.at
 +++ b/tests/system-traffic.at
 @@ -192,6 +192,46 @@ NS_CHECK_EXEC([at_ns0], [ping6 -s 3200 -q -c 3 -i 0.3 -w 2 fc00:1::2 | FORMAT_PI
@@ -66694,7 +67610,7 @@ index f22d86e466..d4c34c1291 100644
  
  
  OVS_TRAFFIC_VSWITCHD_STOP
-@@ -1933,15 +2148,51 @@ dnl p1(at_ns1) interface
+@@ -1933,14 +2148,50 @@ dnl p1(at_ns1) interface
  NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 00 00 00 00 00 02 00 00 00 00 00 01 88 47 00 00 21 40 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
  
  dnl Check the expected decapsulated on the egress interface
@@ -66712,8 +67628,8 @@ index f22d86e466..d4c34c1291 100644
 +OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0040:  *1617 *1819 *1a1b *1c1d *1e1f *2021 *2223 *2425" 2>&1 1>/dev/null])
 +OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0050:  *2627 *2829 *2a2b *2c2d *2e2f *3031 *3233 *3435" 2>&1 1>/dev/null])
 +OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0060:  *3637" 2>&1 1>/dev/null])
- 
- 
++
++
 +OVS_TRAFFIC_VSWITCHD_STOP
 +AT_CLEANUP
 +
@@ -66744,15 +67660,14 @@ index f22d86e466..d4c34c1291 100644
 +dnl Wait for qdiscs to be applied.
 +OVS_WAIT_UNTIL([tc qdisc show dev ovs-p0 | grep -q htb])
 +OVS_WAIT_UNTIL([tc qdisc show dev ovs-p1 | grep -q htb])
-+
+ 
 +dnl Check the configuration.
 +m4_define([HTB_CONF], [rate 2Mbit ceil 3Mbit burst 375000b cburst 375000b])
 +AT_CHECK([tc class show dev ovs-p0 | grep -q 'class htb .* HTB_CONF'])
 +AT_CHECK([tc class show dev ovs-p1 | grep -q 'class htb .* HTB_CONF'])
-+
+ 
  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
- 
 @@ -1985,9 +2236,9 @@ OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
  dnl Check this output. We only see the latter two packets, not the first.
  AT_CHECK([cat ofctl_monitor.log], [0], [dnl
@@ -66866,7 +67781,7 @@ index f22d86e466..d4c34c1291 100644
      echo Request $i
      NS_CHECK_EXEC([at_ns1], [wget 10.1.1.64 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
  done
-@@ -6743,6 +7008,132 @@ AT_CHECK([ovs-ofctl dump-flows br0 | grep table=2, | OFPROTO_CLEAR_DURATION_IDLE
+@@ -6743,6 +7008,241 @@ AT_CHECK([ovs-ofctl dump-flows br0 | grep table=2, | OFPROTO_CLEAR_DURATION_IDLE
  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
  
@@ -66996,10 +67911,119 @@ index f22d86e466..d4c34c1291 100644
 +OVS_TRAFFIC_VSWITCHD_STOP
 +AT_CLEANUP
 +
++AT_SETUP([conntrack - ICMP from different source related with NAT])
++AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_TCPDUMP = no])
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++OVS_TRAFFIC_VSWITCHD_START()
++
++ADD_NAMESPACES(client, server)
++
++ADD_VETH(client, client, br0, "192.168.20.10/24", "00:00:00:00:20:10")
++ADD_VETH(server, server, br0, "192.168.10.20/24", "00:00:00:00:10:20")
++
++dnl Send traffic from client to CT, do DNAT if the traffic is new otherwise send it to server
++AT_DATA([flows.txt], [dnl
++table=0,ip,actions=ct(table=1,zone=42,nat)
++table=1,in_port=ovs-client,ip,ct_state=+trk+new,actions=ct(commit,table=2,zone=42,nat(dst=192.168.10.20)
++table=1,icmp,ct_state=+trk+rel-rpl,actions=ct(commit,table=2,zone=42,nat)
++table=1,ip,actions=resubmit(,2)
++table=2,in_port=ovs-client,ip,ct_state=+trk+new,actions=output:ovs-server
++table=2,in_port=ovs-client,icmp,ct_state=+trk+rel,actions=output:ovs-server
++table=2,in_port=ovs-server,icmp,ct_state=+trk+rel,actions=output:ovs-client
++table=2,in_port=ovs-server,ip,ct_state=+trk+rpl,actions=output:ovs-client
++])
++
++AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
++
++rm server.pcap
++tcpdump -l -U -i ovs-server -w server.pcap 2>tcpdump0_err &
++on_exit "kill $!"
++OVS_WAIT_UNTIL([grep "listening" tcpdump0_err])
++
++dnl Send UDP client->server
++AT_CHECK([ovs-ofctl packet-out br0 "in_port=ovs-client,\
++packet=00000000102000000000201008004500001C000040000A11C762C0A8140AC0A814140001000200080000,actions=resubmit(,0)"])
++dnl Send UDP response server->client
++AT_CHECK([ovs-ofctl packet-out br0 "in_port=ovs-server,\
++packet=00000000201000000000102008004500001C000040000A11D162C0A80A14C0A8140A0002000100080000,actions=resubmit(,0)"])
++dnl Fake router sending ICMP need frag router->server
++AT_CHECK([ovs-ofctl packet-out br0 "in_port=ovs-client,\
++packet=000000001020000000002000080045000038011F0000FF011140C0A81401C0A814140304F778000005784500001C000040000A11C762C0A81414C0A8140A0002000100080000,\
++actions=resubmit(,0)"
++])
++
++AT_CHECK([ovs-appctl revalidator/purge], [0])
++AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 | ofctl_strip | sort ], [0], [dnl
++ n_packets=3, n_bytes=154, reset_counts ip actions=ct(table=1,zone=42,nat)
++ table=1, n_packets=1, n_bytes=42, reset_counts ct_state=+new+trk,ip,in_port=1 actions=ct(commit,table=2,zone=42,nat(dst=192.168.10.20))
++ table=1, n_packets=1, n_bytes=42, reset_counts ip actions=resubmit(,2)
++ table=1, n_packets=1, n_bytes=70, reset_counts ct_state=+rel-rpl+trk,icmp actions=ct(commit,table=2,zone=42,nat)
++ table=2, n_packets=1, n_bytes=42, reset_counts ct_state=+new+trk,ip,in_port=1 actions=output:2
++ table=2, n_packets=1, n_bytes=42, reset_counts ct_state=+rpl+trk,ip,in_port=2 actions=output:1
++ table=2, n_packets=1, n_bytes=70, reset_counts ct_state=+rel+trk,icmp,in_port=1 actions=output:2
++ table=2, reset_counts ct_state=+rel+trk,icmp,in_port=2 actions=output:1
++OFPST_FLOW reply (OF1.5):
++])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "192.168.20.10"], [0], [dnl
++udp,orig=(src=192.168.20.10,dst=192.168.20.20,sport=1,dport=2),reply=(src=192.168.10.20,dst=192.168.20.10,sport=2,dport=1),zone=42
++])
++
++OVS_WAIT_UNTIL([ovs-pcap server.pcap | grep 000000001020000000002000])
++
++AT_CHECK([ovs-pcap server.pcap | grep 000000001020000000002000], [0], [dnl
++000000001020000000002000080045000038011f0000ff011b40c0a81401c0a80a140304f778000005784500001c000040000a11d162c0a80a14c0a8140a0002000100080000
++])
++
++dnl Check the ICMP error in reply direction
++AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=42])
++
++rm client.pcap
++tcpdump -l -U -i ovs-client -w client.pcap 2>tcpdump1_err &
++on_exit "kill $!"
++OVS_WAIT_UNTIL([grep "listening" tcpdump1_err])
++
++dnl Send UDP client->server
++AT_CHECK([ovs-ofctl packet-out br0 "in_port=ovs-client,\
++packet=00000000102000000000201008004500001C000040000A11C762C0A8140AC0A814140001000200080000,actions=resubmit(,0)"])
++dnl Fake router sending ICMP need frag router->client
++AT_CHECK([ovs-ofctl packet-out br0 "in_port=ovs-server,\
++packet=000000002010000000002000080045000038011F0000FF01114AC0A81401C0A8140A0304F778000005784500001C000040000A11D162C0A8140AC0A80A140001000200080000,\
++actions=resubmit(,0)"
++])
++
++AT_CHECK([ovs-appctl revalidator/purge], [0])
++AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 | ofctl_strip | sort ], [0], [dnl
++ n_packets=5, n_bytes=266, reset_counts ip actions=ct(table=1,zone=42,nat)
++ table=1, n_packets=1, n_bytes=70, reset_counts ct_state=+rel-rpl+trk,icmp actions=ct(commit,table=2,zone=42,nat)
++ table=1, n_packets=2, n_bytes=112, reset_counts ip actions=resubmit(,2)
++ table=1, n_packets=2, n_bytes=84, reset_counts ct_state=+new+trk,ip,in_port=1 actions=ct(commit,table=2,zone=42,nat(dst=192.168.10.20))
++ table=2, n_packets=1, n_bytes=42, reset_counts ct_state=+rpl+trk,ip,in_port=2 actions=output:1
++ table=2, n_packets=1, n_bytes=70, reset_counts ct_state=+rel+trk,icmp,in_port=1 actions=output:2
++ table=2, n_packets=1, n_bytes=70, reset_counts ct_state=+rel+trk,icmp,in_port=2 actions=output:1
++ table=2, n_packets=2, n_bytes=84, reset_counts ct_state=+new+trk,ip,in_port=1 actions=output:2
++OFPST_FLOW reply (OF1.5):
++])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "192.168.20.10"], [0], [dnl
++udp,orig=(src=192.168.20.10,dst=192.168.20.20,sport=1,dport=2),reply=(src=192.168.10.20,dst=192.168.20.10,sport=2,dport=1),zone=42
++])
++
++OVS_WAIT_UNTIL([ovs-pcap client.pcap | grep 000000002010000000002000])
++
++AT_CHECK([ovs-pcap client.pcap | grep 000000002010000000002000], [0], [dnl
++000000002010000000002000080045000038011f0000ff011137c0a81414c0a8140a0304f778000005784500001c000040000a11c762c0a8140ac0a814140001000200080000
++])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
  AT_BANNER([802.1ad])
  
  AT_SETUP([802.1ad - vlan_limit])
-@@ -7007,12 +7398,12 @@ dnl p1(at_ns1) interface
+@@ -7007,12 +7507,12 @@ dnl p1(at_ns1) interface
  NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
  
  dnl Check the expected nsh encapsulated packet on the egress interface
@@ -67018,7 +68042,7 @@ index f22d86e466..d4c34c1291 100644
  
  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
-@@ -7039,10 +7430,10 @@ dnl p1(at_ns1) interface
+@@ -7039,10 +7539,10 @@ dnl p1(at_ns1) interface
  NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 02 06 01 03 00 00 64 03 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
  
  dnl Check the expected de-capsulated TCP packet on the egress interface
@@ -67033,7 +68057,7 @@ index f22d86e466..d4c34c1291 100644
  
  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
-@@ -7072,12 +7463,12 @@ dnl p1(at_ns1) interface
+@@ -7072,12 +7572,12 @@ dnl p1(at_ns1) interface
  NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 02 06 01 03 00 01 00 03 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
  
  dnl Check the expected NSH packet with new fields in the header
@@ -67052,7 +68076,7 @@ index f22d86e466..d4c34c1291 100644
  
  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
-@@ -7106,23 +7497,23 @@ dnl First send packet from at_ns0 --> OVS with SPI=0x100 and SI=2
+@@ -7106,23 +7606,23 @@ dnl First send packet from at_ns0 --> OVS with SPI=0x100 and SI=2
  NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 02 06 01 03 00 01 00 02 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
  
  dnl Check for the above packet on p1 interface
diff --git a/SPECS/openvswitch2.17.spec b/SPECS/openvswitch2.17.spec
index da9a73b..8f789bb 100644
--- a/SPECS/openvswitch2.17.spec
+++ b/SPECS/openvswitch2.17.spec
@@ -63,7 +63,7 @@ Summary: Open vSwitch
 Group: System Environment/Daemons daemon/database/utilities
 URL: http://www.openvswitch.org/
 Version: 2.17.0
-Release: 78%{?dist}
+Release: 79%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -749,6 +749,18 @@ exit 0
 %endif
 
 %changelog
+* Thu Mar 02 2023 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-79
+- Merging upstream branch-2.17 [RH git: 3da76b1dd5]
+    Commit list:
+    132fa24b65 classifier: Fix missing masks on a final stage with ports trie.
+    8661abd4c4 ofproto: Fix re-creation of tunnel backing interfaces on restart.
+    638441e981 ovs-actions: Correct typo in ovs-actions man page.
+    3c4bd63bca ofproto-ipfix: Use per-domain template timeouts.
+    d2583ccb74 ofproto-dpif-upcall: Use last known stats ukey stats on revalidate missed dp flows.
+    705190d88e conntrack: Properly unNAT inner header of related traffic. (#2137754)
+    d87b6180ec dpctl: Fix memory leak in flush conntrack.
+
+
 * Tue Feb 21 2023 Timothy Redaelli <tredaelli@redhat.com> - 2.17.0-78
 - redhat: add a workaround for meson [RH git: 39c6e2a48b]
     Currently, fast-datapath-rhel-8 is aligned to RHEL 8.0, with an