diff --git a/SOURCES/openvswitch-2.17.0.patch b/SOURCES/openvswitch-2.17.0.patch
index 0ef8df9..954f7cf 100644
--- a/SOURCES/openvswitch-2.17.0.patch
+++ b/SOURCES/openvswitch-2.17.0.patch
@@ -51391,6 +51391,82 @@ index d3b4601858..7f3e63c384 100644
  }
  
  static bool
+diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h
+index dfdf4e676b..581f517ad1 100644
+--- a/lib/conntrack-private.h
++++ b/lib/conntrack-private.h
+@@ -48,6 +48,12 @@ struct ct_endpoint {
+  * hashing in ct_endpoint_hash_add(). */
+ BUILD_ASSERT_DECL(sizeof(struct ct_endpoint) == sizeof(union ct_addr) + 4);
+ 
++enum key_dir {
++    CT_DIR_FWD = 0,
++    CT_DIR_REV,
++    CT_DIRS,
++};
++
+ /* Changes to this structure need to be reflected in conn_key_hash()
+  * and conn_key_cmp(). */
+ struct conn_key {
+@@ -86,21 +92,19 @@ struct alg_exp_node {
+     bool nat_rpl_dst;
+ };
+ 
+-enum OVS_PACKED_ENUM ct_conn_type {
+-    CT_CONN_TYPE_DEFAULT,
+-    CT_CONN_TYPE_UN_NAT,
++struct conn_key_node {
++    enum key_dir dir;
++    struct conn_key key;
++    struct cmap_node cm_node;
+ };
+ 
+ struct conn {
+     /* Immutable data. */
+-    struct conn_key key;
+-    struct conn_key rev_key;
++    struct conn_key_node key_node[CT_DIRS];
+     struct conn_key parent_key; /* Only used for orig_tuple support. */
+     struct ovs_list exp_node;
+-    struct cmap_node cm_node;
+     uint16_t nat_action;
+     char *alg;
+-    struct conn *nat_conn; /* The NAT 'conn' context, if there is one. */
+ 
+     /* Mutable data. */
+     struct ovs_mutex lock; /* Guards all mutable fields. */
+@@ -120,7 +124,6 @@ struct conn {
+ 
+     /* Immutable data. */
+     bool alg_related; /* True if alg data connection. */
+-    enum ct_conn_type conn_type;
+ 
+     uint32_t tp_id; /* Timeout policy ID. */
+ };
+diff --git a/lib/conntrack-tp.c b/lib/conntrack-tp.c
+index a586d3a8d3..2bdda67110 100644
+--- a/lib/conntrack-tp.c
++++ b/lib/conntrack-tp.c
+@@ -276,7 +276,8 @@ conn_update_expiration(struct conntrack *ct, struct conn *conn,
+     ovs_mutex_lock(&conn->lock);
+     VLOG_DBG_RL(&rl, "Update timeout %s zone=%u with policy id=%d "
+                 "val=%u sec.",
+-                ct_timeout_str[tm], conn->key.zone, conn->tp_id, val);
++                ct_timeout_str[tm], conn->key_node[CT_DIR_FWD].key.zone,
++                conn->tp_id, val);
+ 
+     conn_update_expiration__(ct, conn, tm, now, val);
+ }
+@@ -307,7 +308,8 @@ conn_init_expiration(struct conntrack *ct, struct conn *conn,
+     }
+ 
+     VLOG_DBG_RL(&rl, "Init timeout %s zone=%u with policy id=%d val=%u sec.",
+-                ct_timeout_str[tm], conn->key.zone, conn->tp_id, val);
++                ct_timeout_str[tm], conn->key_node[CT_DIR_FWD].key.zone,
++                conn->tp_id, val);
+ 
+     conn_init_expiration__(ct, conn, tm, now, val);
+ }
 diff --git a/lib/conntrack-tp.h b/lib/conntrack-tp.h
 index 4d411d19fd..7ece2eae2f 100644
 --- a/lib/conntrack-tp.h
@@ -51412,10 +51488,262 @@ index 4d411d19fd..7ece2eae2f 100644
  int timeout_policy_update(struct conntrack *ct, struct timeout_policy *tp);
  int timeout_policy_delete(struct conntrack *ct, uint32_t tp_id);
 diff --git a/lib/conntrack.c b/lib/conntrack.c
-index 33a1a92953..fff8e77db1 100644
+index 33a1a92953..a85e9ba886 100644
 --- a/lib/conntrack.c
 +++ b/lib/conntrack.c
-@@ -723,109 +723,59 @@ handle_alg_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,
+@@ -94,14 +94,13 @@ static bool valid_new(struct dp_packet *pkt, struct conn_key *);
+ static struct conn *new_conn(struct conntrack *ct, struct dp_packet *pkt,
+                              struct conn_key *, long long now,
+                              uint32_t tp_id);
+-static void delete_conn_cmn(struct conn *);
++static void delete_conn__(struct conn *);
+ static void delete_conn(struct conn *);
+-static void delete_conn_one(struct conn *conn);
+ static enum ct_update_res conn_update(struct conntrack *ct, struct conn *conn,
+                                       struct dp_packet *pkt,
+                                       struct conn_lookup_ctx *ctx,
+                                       long long now);
+-static bool conn_expired(struct conn *, long long now);
++static bool conn_expired(const struct conn *, long long now);
+ static void set_mark(struct dp_packet *, struct conn *,
+                      uint32_t val, uint32_t mask);
+ static void set_label(struct dp_packet *, struct conn *,
+@@ -110,8 +109,7 @@ static void set_label(struct dp_packet *, struct conn *,
+ static void *clean_thread_main(void *f_);
+ 
+ static bool
+-nat_get_unique_tuple(struct conntrack *ct, const struct conn *conn,
+-                     struct conn *nat_conn,
++nat_get_unique_tuple(struct conntrack *ct, struct conn *conn,
+                      const struct nat_action_info_t *nat_info);
+ 
+ static uint8_t
+@@ -205,7 +203,7 @@ static alg_helper alg_helpers[] = {
+ #define ALG_WC_SRC_PORT 0
+ 
+ /* If the total number of connections goes above this value, no new connections
+- * are accepted; this is for CT_CONN_TYPE_DEFAULT connections. */
++ * are accepted. */
+ #define DEFAULT_N_CONN_LIMIT 3000000
+ 
+ /* Does a member by member comparison of two conn_keys; this
+@@ -231,61 +229,6 @@ conn_key_cmp(const struct conn_key *key1, const struct conn_key *key2)
+     return 1;
+ }
+ 
+-static void
+-ct_print_conn_info(const struct conn *c, const char *log_msg,
+-                   enum vlog_level vll, bool force, bool rl_on)
+-{
+-#define CT_VLOG(RL_ON, LEVEL, ...)                                          \
+-    do {                                                                    \
+-        if (RL_ON) {                                                        \
+-            static struct vlog_rate_limit rl_ = VLOG_RATE_LIMIT_INIT(5, 5); \
+-            vlog_rate_limit(&this_module, LEVEL, &rl_, __VA_ARGS__);        \
+-        } else {                                                            \
+-            vlog(&this_module, LEVEL, __VA_ARGS__);                         \
+-        }                                                                   \
+-    } while (0)
+-
+-    if (OVS_UNLIKELY(force || vlog_is_enabled(&this_module, vll))) {
+-        if (c->key.dl_type == htons(ETH_TYPE_IP)) {
+-            CT_VLOG(rl_on, vll, "%s: src ip "IP_FMT" dst ip "IP_FMT" rev src "
+-                    "ip "IP_FMT" rev dst ip "IP_FMT" src/dst ports "
+-                    "%"PRIu16"/%"PRIu16" rev src/dst ports "
+-                    "%"PRIu16"/%"PRIu16" zone/rev zone "
+-                    "%"PRIu16"/%"PRIu16" nw_proto/rev nw_proto "
+-                    "%"PRIu8"/%"PRIu8, log_msg,
+-                    IP_ARGS(c->key.src.addr.ipv4),
+-                    IP_ARGS(c->key.dst.addr.ipv4),
+-                    IP_ARGS(c->rev_key.src.addr.ipv4),
+-                    IP_ARGS(c->rev_key.dst.addr.ipv4),
+-                    ntohs(c->key.src.port), ntohs(c->key.dst.port),
+-                    ntohs(c->rev_key.src.port), ntohs(c->rev_key.dst.port),
+-                    c->key.zone, c->rev_key.zone, c->key.nw_proto,
+-                    c->rev_key.nw_proto);
+-        } else {
+-            char ip6_s[INET6_ADDRSTRLEN];
+-            inet_ntop(AF_INET6, &c->key.src.addr.ipv6, ip6_s, sizeof ip6_s);
+-            char ip6_d[INET6_ADDRSTRLEN];
+-            inet_ntop(AF_INET6, &c->key.dst.addr.ipv6, ip6_d, sizeof ip6_d);
+-            char ip6_rs[INET6_ADDRSTRLEN];
+-            inet_ntop(AF_INET6, &c->rev_key.src.addr.ipv6, ip6_rs,
+-                      sizeof ip6_rs);
+-            char ip6_rd[INET6_ADDRSTRLEN];
+-            inet_ntop(AF_INET6, &c->rev_key.dst.addr.ipv6, ip6_rd,
+-                      sizeof ip6_rd);
+-
+-            CT_VLOG(rl_on, vll, "%s: src ip %s dst ip %s rev src ip %s"
+-                    " rev dst ip %s src/dst ports %"PRIu16"/%"PRIu16
+-                    " rev src/dst ports %"PRIu16"/%"PRIu16" zone/rev zone "
+-                    "%"PRIu16"/%"PRIu16" nw_proto/rev nw_proto "
+-                    "%"PRIu8"/%"PRIu8, log_msg, ip6_s, ip6_d, ip6_rs,
+-                    ip6_rd, ntohs(c->key.src.port), ntohs(c->key.dst.port),
+-                    ntohs(c->rev_key.src.port), ntohs(c->rev_key.dst.port),
+-                    c->key.zone, c->rev_key.zone, c->key.nw_proto,
+-                    c->rev_key.nw_proto);
+-        }
+-    }
+-}
+-
+ /* Initializes the connection tracker 'ct'.  The caller is responsible for
+  * calling 'conntrack_destroy()', when the instance is not needed anymore */
+ struct conntrack *
+@@ -444,34 +387,31 @@ zone_limit_delete(struct conntrack *ct, uint16_t zone)
+ }
+ 
+ static void
+-conn_clean_cmn(struct conntrack *ct, struct conn *conn)
++conn_clean(struct conntrack *ct, struct conn *conn)
+     OVS_REQUIRES(ct->ct_lock)
+ {
++    uint32_t hash;
++
++    if (conn->cleaned) {
++        return;
++    }
++
+     if (conn->alg) {
+-        expectation_clean(ct, &conn->key);
++        expectation_clean(ct, &conn->key_node[CT_DIR_FWD].key);
+     }
+ 
+-    uint32_t hash = conn_key_hash(&conn->key, ct->hash_basis);
+-    cmap_remove(&ct->conns, &conn->cm_node, hash);
++    hash = conn_key_hash(&conn->key_node[CT_DIR_FWD].key, ct->hash_basis);
++    cmap_remove(&ct->conns, &conn->key_node[CT_DIR_FWD].cm_node, hash);
+ 
+     struct zone_limit *zl = zone_limit_lookup(ct, conn->admit_zone);
+     if (zl && zl->czl.zone_limit_seq == conn->zone_limit_seq) {
+         zl->czl.count--;
+     }
+-}
+ 
+-/* Must be called with 'conn' of 'conn_type' CT_CONN_TYPE_DEFAULT.  Also
+- * removes the associated nat 'conn' from the lookup datastructures. */
+-static void
+-conn_clean(struct conntrack *ct, struct conn *conn)
+-    OVS_REQUIRES(ct->ct_lock)
+-{
+-    ovs_assert(conn->conn_type == CT_CONN_TYPE_DEFAULT);
+-
+-    conn_clean_cmn(ct, conn);
+-    if (conn->nat_conn) {
+-        uint32_t hash = conn_key_hash(&conn->nat_conn->key, ct->hash_basis);
+-        cmap_remove(&ct->conns, &conn->nat_conn->cm_node, hash);
++    if (conn->nat_action) {
++        hash = conn_key_hash(&conn->key_node[CT_DIR_REV].key,
++                             ct->hash_basis);
++        cmap_remove(&ct->conns, &conn->key_node[CT_DIR_REV].cm_node, hash);
+     }
+     ovs_list_remove(&conn->exp_node);
+     conn->cleaned = true;
+@@ -479,33 +419,27 @@ conn_clean(struct conntrack *ct, struct conn *conn)
+     atomic_count_dec(&ct->n_conn);
+ }
+ 
+-static void
+-conn_clean_one(struct conntrack *ct, struct conn *conn)
+-    OVS_REQUIRES(ct->ct_lock)
+-{
+-    conn_clean_cmn(ct, conn);
+-    if (conn->conn_type == CT_CONN_TYPE_DEFAULT) {
+-        ovs_list_remove(&conn->exp_node);
+-        conn->cleaned = true;
+-        atomic_count_dec(&ct->n_conn);
+-    }
+-    ovsrcu_postpone(delete_conn_one, conn);
+-}
+-
+ /* Destroys the connection tracker 'ct' and frees all the allocated memory.
+  * The caller of this function must already have shut down packet input
+  * and PMD threads (which would have been quiesced).  */
+ void
+ conntrack_destroy(struct conntrack *ct)
+ {
++    struct conn_key_node *keyn;
+     struct conn *conn;
+     latch_set(&ct->clean_thread_exit);
+     pthread_join(ct->clean_thread, NULL);
+     latch_destroy(&ct->clean_thread_exit);
+ 
+     ovs_mutex_lock(&ct->ct_lock);
+-    CMAP_FOR_EACH (conn, cm_node, &ct->conns) {
+-        conn_clean_one(ct, conn);
++    CMAP_FOR_EACH (keyn, cm_node, &ct->conns) {
++        if (keyn->dir == CT_DIR_FWD) {
++            conn = CONTAINER_OF(keyn, struct conn, key_node[CT_DIR_FWD]);
++        } else {
++            conn = CONTAINER_OF(keyn, struct conn, key_node[CT_DIR_REV]);
++        }
++
++        conn_clean(ct, conn);
+     }
+     cmap_destroy(&ct->conns);
+ 
+@@ -544,31 +478,39 @@ conn_key_lookup(struct conntrack *ct, const struct conn_key *key,
+                 uint32_t hash, long long now, struct conn **conn_out,
+                 bool *reply)
+ {
+-    struct conn *conn;
++    struct conn_key_node *keyn;
++    struct conn *conn = NULL;
+     bool found = false;
+ 
+-    CMAP_FOR_EACH_WITH_HASH (conn, cm_node, hash, &ct->conns) {
+-        if (!conn_key_cmp(&conn->key, key) && !conn_expired(conn, now)) {
+-            found = true;
+-            if (reply) {
+-                *reply = false;
+-            }
+-            break;
++    CMAP_FOR_EACH_WITH_HASH (keyn, cm_node, hash, &ct->conns) {
++        if (keyn->dir == CT_DIR_FWD) {
++            conn = CONTAINER_OF(keyn, struct conn, key_node[CT_DIR_FWD]);
++        } else {
++            conn = CONTAINER_OF(keyn, struct conn, key_node[CT_DIR_REV]);
+         }
+-        if (!conn_key_cmp(&conn->rev_key, key) && !conn_expired(conn, now)) {
+-            found = true;
+-            if (reply) {
+-                *reply = true;
++
++        if (conn_expired(conn, now)) {
++            continue;
++        }
++
++        for (int i = CT_DIR_FWD; i < CT_DIRS; i++) {
++            if (!conn_key_cmp(&conn->key_node[i].key, key)) {
++                found = true;
++                if (reply) {
++                    *reply = (i == CT_DIR_REV);
++                }
++                goto out_found;
+             }
+-            break;
+         }
+     }
+ 
++out_found:
+     if (found && conn_out) {
+         *conn_out = conn;
+     } else if (conn_out) {
+         *conn_out = NULL;
+     }
++
+     return found;
+ }
+ 
+@@ -602,7 +544,7 @@ write_ct_md(struct dp_packet *pkt, uint16_t zone, const struct conn *conn,
+         if (conn->alg_related) {
+             key = &conn->parent_key;
+         } else {
+-            key = &conn->key;
++            key = &conn->key_node[CT_DIR_FWD].key;
+         }
+     } else if (alg_exp) {
+         pkt->md.ct_mark = alg_exp->parent_mark;
+@@ -723,109 +665,59 @@ handle_alg_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,
  }
  
  static void
@@ -51559,7 +51887,7 @@ index 33a1a92953..fff8e77db1 100644
  {
      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)
+@@ -834,98 +726,78 @@ 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;
  
@@ -51600,13 +51928,13 @@ index 33a1a92953..fff8e77db1 100644
 +    /* Reverse the key for inner packet. */
 +    struct conn_key rev_key = *key;
 +    conn_key_reverse(&rev_key);
-+
+ 
+-        reverse_pat_packet(pkt, conn);
 +    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);
@@ -51665,7 +51993,8 @@ index 33a1a92953..fff8e77db1 100644
 -                                 nh6->ip6_dst.be32,
 -                                 &conn->key.src.addr.ipv6, true);
 -        }
-+    struct conn_key *key = reply ? &conn->key : &conn->rev_key;
++    enum key_dir dir = reply ? CT_DIR_FWD : CT_DIR_REV;
++    struct conn_key *key = &conn->key_node[dir].key;
 +    uint16_t nat_action = reply ? nat_action_reverse(conn->nat_action)
 +                                : conn->nat_action;
  
@@ -51691,14 +52020,14 @@ index 33a1a92953..fff8e77db1 100644
 +    } 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);
@@ -51710,16 +52039,133 @@ index 33a1a92953..fff8e77db1 100644
          }
      }
  }
-@@ -1044,7 +973,7 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
-                 memcpy(nc, nat_conn, sizeof *nc);
+@@ -937,7 +809,7 @@ conn_seq_skew_set(struct conntrack *ct, const struct conn *conn_in,
+ {
+     struct conn *conn;
+     ovs_mutex_unlock(&conn_in->lock);
+-    conn_lookup(ct, &conn_in->key, now, &conn, NULL);
++    conn_lookup(ct, &conn_in->key_node[CT_DIR_FWD].key, now, &conn, NULL);
+     ovs_mutex_lock(&conn_in->lock);
+ 
+     if (conn && seq_skew) {
+@@ -975,7 +847,6 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
+     OVS_REQUIRES(ct->ct_lock)
+ {
+     struct conn *nc = NULL;
+-    struct conn *nat_conn = NULL;
+ 
+     if (!valid_new(pkt, &ctx->key)) {
+         pkt->md.ct_state = CS_INVALID;
+@@ -989,6 +860,7 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
+     }
+ 
+     if (commit) {
++        struct conn_key_node *fwd_key_node, *rev_key_node;
+         struct zone_limit *zl = zone_limit_lookup_or_default(ct,
+                                                              ctx->key.zone);
+         if (zl && zl->czl.count >= zl->czl.limit) {
+@@ -1003,9 +875,12 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
+         }
+ 
+         nc = new_conn(ct, pkt, &ctx->key, now, tp_id);
+-        memcpy(&nc->key, &ctx->key, sizeof nc->key);
+-        memcpy(&nc->rev_key, &nc->key, sizeof nc->rev_key);
+-        conn_key_reverse(&nc->rev_key);
++        fwd_key_node = &nc->key_node[CT_DIR_FWD];
++        rev_key_node = &nc->key_node[CT_DIR_REV];
++        memcpy(&fwd_key_node->key, &ctx->key, sizeof fwd_key_node->key);
++        memcpy(&rev_key_node->key, &fwd_key_node->key,
++               sizeof rev_key_node->key);
++        conn_key_reverse(&rev_key_node->key);
+ 
+         if (ct_verify_helper(helper, ct_alg_ctl)) {
+             nc->alg = nullable_xstrdup(helper);
+@@ -1020,45 +895,33 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
+ 
+         if (nat_action_info) {
+             nc->nat_action = nat_action_info->nat_action;
+-            nat_conn = xzalloc(sizeof *nat_conn);
+ 
+             if (alg_exp) {
+                 if (alg_exp->nat_rpl_dst) {
+-                    nc->rev_key.dst.addr = alg_exp->alg_nat_repl_addr;
++                    rev_key_node->key.dst.addr = alg_exp->alg_nat_repl_addr;
+                     nc->nat_action = NAT_ACTION_SRC;
+                 } else {
+-                    nc->rev_key.src.addr = alg_exp->alg_nat_repl_addr;
++                    rev_key_node->key.src.addr = alg_exp->alg_nat_repl_addr;
+                     nc->nat_action = NAT_ACTION_DST;
+                 }
+             } else {
+-                memcpy(nat_conn, nc, sizeof *nat_conn);
+-                bool nat_res = nat_get_unique_tuple(ct, nc, nat_conn,
+-                                                    nat_action_info);
++                bool nat_res = nat_get_unique_tuple(ct, nc, nat_action_info);
+ 
+                 if (!nat_res) {
+                     goto nat_res_exhaustion;
+                 }
+-
+-                /* Update nc with nat adjustments made to nat_conn by
+-                 * nat_get_unique_tuple(). */
+-                memcpy(nc, nat_conn, sizeof *nc);
              }
  
 -            nat_packet(pkt, nc, 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;
+-            nat_conn->nat_action = 0;
+-            nat_conn->alg = NULL;
+-            nat_conn->nat_conn = NULL;
+-            uint32_t nat_hash = conn_key_hash(&nat_conn->key, ct->hash_basis);
+-            cmap_insert(&ct->conns, &nat_conn->cm_node, nat_hash);
 +            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,
++            uint32_t rev_hash = conn_key_hash(&rev_key_node->key,
++                                              ct->hash_basis);
++            cmap_insert(&ct->conns, &rev_key_node->cm_node, rev_hash);
+         }
+ 
+-        nc->nat_conn = nat_conn;
+         ovs_mutex_init_adaptive(&nc->lock);
+-        nc->conn_type = CT_CONN_TYPE_DEFAULT;
+-        cmap_insert(&ct->conns, &nc->cm_node, ctx->hash);
++        fwd_key_node->dir = CT_DIR_FWD;
++        rev_key_node->dir = CT_DIR_REV;
++        cmap_insert(&ct->conns, &fwd_key_node->cm_node, ctx->hash);
+         atomic_count_inc(&ct->n_conn);
+         ctx->conn = nc; /* For completeness. */
+         if (zl) {
+@@ -1078,9 +941,8 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
+      * firewall rules or a separate firewall.  Also using zone partitioning
+      * can limit DoS impact. */
+ nat_res_exhaustion:
+-    free(nat_conn);
+     ovs_list_remove(&nc->exp_node);
+-    delete_conn_cmn(nc);
++    delete_conn__(nc);
+     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+     VLOG_WARN_RL(&rl, "Unable to NAT due to tuple space exhaustion - "
+                  "if DoS attack, use firewalling and/or zone partitioning.");
+@@ -1092,7 +954,6 @@ conn_update_state(struct conntrack *ct, struct dp_packet *pkt,
+                   struct conn_lookup_ctx *ctx, struct conn *conn,
+                   long long now)
+ {
+-    ovs_assert(conn->conn_type == CT_CONN_TYPE_DEFAULT);
+     bool create_new_conn = false;
+ 
+     if (ctx->icmp_related) {
+@@ -1120,7 +981,8 @@ conn_update_state(struct conntrack *ct, struct dp_packet *pkt,
+             break;
+         case CT_UPDATE_NEW:
+             ovs_mutex_lock(&ct->ct_lock);
+-            if (conn_lookup(ct, &conn->key, now, NULL, NULL)) {
++            if (conn_lookup(ct, &conn->key_node[CT_DIR_FWD].key,
++                            now, NULL, NULL)) {
+                 conn_clean(ct, conn);
+             }
+             ovs_mutex_unlock(&ct->ct_lock);
+@@ -1148,11 +1010,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);
          }
@@ -51733,7 +52179,66 @@ index 33a1a92953..fff8e77db1 100644
      }
  }
  
-@@ -1526,14 +1452,14 @@ set_label(struct dp_packet *pkt, struct conn *conn,
+@@ -1300,8 +1159,10 @@ initial_conn_lookup(struct conntrack *ct, struct conn_lookup_ctx *ctx,
+ 
+     if (natted) {
+         if (OVS_LIKELY(ctx->conn)) {
++            enum key_dir dir;
+             ctx->reply = !ctx->reply;
+-            ctx->key = ctx->reply ? ctx->conn->rev_key : ctx->conn->key;
++            dir = ctx->reply ? CT_DIR_REV : CT_DIR_FWD;
++            ctx->key = ctx->conn->key_node[dir].key;
+             ctx->hash = conn_key_hash(&ctx->key, ct->hash_basis);
+         } else {
+             /* A lookup failure does not necessarily imply that an
+@@ -1335,32 +1196,14 @@ process_one(struct conntrack *ct, struct dp_packet *pkt,
+     /* Delete found entry if in wrong direction. 'force' implies commit. */
+     if (OVS_UNLIKELY(force && ctx->reply && conn)) {
+         ovs_mutex_lock(&ct->ct_lock);
+-        if (conn_lookup(ct, &conn->key, now, NULL, NULL)) {
++        if (conn_lookup(ct, &conn->key_node[CT_DIR_FWD].key,
++                        now, NULL, NULL)) {
+             conn_clean(ct, conn);
+         }
+         ovs_mutex_unlock(&ct->ct_lock);
+         conn = NULL;
+     }
+ 
+-    if (OVS_LIKELY(conn)) {
+-        if (conn->conn_type == CT_CONN_TYPE_UN_NAT) {
+-
+-            ctx->reply = true;
+-            struct conn *rev_conn = conn;  /* Save for debugging. */
+-            uint32_t hash = conn_key_hash(&conn->rev_key, ct->hash_basis);
+-            conn_key_lookup(ct, &ctx->key, hash, now, &conn, &ctx->reply);
+-
+-            if (!conn) {
+-                pkt->md.ct_state |= CS_INVALID;
+-                write_ct_md(pkt, zone, NULL, NULL, NULL);
+-                char *log_msg = xasprintf("Missing parent conn %p", rev_conn);
+-                ct_print_conn_info(rev_conn, log_msg, VLL_INFO, true, true);
+-                free(log_msg);
+-                return;
+-            }
+-        }
+-    }
+-
+     enum ct_alg_ctl_type ct_alg_ctl = get_alg_ctl_type(pkt, tp_src, tp_dst,
+                                                        helper);
+ 
+@@ -1453,8 +1296,9 @@ conntrack_execute(struct conntrack *ct, struct dp_packet_batch *pkt_batch,
+         struct conn *conn = packet->md.conn;
+         if (OVS_UNLIKELY(packet->md.ct_state == CS_INVALID)) {
+             write_ct_md(packet, zone, NULL, NULL, NULL);
+-        } else if (conn && conn->key.zone == zone && !force
+-                   && !get_alg_ctl_type(packet, tp_src, tp_dst, helper)) {
++        } else if (conn &&
++                   conn->key_node[CT_DIR_FWD].key.zone == zone && !force &&
++                   !get_alg_ctl_type(packet, tp_src, tp_dst, helper)) {
+             process_one_fast(zone, setmark, setlabel, nat_action_info,
+                              conn, packet);
+         } else if (OVS_UNLIKELY(!conn_key_extract(ct, packet, dl_type, &ctx,
+@@ -1526,14 +1370,14 @@ set_label(struct dp_packet *pkt, struct conn *conn,
  static long long
  ct_sweep(struct conntrack *ct, long long now, size_t limit)
  {
@@ -51750,16 +52255,36 @@ index 33a1a92953..fff8e77db1 100644
              ovs_mutex_lock(&conn->lock);
              if (now < conn->expiration || count >= limit) {
                  min_expiration = MIN(min_expiration, conn->expiration);
-@@ -2242,7 +2168,7 @@ nat_range_hash(const struct conn *conn, uint32_t basis,
+@@ -2234,7 +2078,7 @@ nat_ipv6_addr_increment(struct in6_addr *ipv6, uint32_t increment)
+ }
+ 
+ static uint32_t
+-nat_range_hash(const struct conn *conn, uint32_t basis,
++nat_range_hash(const struct conn_key *key, uint32_t basis,
+                const struct nat_action_info_t *nat_info)
+ {
+     uint32_t hash = basis;
+@@ -2242,13 +2086,13 @@ 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,
 -                    (nat_info->max_port << 16)
 +                    ((uint32_t) nat_info->max_port << 16)
                      | nat_info->min_port);
-     hash = ct_endpoint_hash_add(hash, &conn->key.src);
-     hash = ct_endpoint_hash_add(hash, &conn->key.dst);
-@@ -2265,8 +2191,16 @@ set_sport_range(const struct nat_action_info_t *ni, const struct conn_key *k,
+-    hash = ct_endpoint_hash_add(hash, &conn->key.src);
+-    hash = ct_endpoint_hash_add(hash, &conn->key.dst);
+-    hash = hash_add(hash, (OVS_FORCE uint32_t) conn->key.dl_type);
+-    hash = hash_add(hash, conn->key.nw_proto);
+-    hash = hash_add(hash, conn->key.zone);
++    hash = ct_endpoint_hash_add(hash, &key->src);
++    hash = ct_endpoint_hash_add(hash, &key->dst);
++    hash = hash_add(hash, (OVS_FORCE uint32_t) key->dl_type);
++    hash = hash_add(hash, key->nw_proto);
++    hash = hash_add(hash, key->zone);
+ 
+     /* The purpose of the second parameter is to distinguish hashes of data of
+      * different length; our data always has the same length so there is no
+@@ -2265,8 +2109,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);
@@ -51778,12 +52303,33 @@ index 33a1a92953..fff8e77db1 100644
      } else {
          *min = ni->min_port;
          *max = ni->max_port;
-@@ -2389,6 +2323,26 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
+@@ -2314,7 +2166,7 @@ get_addr_in_range(union ct_addr *min, union ct_addr *max,
+ }
+ 
+ static void
+-get_initial_addr(const struct conn *conn, union ct_addr *min,
++get_initial_addr(const struct conn_key *key, union ct_addr *min,
+                  union ct_addr *max, union ct_addr *curr,
+                  uint32_t hash, bool ipv4,
+                  const struct nat_action_info_t *nat_info)
+@@ -2324,9 +2176,9 @@ get_initial_addr(const struct conn *conn, union ct_addr *min,
+     /* All-zero case. */
+     if (!memcmp(min, &zero_ip, sizeof *min)) {
+         if (nat_info->nat_action & NAT_ACTION_SRC) {
+-            *curr = conn->key.src.addr;
++            *curr = key->src.addr;
+         } else if (nat_info->nat_action & NAT_ACTION_DST) {
+-            *curr = conn->key.dst.addr;
++            *curr = key->dst.addr;
+         }
+     } else {
+         get_addr_in_range(min, max, curr, hash, ipv4);
+@@ -2389,6 +2241,25 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
      return exhausted;
  }
  
 +static bool
-+nat_get_unique_l4(struct conntrack *ct, struct conn *nat_conn,
++nat_get_unique_l4(struct conntrack *ct, struct conn_key *rev_key,
 +                  ovs_be16 *port, uint16_t curr, uint16_t min,
 +                  uint16_t max)
 +{
@@ -51791,8 +52337,7 @@ index 33a1a92953..fff8e77db1 100644
 +
 +    FOR_EACH_PORT_IN_RANGE (curr, min, max) {
 +        *port = htons(curr);
-+        if (!conn_lookup(ct, &nat_conn->rev_key,
-+                         time_msec(), NULL, NULL)) {
++        if (!conn_lookup(ct, rev_key, time_msec(), NULL, NULL)) {
 +            return true;
 +        }
 +    }
@@ -51805,7 +52350,7 @@ index 33a1a92953..fff8e77db1 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 +2357,11 @@ next_addr_in_range_guarded(union ct_addr *curr, union ct_addr *min,
+@@ -2403,61 +2274,72 @@ 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).
@@ -51820,19 +52365,61 @@ index 33a1a92953..fff8e77db1 100644
   *
   * If none can be found, return exhaustion to the caller. */
  static bool
-@@ -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,
+-nat_get_unique_tuple(struct conntrack *ct, const struct conn *conn,
+-                     struct conn *nat_conn,
++nat_get_unique_tuple(struct conntrack *ct, struct conn *conn,
+                      const struct nat_action_info_t *nat_info)
+ {
++    struct conn_key *fwd_key = &conn->key_node[CT_DIR_FWD].key;
++    struct conn_key *rev_key = &conn->key_node[CT_DIR_REV].key;
+     union ct_addr min_addr = {0}, max_addr = {0}, curr_addr = {0},
+                   guard_addr = {0};
+-    uint32_t hash = nat_range_hash(conn, ct->hash_basis, nat_info);
+-    bool pat_proto = conn->key.nw_proto == IPPROTO_TCP ||
+-                     conn->key.nw_proto == IPPROTO_UDP;
++    bool pat_proto = fwd_key->nw_proto == IPPROTO_TCP ||
++                     fwd_key->nw_proto == IPPROTO_UDP;
+     uint16_t min_dport, max_dport, curr_dport;
+     uint16_t min_sport, max_sport, curr_sport;
++    uint32_t hash;
+ 
++    hash = nat_range_hash(fwd_key, ct->hash_basis, nat_info);
+     min_addr = nat_info->min_addr;
+     max_addr = nat_info->max_addr;
+ 
+-    get_initial_addr(conn, &min_addr, &max_addr, &curr_addr, hash,
+-                     (conn->key.dl_type == htons(ETH_TYPE_IP)), nat_info);
++    get_initial_addr(fwd_key, &min_addr, &max_addr, &curr_addr, hash,
++                     (fwd_key->dl_type == htons(ETH_TYPE_IP)), nat_info);
+ 
+     /* Save the address we started from so that
+      * we can stop once we reach it. */
+     guard_addr = curr_addr;
+ 
+-    set_sport_range(nat_info, &conn->key, hash, &curr_sport,
++    set_sport_range(nat_info, fwd_key, hash, &curr_sport,
+                     &min_sport, &max_sport);
+-    set_dport_range(nat_info, &conn->key, hash, &curr_dport,
++    set_dport_range(nat_info, fwd_key, hash, &curr_dport,
                      &min_dport, &max_dport);
  
 +    if (pat_proto) {
-+        nat_conn->rev_key.src.port = htons(curr_dport);
-+        nat_conn->rev_key.dst.port = htons(curr_sport);
++        rev_key->src.port = htons(curr_dport);
++        rev_key->dst.port = htons(curr_sport);
 +    }
 +
  another_round:
-     store_addr_to_key(&curr_addr, &nat_conn->rev_key,
-                       nat_info->nat_action);
-@@ -2449,15 +2410,19 @@ another_round:
+-    store_addr_to_key(&curr_addr, &nat_conn->rev_key,
+-                      nat_info->nat_action);
++    store_addr_to_key(&curr_addr, rev_key, nat_info->nat_action);
+ 
+     if (!pat_proto) {
+-        if (!conn_lookup(ct, &nat_conn->rev_key,
+-                         time_msec(), NULL, NULL)) {
++        if (!conn_lookup(ct, rev_key, time_msec(), NULL, NULL)) {
+             return true;
+         }
+ 
          goto next_addr;
      }
  
@@ -51847,12 +52434,12 @@ index 33a1a92953..fff8e77db1 100644
 -        }
 +    bool found = false;
 +    if (nat_info->nat_action & NAT_ACTION_DST_PORT) {
-+        found = nat_get_unique_l4(ct, nat_conn, &nat_conn->rev_key.src.port,
++        found = nat_get_unique_l4(ct, rev_key, &rev_key->src.port,
 +                                  curr_dport, min_dport, max_dport);
 +    }
 +
 +    if (!found) {
-+        found = nat_get_unique_l4(ct, nat_conn, &nat_conn->rev_key.dst.port,
++        found = nat_get_unique_l4(ct, rev_key, &rev_key->dst.port,
 +                                  curr_sport, min_sport, max_sport);
 +    }
 +
@@ -51861,7 +52448,175 @@ index 33a1a92953..fff8e77db1 100644
      }
  
      /* Check if next IP is in range and respin. Otherwise, notify
-@@ -2857,8 +2822,8 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key)
+@@ -2465,7 +2347,7 @@ another_round:
+ next_addr:
+     if (next_addr_in_range_guarded(&curr_addr, &min_addr,
+                                    &max_addr, &guard_addr,
+-                                   conn->key.dl_type == htons(ETH_TYPE_IP))) {
++                                   fwd_key->dl_type == htons(ETH_TYPE_IP))) {
+         return false;
+     }
+ 
+@@ -2477,23 +2359,20 @@ conn_update(struct conntrack *ct, struct conn *conn, struct dp_packet *pkt,
+             struct conn_lookup_ctx *ctx, long long now)
+ {
+     ovs_mutex_lock(&conn->lock);
++    uint8_t nw_proto = conn->key_node[CT_DIR_FWD].key.nw_proto;
+     enum ct_update_res update_res =
+-        l4_protos[conn->key.nw_proto]->conn_update(ct, conn, pkt, ctx->reply,
+-                                                   now);
++        l4_protos[nw_proto]->conn_update(ct, conn, pkt, ctx->reply, now);
+     ovs_mutex_unlock(&conn->lock);
+     return update_res;
+ }
+ 
+ static bool
+-conn_expired(struct conn *conn, long long now)
++conn_expired(const struct conn *conn, long long now)
+ {
+-    if (conn->conn_type == CT_CONN_TYPE_DEFAULT) {
+-        ovs_mutex_lock(&conn->lock);
+-        bool expired = now >= conn->expiration ? true : false;
+-        ovs_mutex_unlock(&conn->lock);
+-        return expired;
+-    }
+-    return false;
++    ovs_mutex_lock(&conn->lock);
++    bool expired = now >= conn->expiration ? true : false;
++    ovs_mutex_unlock(&conn->lock);
++    return expired;
+ }
+ 
+ static bool
+@@ -2510,7 +2389,7 @@ new_conn(struct conntrack *ct, struct dp_packet *pkt, struct conn_key *key,
+ }
+ 
+ static void
+-delete_conn_cmn(struct conn *conn)
++delete_conn__(struct conn *conn)
+ {
+     free(conn->alg);
+     free(conn);
+@@ -2519,20 +2398,8 @@ delete_conn_cmn(struct conn *conn)
+ static void
+ delete_conn(struct conn *conn)
+ {
+-    ovs_assert(conn->conn_type == CT_CONN_TYPE_DEFAULT);
+     ovs_mutex_destroy(&conn->lock);
+-    free(conn->nat_conn);
+-    delete_conn_cmn(conn);
+-}
+-
+-/* Only used by conn_clean_one(). */
+-static void
+-delete_conn_one(struct conn *conn)
+-{
+-    if (conn->conn_type == CT_CONN_TYPE_DEFAULT) {
+-        ovs_mutex_destroy(&conn->lock);
+-    }
+-    delete_conn_cmn(conn);
++    delete_conn__(conn);
+ }
+ 
+ /* Convert a conntrack address 'a' into an IP address 'b' based on 'dl_type'.
+@@ -2623,11 +2490,14 @@ static void
+ conn_to_ct_dpif_entry(const struct conn *conn, struct ct_dpif_entry *entry,
+                       long long now)
+ {
++    const struct conn_key *rev_key = &conn->key_node[CT_DIR_REV].key;
++    const struct conn_key *key = &conn->key_node[CT_DIR_FWD].key;
++
+     memset(entry, 0, sizeof *entry);
+-    conn_key_to_tuple(&conn->key, &entry->tuple_orig);
+-    conn_key_to_tuple(&conn->rev_key, &entry->tuple_reply);
++    conn_key_to_tuple(key, &entry->tuple_orig);
++    conn_key_to_tuple(rev_key, &entry->tuple_reply);
+ 
+-    entry->zone = conn->key.zone;
++    entry->zone = key->zone;
+ 
+     ovs_mutex_lock(&conn->lock);
+     entry->mark = conn->mark;
+@@ -2635,7 +2505,7 @@ conn_to_ct_dpif_entry(const struct conn *conn, struct ct_dpif_entry *entry,
+ 
+     long long expiration = conn->expiration - now;
+ 
+-    struct ct_l4_proto *class = l4_protos[conn->key.nw_proto];
++    struct ct_l4_proto *class = l4_protos[key->nw_proto];
+     if (class->conn_get_protoinfo) {
+         class->conn_get_protoinfo(conn, &entry->protoinfo);
+     }
+@@ -2683,10 +2553,21 @@ conntrack_dump_next(struct conntrack_dump *dump, struct ct_dpif_entry *entry)
+         if (!cm_node) {
+             break;
+         }
++        struct conn_key_node *keyn;
+         struct conn *conn;
+-        INIT_CONTAINER(conn, cm_node, cm_node);
+-        if ((!dump->filter_zone || conn->key.zone == dump->zone) &&
+-            (conn->conn_type != CT_CONN_TYPE_UN_NAT)) {
++
++        INIT_CONTAINER(keyn, cm_node, cm_node);
++
++        if (keyn->dir != CT_DIR_FWD) {
++            continue;
++        }
++
++        conn = CONTAINER_OF(keyn, struct conn, key_node[CT_DIR_FWD]);
++        if (conn_expired(conn, now)) {
++            continue;
++        }
++
++        if ((!dump->filter_zone || keyn->key.zone == dump->zone)) {
+             conn_to_ct_dpif_entry(conn, entry, now);
+             return 0;
+         }
+@@ -2704,12 +2585,18 @@ conntrack_dump_done(struct conntrack_dump *dump OVS_UNUSED)
+ int
+ conntrack_flush(struct conntrack *ct, const uint16_t *zone)
+ {
++    struct conn_key_node *keyn;
+     struct conn *conn;
+ 
+     ovs_mutex_lock(&ct->ct_lock);
+-    CMAP_FOR_EACH (conn, cm_node, &ct->conns) {
+-        if (!zone || *zone == conn->key.zone) {
+-            conn_clean_one(ct, conn);
++    CMAP_FOR_EACH (keyn, cm_node, &ct->conns) {
++        if (keyn->dir != CT_DIR_FWD) {
++            continue;
++        }
++
++        conn = CONTAINER_OF(keyn, struct conn, key_node[CT_DIR_FWD]);
++        if (!zone || *zone == keyn->key.zone) {
++            conn_clean(ct, conn);
+         }
+     }
+     ovs_mutex_unlock(&ct->ct_lock);
+@@ -2721,19 +2608,19 @@ int
+ conntrack_flush_tuple(struct conntrack *ct, const struct ct_dpif_tuple *tuple,
+                       uint16_t zone)
+ {
+-    int error = 0;
+     struct conn_key key;
+     struct conn *conn;
++    int error = 0;
+ 
+     memset(&key, 0, sizeof(key));
+     tuple_to_conn_key(tuple, zone, &key);
+     ovs_mutex_lock(&ct->ct_lock);
+     conn_lookup(ct, &key, time_msec(), &conn, NULL);
+ 
+-    if (conn && conn->conn_type == CT_CONN_TYPE_DEFAULT) {
++    if (conn) {
+         conn_clean(ct, conn);
+     } else {
+-        VLOG_WARN("Must flush tuple using the original pre-NATed tuple");
++        VLOG_WARN("Tuple not found");
+         error = ENOENT;
+     }
+ 
+@@ -2857,8 +2744,8 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key)
  {
      ovs_rwlock_wrlock(&ct->resources_lock);
  
@@ -51872,6 +52627,143 @@ index 33a1a92953..fff8e77db1 100644
                                      conn_key_hash(parent_key, ct->hash_basis),
                                      &ct->alg_expectation_refs) {
          if (!conn_key_cmp(&node->parent_key, parent_key)) {
+@@ -2877,50 +2764,54 @@ expectation_create(struct conntrack *ct, ovs_be16 dst_port,
+                    const struct conn *parent_conn, bool reply, bool src_ip_wc,
+                    bool skip_nat)
+ {
++    const struct conn_key *pconn_key, *pconn_rev_key;
+     union ct_addr src_addr;
+     union ct_addr dst_addr;
+     union ct_addr alg_nat_repl_addr;
+     struct alg_exp_node *alg_exp_node = xzalloc(sizeof *alg_exp_node);
+ 
++    pconn_key = &parent_conn->key_node[CT_DIR_FWD].key;
++    pconn_rev_key = &parent_conn->key_node[CT_DIR_REV].key;
++
+     if (reply) {
+-        src_addr = parent_conn->key.src.addr;
+-        dst_addr = parent_conn->key.dst.addr;
++        src_addr = pconn_key->src.addr;
++        dst_addr = pconn_key->dst.addr;
+         alg_exp_node->nat_rpl_dst = true;
+         if (skip_nat) {
+             alg_nat_repl_addr = dst_addr;
+         } else if (parent_conn->nat_action & NAT_ACTION_DST) {
+-            alg_nat_repl_addr = parent_conn->rev_key.src.addr;
++            alg_nat_repl_addr = pconn_rev_key->src.addr;
+             alg_exp_node->nat_rpl_dst = false;
+         } else {
+-            alg_nat_repl_addr = parent_conn->rev_key.dst.addr;
++            alg_nat_repl_addr = pconn_rev_key->dst.addr;
+         }
+     } else {
+-        src_addr = parent_conn->rev_key.src.addr;
+-        dst_addr = parent_conn->rev_key.dst.addr;
++        src_addr = pconn_rev_key->src.addr;
++        dst_addr = pconn_rev_key->dst.addr;
+         alg_exp_node->nat_rpl_dst = false;
+         if (skip_nat) {
+             alg_nat_repl_addr = src_addr;
+         } else if (parent_conn->nat_action & NAT_ACTION_DST) {
+-            alg_nat_repl_addr = parent_conn->key.dst.addr;
++            alg_nat_repl_addr = pconn_key->dst.addr;
+             alg_exp_node->nat_rpl_dst = true;
+         } else {
+-            alg_nat_repl_addr = parent_conn->key.src.addr;
++            alg_nat_repl_addr = pconn_key->src.addr;
+         }
+     }
+     if (src_ip_wc) {
+         memset(&src_addr, 0, sizeof src_addr);
+     }
+ 
+-    alg_exp_node->key.dl_type = parent_conn->key.dl_type;
+-    alg_exp_node->key.nw_proto = parent_conn->key.nw_proto;
+-    alg_exp_node->key.zone = parent_conn->key.zone;
++    alg_exp_node->key.dl_type = pconn_key->dl_type;
++    alg_exp_node->key.nw_proto = pconn_key->nw_proto;
++    alg_exp_node->key.zone = pconn_key->zone;
+     alg_exp_node->key.src.addr = src_addr;
+     alg_exp_node->key.dst.addr = dst_addr;
+     alg_exp_node->key.src.port = ALG_WC_SRC_PORT;
+     alg_exp_node->key.dst.port = dst_port;
+     alg_exp_node->parent_mark = parent_conn->mark;
+     alg_exp_node->parent_label = parent_conn->label;
+-    memcpy(&alg_exp_node->parent_key, &parent_conn->key,
++    memcpy(&alg_exp_node->parent_key, pconn_key,
+            sizeof alg_exp_node->parent_key);
+     /* Take the write lock here because it is almost 100%
+      * likely that the lookup will fail and
+@@ -3172,12 +3063,16 @@ process_ftp_ctl_v4(struct conntrack *ct,
+ 
+     switch (mode) {
+     case CT_FTP_MODE_ACTIVE:
+-        *v4_addr_rep = conn_for_expectation->rev_key.dst.addr.ipv4;
+-        conn_ipv4_addr = conn_for_expectation->key.src.addr.ipv4;
++        *v4_addr_rep =
++            conn_for_expectation->key_node[CT_DIR_REV].key.dst.addr.ipv4;
++        conn_ipv4_addr =
++            conn_for_expectation->key_node[CT_DIR_FWD].key.src.addr.ipv4;
+         break;
+     case CT_FTP_MODE_PASSIVE:
+-        *v4_addr_rep = conn_for_expectation->key.dst.addr.ipv4;
+-        conn_ipv4_addr = conn_for_expectation->rev_key.src.addr.ipv4;
++        *v4_addr_rep =
++            conn_for_expectation->key_node[CT_DIR_FWD].key.dst.addr.ipv4;
++        conn_ipv4_addr =
++            conn_for_expectation->key_node[CT_DIR_REV].key.src.addr.ipv4;
+         break;
+     case CT_TFTP_MODE:
+     default:
+@@ -3209,7 +3104,7 @@ skip_ipv6_digits(char *str)
+ static enum ftp_ctl_pkt
+ process_ftp_ctl_v6(struct conntrack *ct,
+                    struct dp_packet *pkt,
+-                   const struct conn *conn_for_expectation,
++                   const struct conn *conn_for_exp,
+                    union ct_addr *v6_addr_rep, char **ftp_data_start,
+                    size_t *addr_offset_from_ftp_data_start,
+                    size_t *addr_size, enum ct_alg_mode *mode)
+@@ -3277,24 +3172,25 @@ process_ftp_ctl_v6(struct conntrack *ct,
+ 
+     switch (*mode) {
+     case CT_FTP_MODE_ACTIVE:
+-        *v6_addr_rep = conn_for_expectation->rev_key.dst.addr;
++        *v6_addr_rep = conn_for_exp->key_node[CT_DIR_REV].key.dst.addr;
+         /* Although most servers will block this exploit, there may be some
+          * less well managed. */
+         if (memcmp(&ip6_addr, &v6_addr_rep->ipv6, sizeof ip6_addr) &&
+-            memcmp(&ip6_addr, &conn_for_expectation->key.src.addr.ipv6,
++            memcmp(&ip6_addr,
++                   &conn_for_exp->key_node[CT_DIR_FWD].key.src.addr.ipv6,
+                    sizeof ip6_addr)) {
+             return CT_FTP_CTL_INVALID;
+         }
+         break;
+     case CT_FTP_MODE_PASSIVE:
+-        *v6_addr_rep = conn_for_expectation->key.dst.addr;
++        *v6_addr_rep = conn_for_exp->key_node[CT_DIR_FWD].key.dst.addr;
+         break;
+     case CT_TFTP_MODE:
+     default:
+         OVS_NOT_REACHED();
+     }
+ 
+-    expectation_create(ct, port, conn_for_expectation,
++    expectation_create(ct, port, conn_for_exp,
+                        !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);
+     return CT_FTP_CTL_INTEREST;
+ }
+@@ -3448,7 +3344,8 @@ handle_tftp_ctl(struct conntrack *ct,
+                 long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,
+                 bool nat OVS_UNUSED)
+ {
+-    expectation_create(ct, conn_for_expectation->key.src.port,
++    expectation_create(ct,
++                       conn_for_expectation->key_node[CT_DIR_FWD].key.src.port,
+                        conn_for_expectation,
+                        !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);
+ }
 diff --git a/lib/cpu.c b/lib/cpu.c
 index 2df003c51b..e7cbfcfe6e 100644
 --- a/lib/cpu.c
diff --git a/SPECS/openvswitch2.17.spec b/SPECS/openvswitch2.17.spec
index 3707218..475348a 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: 112%{?dist}
+Release: 113%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -751,6 +751,13 @@ exit 0
 %endif
 
 %changelog
+* Fri Oct 13 2023 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-113
+- Merging upstream branch-2.17 [RH git: aa85ffacfd]
+    Commit list:
+    be1a8f7ecb conntrack: Remove nat_conn introducing key directionality.
+    f179c7c07f conntrack: simplify cleanup path
+
+
 * Thu Oct 12 2023 Timothy Redaelli <tredaelli@redhat.com> - 2.17.0-112
 - redhat: use rhpkg push instead of git push [RH git: 7b22bbc0dd]