diff --git a/lib/conntrack.c b/lib/conntrack.c
index fe5fd0fe8be1..c05b3901d7af 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -2368,6 +2368,10 @@ delete_conn(struct conn *conn)
     free(conn);
 }
 
+/* Convert a conntrack address 'a' into an IP address 'b' based on 'dl_type'.
+ *
+ * Note that 'dl_type' should be either "ETH_TYPE_IP" or "ETH_TYPE_IPv6"
+ * in network-byte order. */
 static void
 ct_endpoint_to_ct_dpif_inet_addr(const struct ct_addr *a,
                                  union ct_dpif_inet_addr *b,
@@ -2380,6 +2384,22 @@ ct_endpoint_to_ct_dpif_inet_addr(const struct ct_addr *a,
     }
 }
 
+/* Convert an IP address 'a' into a conntrack address 'b' based on 'dl_type'.
+ *
+ * Note that 'dl_type' should be either "ETH_TYPE_IP" or "ETH_TYPE_IPv6"
+ * in network-byte order. */
+static void
+ct_dpif_inet_addr_to_ct_endpoint(const union ct_dpif_inet_addr *a,
+                                 struct ct_addr *b,
+                                 ovs_be16 dl_type)
+{
+    if (dl_type == htons(ETH_TYPE_IP)) {
+        b->ipv4_aligned = a->ip;
+    } else if (dl_type == htons(ETH_TYPE_IPV6)){
+        b->ipv6_aligned = a->in6;
+    }
+}
+
 static void
 conn_key_to_tuple(const struct conn_key *key, struct ct_dpif_tuple *tuple)
 {
@@ -2405,6 +2425,35 @@ conn_key_to_tuple(const struct conn_key *key, struct ct_dpif_tuple *tuple)
 }
 
 static void
+tuple_to_conn_key(const struct ct_dpif_tuple *tuple, uint16_t zone,
+                  struct conn_key *key)
+{
+    if (tuple->l3_type == AF_INET) {
+        key->dl_type = htons(ETH_TYPE_IP);
+    } else if (tuple->l3_type == AF_INET6) {
+        key->dl_type = htons(ETH_TYPE_IPV6);
+    }
+    key->nw_proto = tuple->ip_proto;
+    ct_dpif_inet_addr_to_ct_endpoint(&tuple->src, &key->src.addr,
+                                     key->dl_type);
+    ct_dpif_inet_addr_to_ct_endpoint(&tuple->dst, &key->dst.addr,
+                                     key->dl_type);
+
+    if (tuple->ip_proto == IPPROTO_ICMP || tuple->ip_proto == IPPROTO_ICMPV6) {
+        key->src.icmp_id = tuple->icmp_id;
+        key->src.icmp_type = tuple->icmp_type;
+        key->src.icmp_code = tuple->icmp_code;
+        key->dst.icmp_id = tuple->icmp_id;
+        key->dst.icmp_type = reverse_icmp_type(tuple->icmp_type);
+        key->dst.icmp_code = tuple->icmp_code;
+    } else {
+        key->src.port = tuple->src_port;
+        key->dst.port = tuple->dst_port;
+    }
+    key->zone = zone;
+}
+
+static void
 conn_to_ct_dpif_entry(const struct conn *conn, struct ct_dpif_entry *entry,
                       long long now, int bkt)
 {
@@ -2517,6 +2566,29 @@ conntrack_flush(struct conntrack *ct, const uint16_t *zone)
 }
 
 int
+conntrack_flush_tuple(struct conntrack *ct, const struct ct_dpif_tuple *tuple,
+                      uint16_t zone)
+{
+    struct conn_lookup_ctx ctx;
+    int error = 0;
+
+    memset(&ctx, 0, sizeof(ctx));
+    tuple_to_conn_key(tuple, zone, &ctx.key);
+    ctx.hash = conn_key_hash(&ctx.key, ct->hash_basis);
+    unsigned bucket = hash_to_bucket(ctx.hash);
+
+    ct_lock_lock(&ct->buckets[bucket].lock);
+    conn_key_lookup(&ct->buckets[bucket], &ctx, time_msec());
+    if (ctx.conn) {
+        conn_clean(ct, ctx.conn, &ct->buckets[bucket]);
+    } else {
+        error = ENOENT;
+    }
+    ct_lock_unlock(&ct->buckets[bucket].lock);
+    return error;
+}
+
+int
 conntrack_set_maxconns(struct conntrack *ct, uint32_t maxconns)
 {
     atomic_store_relaxed(&ct->n_conn_limit, maxconns);
diff --git a/lib/conntrack.h b/lib/conntrack.h
index ac650304a41f..e3a5dcc8023f 100644
--- a/lib/conntrack.h
+++ b/lib/conntrack.h
@@ -109,6 +109,7 @@ struct conntrack_dump {
 };
 
 struct ct_dpif_entry;
+struct ct_dpif_tuple;
 
 int conntrack_dump_start(struct conntrack *, struct conntrack_dump *,
                          const uint16_t *pzone, int *);
@@ -116,6 +117,8 @@ int conntrack_dump_next(struct conntrack_dump *, struct ct_dpif_entry *);
 int conntrack_dump_done(struct conntrack_dump *);
 
 int conntrack_flush(struct conntrack *, const uint16_t *zone);
+int conntrack_flush_tuple(struct conntrack *, const struct ct_dpif_tuple *,
+                          uint16_t zone);
 int conntrack_set_maxconns(struct conntrack *ct, uint32_t maxconns);
 int conntrack_get_maxconns(struct conntrack *ct, uint32_t *maxconns);
 int conntrack_get_nconns(struct conntrack *ct, uint32_t *nconns);
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index ba62128c758c..b06caa281afc 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -5842,7 +5842,7 @@ dpif_netdev_ct_flush(struct dpif *dpif, const uint16_t *zone,
     struct dp_netdev *dp = get_dp_netdev(dpif);
 
     if (tuple) {
-        return EOPNOTSUPP;
+        return conntrack_flush_tuple(&dp->conntrack, tuple, zone ? *zone : 0);
     }
     return conntrack_flush(&dp->conntrack, zone);
 }
diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
index 12b0adf7190e..f23a4063375c 100644
--- a/tests/system-kmod-macros.at
+++ b/tests/system-kmod-macros.at
@@ -97,14 +97,6 @@ m4_define([CHECK_CONNTRACK_LOCAL_STACK])
 #
 m4_define([CHECK_CONNTRACK_NAT])
 
-# CHECK_CT_DPIF_FLUSH_BY_CT_TUPLE()
-#
-# Perform requirements checks for running ovs-dpctl flush-conntrack by
-# conntrack 5-tuple test. The kernel datapath does support this
-# feature. Will remove this check after both kernel and userspace datapath
-# support it.
-m4_define([CHECK_CT_DPIF_FLUSH_BY_CT_TUPLE])
-
 # CHECK_CT_DPIF_SET_GET_MAXCONNS()
 #
 # Perform requirements checks for running ovs-dpctl ct-set-maxconns or
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index dbd56405d1fd..2afadec15827 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -834,7 +834,6 @@ AT_CLEANUP
 
 AT_SETUP([conntrack - ct flush by 5-tuple])
 CHECK_CONNTRACK()
-CHECK_CT_DPIF_FLUSH_BY_CT_TUPLE()
 OVS_TRAFFIC_VSWITCHD_START()
 
 ADD_NAMESPACES(at_ns0, at_ns1)
diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at
index 20a8635f9e39..00e1f817ff78 100644
--- a/tests/system-userspace-macros.at
+++ b/tests/system-userspace-macros.at
@@ -100,16 +100,6 @@ m4_define([CHECK_CONNTRACK_LOCAL_STACK],
 #
 m4_define([CHECK_CONNTRACK_NAT])
 
-# CHECK_CT_DPIF_FLUSH_BY_CT_TUPLE()
-#
-# Perform requirements checks for running ovs-dpctl flush-conntrack by
-# conntrack 5-tuple test. The userspace datapath does not support
-# this feature yet.
-m4_define([CHECK_CT_DPIF_FLUSH_BY_CT_TUPLE],
-[
-    AT_SKIP_IF([:])
-])
-
 # CHECK_CT_DPIF_SET_GET_MAXCONNS()
 #
 # Perform requirements checks for running ovs-dpctl ct-set-maxconns or
