diff --git a/include/linux/tc_act/tc_tunnel_key.h b/include/linux/tc_act/tc_tunnel_key.h
index 26cbd2ff1..f13acf17d 100644
--- a/include/linux/tc_act/tc_tunnel_key.h
+++ b/include/linux/tc_act/tc_tunnel_key.h
@@ -47,6 +47,29 @@ enum {
 
 #define TCA_TUNNEL_KEY_MAX (__TCA_TUNNEL_KEY_MAX - 1)
 
+enum {
+	TCA_TUNNEL_KEY_ENC_OPTS_UNSPEC,
+	TCA_TUNNEL_KEY_ENC_OPTS_GENEVE,	/* Nested
+					 * TCA_TUNNEL_KEY_ENC_OPTS_GENEVE
+					 * attributes
+					 */
+	__TCA_TUNNEL_KEY_ENC_OPTS_MAX,
+};
+
+#define TCA_TUNNEL_KEY_ENC_OPTS_MAX (__TCA_TUNNEL_KEY_ENC_OPTS_MAX - 1)
+
+enum {
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_UNSPEC,
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS,	/* be16 */
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE,	/* u8 */
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA,	/* 4 to 128 bytes */
+
+	__TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX,
+};
+
+#define TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX \
+	(__TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX - 1)
+
 #endif /* __KERNEL__ || HAVE_TCA_TUNNEL_KEY_ENC_TTL */
 
 #endif /* __LINUX_TC_ACT_TC_TUNNEL_KEY_WRAPPER_H */
diff --git a/lib/netdev-tc-offloads.c b/lib/netdev-tc-offloads.c
index 7bc745e95..0612b1348 100644
--- a/lib/netdev-tc-offloads.c
+++ b/lib/netdev-tc-offloads.c
@@ -409,6 +409,29 @@ parse_flower_rewrite_to_netlink_action(struct ofpbuf *buf,
     }
 }
 
+static void parse_tc_flower_geneve_opts(struct tc_action *action,
+                                        struct ofpbuf *buf)
+{
+    int tun_opt_len = action->encap.data.present.len;
+    size_t geneve_off;
+    int idx = 0;
+
+    if (!tun_opt_len) {
+        return;
+    }
+
+    geneve_off = nl_msg_start_nested(buf, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
+    while (tun_opt_len) {
+        struct geneve_opt *opt;
+
+        opt = &action->encap.data.opts.gnv[idx];
+        nl_msg_put(buf, opt, sizeof(struct geneve_opt) + opt->length * 4);
+        idx += sizeof(struct geneve_opt) + opt->length * 4;
+        tun_opt_len -= sizeof(struct geneve_opt) + opt->length * 4;
+    }
+    nl_msg_end_nested(buf, geneve_off);
+}
+
 static int
 parse_tc_flower_to_match(struct tc_flower *flower,
                          struct match *match,
@@ -580,6 +603,7 @@ parse_tc_flower_to_match(struct tc_flower *flower,
                 nl_msg_put_be16(buf, OVS_TUNNEL_KEY_ATTR_TP_DST,
                                 action->encap.tp_dst);
 
+                parse_tc_flower_geneve_opts(action, buf);
                 nl_msg_end_nested(buf, tunnel_offset);
                 nl_msg_end_nested(buf, set_offset);
             }
@@ -786,6 +810,12 @@ parse_put_flow_set_action(struct tc_flower *flower, struct tc_action *action,
             action->encap.tp_dst = nl_attr_get_be16(tun_attr);
         }
         break;
+        case OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS: {
+            memcpy(action->encap.data.opts.gnv, nl_attr_get(tun_attr),
+                   nl_attr_get_size(tun_attr));
+            action->encap.data.present.len = nl_attr_get_size(tun_attr);
+        }
+        break;
         }
     }
 
diff --git a/lib/tc.c b/lib/tc.c
index bbc382326..749e658eb 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -659,8 +659,72 @@ static const struct nl_policy tunnel_key_policy[] = {
     [TCA_TUNNEL_KEY_ENC_DST_PORT] = { .type = NL_A_U16, .optional = true, },
     [TCA_TUNNEL_KEY_ENC_TOS] = { .type = NL_A_U8, .optional = true, },
     [TCA_TUNNEL_KEY_ENC_TTL] = { .type = NL_A_U8, .optional = true, },
+    [TCA_TUNNEL_KEY_ENC_OPTS]     = { .type = NL_A_NESTED, .optional = true,},
 };
 
+static void
+nl_parse_act_geneve_opts(const struct nlattr *in_nlattr,
+                         struct tc_action *action)
+{
+    struct geneve_opt *opt = NULL;
+    const struct ofpbuf *msg;
+    struct nlattr *nla;
+    struct ofpbuf buf;
+    size_t left;
+    int cnt;
+
+    nl_attr_get_nested(in_nlattr, &buf);
+    msg = &buf;
+
+    cnt = 0;
+    NL_ATTR_FOR_EACH (nla, left, ofpbuf_at(msg, 0, 0), msg->size) {
+        uint16_t type = nl_attr_type(nla);
+
+        switch (type) {
+        case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS:
+            opt = (struct geneve_opt *) &action->encap.data.opts.gnv[cnt];
+            opt->opt_class = nl_attr_get_be16(nla);
+            action->encap.data.present.len += sizeof(struct geneve_opt);
+            cnt += sizeof(struct geneve_opt);
+            break;
+        case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE:
+            opt->type = nl_attr_get_u8(nla);
+            break;
+        case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA:
+            opt->length = nl_attr_get_size(nla) / 4;
+            memcpy(opt + 1, nl_attr_get_unspec(nla, 1), opt->length * 4);
+            action->encap.data.present.len += opt->length * 4;
+            cnt += opt->length * 4;
+            break;
+        }
+    }
+}
+
+static void
+nl_parse_act_tunnel_opts(struct nlattr *options, struct tc_action *action)
+{
+    const struct ofpbuf *msg;
+    struct nlattr *nla;
+    struct ofpbuf buf;
+    size_t left;
+
+    if (!options) {
+        return;
+    }
+
+    nl_attr_get_nested(options, &buf);
+    msg = &buf;
+
+    NL_ATTR_FOR_EACH (nla, left, ofpbuf_at(msg, 0, 0), msg->size) {
+        uint16_t type = nl_attr_type(nla);
+        switch (type) {
+        case TCA_TUNNEL_KEY_ENC_OPTS_GENEVE:
+            nl_parse_act_geneve_opts(nla, action);
+            break;
+        }
+    }
+}
+
 static int
 nl_parse_act_tunnel_key(struct nlattr *options, struct tc_flower *flower)
 {
@@ -686,6 +750,7 @@ nl_parse_act_tunnel_key(struct nlattr *options, struct tc_flower *flower)
         struct nlattr *ipv6_dst = tun_attrs[TCA_TUNNEL_KEY_ENC_IPV6_DST];
         struct nlattr *tos = tun_attrs[TCA_TUNNEL_KEY_ENC_TOS];
         struct nlattr *ttl = tun_attrs[TCA_TUNNEL_KEY_ENC_TTL];
+        struct nlattr *tun_opt = tun_attrs[TCA_TUNNEL_KEY_ENC_OPTS];
 
         action = &flower->actions[flower->action_count++];
         action->type = TC_ACT_ENCAP;
@@ -701,6 +766,7 @@ nl_parse_act_tunnel_key(struct nlattr *options, struct tc_flower *flower)
         action->encap.tp_dst = dst_port ? nl_attr_get_be16(dst_port) : 0;
         action->encap.tos = tos ? nl_attr_get_u8(tos) : 0;
         action->encap.ttl = ttl ? nl_attr_get_u8(ttl) : 0;
+        nl_parse_act_tunnel_opts(tun_opt, action);
     } else if (tun->t_action == TCA_TUNNEL_KEY_ACT_RELEASE) {
         flower->tunnel.tunnel = true;
     } else {
@@ -1268,13 +1334,46 @@ nl_msg_put_act_tunnel_key_release(struct ofpbuf *request)
     nl_msg_end_nested(request, offset);
 }
 
+static void
+nl_msg_put_act_tunnel_geneve_option(struct ofpbuf *request,
+                                    struct tun_metadata tun_metadata)
+{
+    const struct geneve_opt *opt;
+    size_t outer, inner;
+    int tun_opt_len;
+    int idx = 0;
+
+    tun_opt_len = tun_metadata.present.len;
+    if (!tun_opt_len) {
+        return;
+    }
+
+    outer = nl_msg_start_nested(request, TCA_TUNNEL_KEY_ENC_OPTS);
+
+    while (tun_opt_len) {
+        opt = &tun_metadata.opts.gnv[idx];
+        inner = nl_msg_start_nested(request, TCA_TUNNEL_KEY_ENC_OPTS_GENEVE);
+        nl_msg_put_be16(request, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS,
+                        opt->opt_class);
+        nl_msg_put_u8(request, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE, opt->type);
+        nl_msg_put_unspec(request, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA, opt + 1,
+                          opt->length * 4);
+        nl_msg_end_nested(request, inner);
+
+        idx += sizeof(struct geneve_opt) + opt->length * 4;
+        tun_opt_len -= sizeof(struct geneve_opt) + opt->length * 4;
+    }
+
+    nl_msg_end_nested(request, outer);
+}
+
 static void
 nl_msg_put_act_tunnel_key_set(struct ofpbuf *request, ovs_be64 id,
-                                ovs_be32 ipv4_src, ovs_be32 ipv4_dst,
-                                struct in6_addr *ipv6_src,
-                                struct in6_addr *ipv6_dst,
-                                ovs_be16 tp_dst,
-                                uint8_t tos, uint8_t ttl)
+                              ovs_be32 ipv4_src, ovs_be32 ipv4_dst,
+                              struct in6_addr *ipv6_src,
+                              struct in6_addr *ipv6_dst,
+                              ovs_be16 tp_dst, uint8_t tos, uint8_t ttl,
+                              struct tun_metadata tun_metadata)
 {
     size_t offset;
 
@@ -1304,6 +1403,7 @@ nl_msg_put_act_tunnel_key_set(struct ofpbuf *request, ovs_be64 id,
             nl_msg_put_u8(request, TCA_TUNNEL_KEY_ENC_TTL, ttl);
         }
         nl_msg_put_be16(request, TCA_TUNNEL_KEY_ENC_DST_PORT, tp_dst);
+        nl_msg_put_act_tunnel_geneve_option(request, tun_metadata);
     }
     nl_msg_end_nested(request, offset);
 }
@@ -1546,7 +1646,8 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
                                               &action->encap.ipv6.ipv6_dst,
                                               action->encap.tp_dst,
                                               action->encap.tos,
-                                              action->encap.ttl);
+                                              action->encap.ttl,
+                                              action->encap.data);
                 nl_msg_end_nested(request, act_offset);
             }
             break;
diff --git a/lib/tc.h b/lib/tc.h
index aa8805df2..4ae06eb27 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -27,6 +27,7 @@
 #include "odp-netlink.h"
 #include "openvswitch/ofpbuf.h"
 #include "openvswitch/flow.h"
+#include "openvswitch/tun-metadata.h"
 
 /* For backwards compatability with older kernels */
 #ifndef TC_H_CLSACT
@@ -140,6 +141,7 @@ struct tc_action {
                 struct in6_addr ipv6_src;
                 struct in6_addr ipv6_dst;
             } ipv6;
+            struct tun_metadata data;
         } encap;
      };
 
