diff --git a/include/net/flow_offload.h b/include/net/flow_offload.h
index c6f7bd2..f7215fa 100644
--- a/include/net/flow_offload.h
+++ b/include/net/flow_offload.h
@@ -135,6 +135,7 @@ enum flow_action_id {
 	FLOW_ACTION_SAMPLE,
 	FLOW_ACTION_POLICE,
 	FLOW_ACTION_CT,
+	FLOW_ACTION_CT_METADATA,
 	FLOW_ACTION_MPLS_PUSH,
 	FLOW_ACTION_MPLS_POP,
 	FLOW_ACTION_MPLS_MANGLE,
@@ -197,6 +198,11 @@ struct flow_action_entry {
 			int action;
 			u16 zone;
 		} ct;
+		struct {
+			u32 mark;
+			u32 labels[4];
+			u16 zone;
+		} ct_metadata;
 		struct {				/* FLOW_ACTION_MPLS_PUSH */
 			u32		label;
 			__be16		proto;
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index d9d0945..c2d5cdd 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -16,6 +16,29 @@
 struct flow_offload;
 enum flow_offload_tuple_dir;
 
+struct nf_flow_key {
+	struct flow_dissector_key_meta			meta;
+	struct flow_dissector_key_control		control;
+	struct flow_dissector_key_basic			basic;
+	union {
+		struct flow_dissector_key_ipv4_addrs	ipv4;
+		struct flow_dissector_key_ipv6_addrs	ipv6;
+	};
+	struct flow_dissector_key_tcp			tcp;
+	struct flow_dissector_key_ports			tp;
+} __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */
+
+struct nf_flow_match {
+	struct flow_dissector	dissector;
+	struct nf_flow_key	key;
+	struct nf_flow_key	mask;
+};
+
+struct nf_flow_rule {
+	struct nf_flow_match	match;
+	struct flow_rule	*rule;
+};
+
 struct nf_flowtable_type {
 	struct list_head		list;
 	int				family;
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index c35c337..f5107f3a 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -23,29 +23,6 @@ struct flow_offload_work {
 	struct flow_offload	*flow;
 };
 
-struct nf_flow_key {
-	struct flow_dissector_key_meta			meta;
-	struct flow_dissector_key_control		control;
-	struct flow_dissector_key_basic			basic;
-	union {
-		struct flow_dissector_key_ipv4_addrs	ipv4;
-		struct flow_dissector_key_ipv6_addrs	ipv6;
-	};
-	struct flow_dissector_key_tcp			tcp;
-	struct flow_dissector_key_ports			tp;
-} __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */
-
-struct nf_flow_match {
-	struct flow_dissector	dissector;
-	struct nf_flow_key	key;
-	struct nf_flow_key	mask;
-};
-
-struct nf_flow_rule {
-	struct nf_flow_match	match;
-	struct flow_rule	*rule;
-};
-
 #define NF_FLOW_DISSECTOR(__match, __type, __field)	\
 	(__match)->dissector.offset[__type] =		\
 		offsetof(struct nf_flow_key, __field)
diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c
index 23eba61..0773456 100644
--- a/net/sched/act_ct.c
+++ b/net/sched/act_ct.c
@@ -55,7 +55,199 @@ struct tcf_ct_flow_table {
 	.automatic_shrinking = true,
 };
 
+static inline struct flow_action_entry *
+tcf_ct_flow_table_flow_action_get_next(struct flow_action *flow_action)
+{
+	int i = flow_action->num_entries++;
+
+	return &flow_action->entries[i];
+}
+
+static void
+tcf_ct_flow_table_add_action_nat_ipv4(const struct nf_conntrack_tuple *tuple,
+				      struct nf_conntrack_tuple target,
+				      struct flow_action *action)
+{
+	struct flow_action_entry *entry;
+
+	if (memcmp(&target.src.u3, &tuple->src.u3, sizeof(target.src.u3))) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_IP4;
+		entry->mangle.mask = ~(0xFFFFFFFF);
+		entry->mangle.offset = offsetof(struct iphdr, saddr);
+		entry->mangle.val = htonl(target.src.u3.ip);
+	} else if (memcmp(&target.dst.u3, &tuple->dst.u3,
+			  sizeof(target.dst.u3))) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_IP4;
+		entry->mangle.mask = ~(0xFFFFFFFF);
+		entry->mangle.offset = offsetof(struct iphdr, daddr);
+		entry->mangle.val = htonl(target.dst.u3.ip);
+	}
+}
+
+static void
+tcf_ct_flow_table_add_action_nat_ipv6(const struct nf_conntrack_tuple *tuple,
+				      struct nf_conntrack_tuple target,
+				      struct flow_action *action)
+{
+	struct flow_action_entry *entry;
+	union nf_inet_addr *addr;
+	u32 next_offset = 0;
+	int i;
+
+	if (memcmp(&target.src.u3, &tuple->src.u3, sizeof(target.src.u3))) {
+		addr = &target.src.u3;
+		next_offset = offsetof(struct iphdr, saddr);
+	} else if (memcmp(&target.dst.u3, &tuple->dst.u3,
+			  sizeof(target.dst.u3))) {
+		addr = &target.dst.u3;
+		next_offset = offsetof(struct iphdr, daddr);
+	} else {
+		return;
+	}
+
+	for (i = 0; i < sizeof(struct in6_addr) / sizeof(u32); i++) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_IP6;
+		entry->mangle.mask = ~(0xFFFFFFFF);
+		entry->mangle.val = htonl(addr->ip6[i]);
+		entry->mangle.offset = next_offset;
+
+		next_offset += sizeof(u32);
+	}
+}
+
+static void
+tcf_ct_flow_table_add_action_nat_tcp(const struct nf_conntrack_tuple *tuple,
+				     struct nf_conntrack_tuple target,
+				     struct flow_action *action)
+{
+	struct flow_action_entry *entry;
+
+	if (target.src.u.tcp.port != tuple->src.u.tcp.port) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_TCP;
+		entry->mangle.mask = ~(0xFFFF);
+		entry->mangle.offset = offsetof(struct tcphdr, source);
+		entry->mangle.val = htons(target.src.u.tcp.port);
+	} else if (target.dst.u.tcp.port != tuple->dst.u.tcp.port) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_TCP;
+		entry->mangle.mask = ~(0xFFFF);
+		entry->mangle.offset = offsetof(struct tcphdr, dest);
+		entry->mangle.val = htons(target.dst.u.tcp.port);
+	}
+}
+
+static void
+tcf_ct_flow_table_add_action_nat_udp(const struct nf_conntrack_tuple *tuple,
+				     struct nf_conntrack_tuple target,
+				     struct flow_action *action)
+{
+	struct flow_action_entry *entry;
+
+	if (target.src.u.udp.port != tuple->src.u.udp.port) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_UDP;
+		entry->mangle.mask = ~(0xFFFF);
+		entry->mangle.offset = offsetof(struct udphdr, source);
+		entry->mangle.val = htons(target.src.u.udp.port);
+	} else if (target.dst.u.udp.port != tuple->dst.u.udp.port) {
+		entry = tcf_ct_flow_table_flow_action_get_next(action);
+		entry->id = FLOW_ACTION_MANGLE;
+		entry->mangle.htype = FLOW_ACT_MANGLE_HDR_TYPE_UDP;
+		entry->mangle.mask = ~(0xFFFF);
+		entry->mangle.offset = offsetof(struct udphdr, dest);
+		entry->mangle.val = htons(target.dst.u.udp.port);
+	}
+}
+
+static void tcf_ct_flow_table_add_action_meta(struct nf_conn *ct,
+					      enum ip_conntrack_dir dir,
+					      struct flow_action *action)
+{
+	struct nf_conn_labels *ct_labels;
+	struct flow_action_entry *entry;
+	u32 *act_ct_labels;
+
+	entry = tcf_ct_flow_table_flow_action_get_next(action);
+	entry->id = FLOW_ACTION_CT_METADATA;
+	entry->ct_metadata.zone = nf_ct_zone(ct)->id;
+#if IS_ENABLED(CONFIG_NF_CONNTRACK_MARK)
+	entry->ct_metadata.mark = ct->mark;
+#endif
+
+	act_ct_labels = entry->ct_metadata.labels;
+	ct_labels = nf_ct_labels_find(ct);
+	if (ct_labels)
+		memcpy(act_ct_labels, ct_labels->bits, NF_CT_LABELS_MAX_SIZE);
+	else
+		memset(act_ct_labels, 0, NF_CT_LABELS_MAX_SIZE);
+}
+
+static void tcf_ct_flow_table_add_action_nat(struct net *net,
+					     struct nf_conn *ct,
+					     enum ip_conntrack_dir dir,
+					     struct flow_action *action)
+{
+	const struct nf_conntrack_tuple *tuple = &ct->tuplehash[dir].tuple;
+	struct nf_conntrack_tuple target;
+
+	nf_ct_invert_tuple(&target, &ct->tuplehash[!dir].tuple);
+
+	tuple->src.l3num == NFPROTO_IPV4 ?
+		tcf_ct_flow_table_add_action_nat_ipv4(tuple, target, action) :
+		tcf_ct_flow_table_add_action_nat_ipv6(tuple, target, action);
+
+	nf_ct_protonum(ct) == IPPROTO_TCP ?
+		tcf_ct_flow_table_add_action_nat_tcp(tuple, target, action) :
+		tcf_ct_flow_table_add_action_nat_udp(tuple, target, action);
+}
+
+static int tcf_ct_flow_table_fill_actions(struct net *net,
+					  const struct flow_offload *flow,
+					  enum flow_offload_tuple_dir tdir,
+					  struct nf_flow_rule *flow_rule)
+{
+	struct flow_action *action = &flow_rule->rule->action;
+	const struct nf_conntrack_tuple *tuple;
+	struct nf_conn *ct = flow->ct;
+	enum ip_conntrack_dir dir;
+
+	switch (tdir) {
+	case FLOW_OFFLOAD_DIR_ORIGINAL:
+		dir = IP_CT_DIR_ORIGINAL;
+		break;
+	case FLOW_OFFLOAD_DIR_REPLY:
+		dir = IP_CT_DIR_REPLY;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	tuple = &ct->tuplehash[dir].tuple;
+	if (tuple->src.l3num != NFPROTO_IPV4 &&
+	    tuple->src.l3num != NFPROTO_IPV6)
+		return -EOPNOTSUPP;
+
+	if (nf_ct_protonum(ct) != IPPROTO_TCP &&
+	    nf_ct_protonum(ct) != IPPROTO_UDP)
+		return -EOPNOTSUPP;
+
+	tcf_ct_flow_table_add_action_meta(ct, dir, action);
+	tcf_ct_flow_table_add_action_nat(net, ct, dir, action);
+	return 0;
+}
+
 static struct nf_flowtable_type flowtable_ct = {
+	.action		= tcf_ct_flow_table_fill_actions,
 	.owner		= THIS_MODULE,
 };
 
