diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index a3a9971..a08c237 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -1645,6 +1645,8 @@ dpif_netlink_netdev_match_to_dpif_flow(struct match *match,
             .recirc = true,
             .ct_state = true,
             .ct_zone = true,
+            .ct_mark = true,
+            .ct_label = true,
         },
     };
     size_t offset;
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index fb930ad..389551c 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -624,6 +624,8 @@ parse_tc_flower_to_match(struct tc_flower *flower,
         }
 
         match_set_ct_zone_masked(match, key->ct_zone, mask->ct_zone);
+        match_set_ct_mark_masked(match, key->ct_mark, mask->ct_mark);
+        match_set_ct_label_masked(match, key->ct_label, mask->ct_label);
     }
 
     if (flower->tunnel) {
@@ -793,6 +795,26 @@ parse_tc_flower_to_match(struct tc_flower *flower,
                     nl_msg_put_u16(buf, OVS_CT_ATTR_ZONE, action->ct.zone);
                 }
 
+                if (action->ct.mark_mask) {
+                    uint32_t mark_and_mask[2] = { action->ct.mark,
+                                                  action->ct.mark_mask };
+                    nl_msg_put_unspec(buf, OVS_CT_ATTR_MARK, &mark_and_mask,
+                                      sizeof mark_and_mask);
+                }
+
+                if (!ovs_u128_is_zero(action->ct.label_mask)) {
+                    struct {
+                        ovs_u128 key;
+                        ovs_u128 mask;
+                    } *ct_label;
+
+                    ct_label = nl_msg_put_unspec_uninit(buf,
+                                                        OVS_CT_ATTR_LABELS,
+                                                        sizeof *ct_label);
+                    ct_label->key = action->ct.label;
+                    ct_label->mask = action->ct.label_mask;
+                }
+
                 nl_msg_end_nested(buf, ct_offset);
             }
             break;
@@ -903,6 +925,28 @@ parse_put_flow_ct_action(struct tc_flower *flower,
                     action->ct.zone = nl_attr_get_u16(ct_attr);
                 }
                 break;
+                case OVS_CT_ATTR_MARK: {
+                    const struct {
+                        uint32_t key;
+                        uint32_t mask;
+                    } *ct_mark;
+
+                    ct_mark = nl_attr_get_unspec(ct_attr, sizeof *ct_mark);
+                    action->ct.mark = ct_mark->key;
+                    action->ct.mark_mask = ct_mark->mask;
+                }
+                break;
+                case OVS_CT_ATTR_LABELS: {
+                    const struct {
+                        ovs_u128 key;
+                        ovs_u128 mask;
+                    } *ct_label;
+
+                    ct_label = nl_attr_get_unspec(ct_attr, sizeof *ct_label);
+                    action->ct.label = ct_label->key;
+                    action->ct.label_mask = ct_label->mask;
+                }
+                break;
             }
         }
 
@@ -1093,22 +1137,12 @@ test_key_and_mask(struct match *match)
         return EOPNOTSUPP;
     }
 
-    if (mask->ct_mark) {
-        VLOG_DBG_RL(&rl, "offloading attribute ct_mark isn't supported");
-        return EOPNOTSUPP;
-    }
-
     if (mask->packet_type && key->packet_type) {
         VLOG_DBG_RL(&rl, "offloading attribute packet_type isn't supported");
         return EOPNOTSUPP;
     }
     mask->packet_type = 0;
 
-    if (!ovs_u128_is_zero(mask->ct_label)) {
-        VLOG_DBG_RL(&rl, "offloading attribute ct_label isn't supported");
-        return EOPNOTSUPP;
-    }
-
     for (int i = 0; i < FLOW_N_REGS; i++) {
         if (mask->regs[i]) {
             VLOG_DBG_RL(&rl,
@@ -1468,6 +1502,18 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
         mask->ct_zone = 0;
     }
 
+    if (mask->ct_mark) {
+        flower.key.ct_mark = key->ct_mark;
+        flower.mask.ct_mark = mask->ct_mark;
+        mask->ct_mark = 0;
+    }
+
+    if (!ovs_u128_is_zero(mask->ct_label)) {
+        flower.key.ct_label = key->ct_label;
+        flower.mask.ct_label = mask->ct_label;
+        mask->ct_label = OVS_U128_ZERO;
+    }
+
     err = test_key_and_mask(match);
     if (err) {
         return err;
diff --git a/lib/tc.c b/lib/tc.c
index 4358cb7..de09c49 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -404,6 +404,11 @@ static const struct nl_policy tca_flower_policy[] = {
     [TCA_FLOWER_KEY_CT_STATE_MASK] = { .type = NL_A_U16, .optional = true, },
     [TCA_FLOWER_KEY_CT_ZONE] = { .type = NL_A_U16, .optional = true, },
     [TCA_FLOWER_KEY_CT_ZONE_MASK] = { .type = NL_A_U16, .optional = true, },
+    [TCA_FLOWER_KEY_CT_MARK] = { .type = NL_A_U32, .optional = true, },
+    [TCA_FLOWER_KEY_CT_MARK_MASK] = { .type = NL_A_U32, .optional = true, },
+    [TCA_FLOWER_KEY_CT_LABELS] = { .type = NL_A_U128, .optional = true, },
+    [TCA_FLOWER_KEY_CT_LABELS_MASK] = { .type = NL_A_U128,
+                                        .optional = true, },
 };
 
 static void
@@ -732,6 +737,20 @@ nl_parse_flower_ct_match(struct nlattr **attrs, struct tc_flower *flower) {
         key->ct_zone = nl_attr_get_u16(attr_key);
         mask->ct_zone = nl_attr_get_u16(attr_mask);
     }
+
+    attr_key = attrs[TCA_FLOWER_KEY_CT_MARK];
+    attr_mask = attrs[TCA_FLOWER_KEY_CT_MARK_MASK];
+    if (attrs[TCA_FLOWER_KEY_CT_MARK_MASK]) {
+        key->ct_mark = nl_attr_get_u32(attr_key);
+        mask->ct_mark = nl_attr_get_u32(attr_mask);
+    }
+
+    attr_key = attrs[TCA_FLOWER_KEY_CT_LABELS];
+    attr_mask = attrs[TCA_FLOWER_KEY_CT_LABELS_MASK];
+    if (attrs[TCA_FLOWER_KEY_CT_LABELS_MASK]) {
+        key->ct_label = nl_attr_get_u128(attr_key);
+        mask->ct_label = nl_attr_get_u128(attr_mask);
+    }
 }
 
 static void
@@ -1261,6 +1280,14 @@ static const struct nl_policy ct_policy[] = {
                          .optional = true, },
     [TCA_CT_ZONE] = { .type = NL_A_U16,
                       .optional = true, },
+    [TCA_CT_MARK] = { .type = NL_A_U32,
+                       .optional = true, },
+    [TCA_CT_MARK_MASK] = { .type = NL_A_U32,
+                            .optional = true, },
+    [TCA_CT_LABELS] = { .type = NL_A_UNSPEC,
+                         .optional = true, },
+    [TCA_CT_LABELS_MASK] = { .type = NL_A_UNSPEC,
+                              .optional = true, },
 };
 
 static int
@@ -1289,11 +1316,20 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
     action->ct.clear = ct_action & TCA_CT_ACT_CLEAR;
     if (!action->ct.clear) {
         struct nlattr *zone = ct_attrs[TCA_CT_ZONE];
+        struct nlattr *mark = ct_attrs[TCA_CT_MARK];
+        struct nlattr *mark_mask = ct_attrs[TCA_CT_MARK_MASK];
+        struct nlattr *label = ct_attrs[TCA_CT_LABELS];
+        struct nlattr *label_mask = ct_attrs[TCA_CT_LABELS_MASK];
 
         action->ct.commit = ct_action & TCA_CT_ACT_COMMIT;
         action->ct.force = ct_action & TCA_CT_ACT_FORCE;
 
         action->ct.zone = zone ? nl_attr_get_u16(zone) : 0;
+        action->ct.mark = mark ? nl_attr_get_u32(mark) : 0;
+        action->ct.mark_mask = mark_mask ? nl_attr_get_u32(mark_mask) : 0;
+        action->ct.label = label? nl_attr_get_u128(label) : OVS_U128_ZERO;
+        action->ct.label_mask = label_mask ?
+                                nl_attr_get_u128(label_mask) : OVS_U128_ZERO;
 
     }
     action->type = TC_ACT_CT;
@@ -2005,6 +2041,21 @@ nl_msg_put_act_ct(struct ofpbuf *request, struct tc_action *action)
                 nl_msg_put_u16(request, TCA_CT_ZONE, action->ct.zone);
             }
 
+            if (!is_all_zeros(&action->ct.label_mask,
+                              sizeof action->ct.label_mask)) {
+                nl_msg_put_u128(request, TCA_CT_LABELS,
+                                action->ct.label);
+                nl_msg_put_u128(request, TCA_CT_LABELS_MASK,
+                                action->ct.label_mask);
+            }
+
+            if (action->ct.mark_mask) {
+                nl_msg_put_u32(request, TCA_CT_MARK,
+                               action->ct.mark);
+                nl_msg_put_u32(request, TCA_CT_MARK_MASK,
+                               action->ct.mark_mask);
+            }
+
             if (action->ct.commit) {
                 ct_action = TCA_CT_ACT_COMMIT;
                 if (action->ct.force) {
@@ -2555,6 +2606,8 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
 
         FLOWER_PUT_MASKED_VALUE(ct_state, TCA_FLOWER_KEY_CT_STATE);
         FLOWER_PUT_MASKED_VALUE(ct_zone, TCA_FLOWER_KEY_CT_ZONE);
+        FLOWER_PUT_MASKED_VALUE(ct_mark, TCA_FLOWER_KEY_CT_MARK);
+        FLOWER_PUT_MASKED_VALUE(ct_label, TCA_FLOWER_KEY_CT_LABELS);
     }
 
     if (host_eth_type == ETH_P_IP) {
diff --git a/lib/tc.h b/lib/tc.h
index 8200c20..bb7a078 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -118,6 +118,8 @@ struct tc_flower_key {
 
     uint16_t ct_state;
     uint16_t ct_zone;
+    uint32_t ct_mark;
+    ovs_u128 ct_label;
 
     struct {
         ovs_be32 ipv4_src;
@@ -207,6 +209,10 @@ struct tc_action {
 
         struct {
             uint16_t zone;
+            uint32_t mark;
+            uint32_t mark_mask;
+            ovs_u128 label;
+            ovs_u128 label_mask;
             bool clear;
             bool force;
             bool commit;
