diff --git a/SOURCES/openvswitch-2.17.0.patch b/SOURCES/openvswitch-2.17.0.patch
index 7720d72..9107b79 100644
--- a/SOURCES/openvswitch-2.17.0.patch
+++ b/SOURCES/openvswitch-2.17.0.patch
@@ -51131,10 +51131,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 +51469,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 +51478,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 +51497,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 +51524,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 +51539,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 +51551,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 +51580,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 +51645,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 +58371,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 +58550,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 +58559,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 +58568,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 +58611,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 +58620,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 +58629,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 +58645,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 +58667,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 +58689,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 +58702,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 +58711,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 +58720,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 +58777,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 +58885,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 +58918,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, "");
  }
@@ -66147,7 +66775,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 +67322,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 +67340,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 +67372,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 +67493,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 +67623,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 +67754,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 +67769,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 +67788,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 9e8720c..3d767cd 100644
--- a/SPECS/openvswitch2.17.spec
+++ b/SPECS/openvswitch2.17.spec
@@ -57,7 +57,7 @@ Summary: Open vSwitch
 Group: System Environment/Daemons daemon/database/utilities
 URL: http://www.openvswitch.org/
 Version: 2.17.0
-Release: 65%{?dist}
+Release: 66%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -748,6 +748,15 @@ exit 0
 %endif
 
 %changelog
+* Tue Feb 21 2023 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-66
+- Merging upstream branch-2.17 [RH git: 05aa9c16ae]
+    Commit list:
+    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.
+
+
 * Mon Feb 13 2023 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-65
 - Merging upstream branch-2.17 [RH git: 2011158f64]
     Commit list: