diff mbox series

[ovs-dev,v2,1/4] netdev-offload-tc: Check if TCA_FLOWER_KEY_ENC_FLAGS is supported.

Message ID b2c35c5b99eec4a18eb6d8f723c3298bdc009ea1.1728907640.git.echaudro@redhat.com
State New
Delegated to: Simon Horman
Headers show
Series Add missing TC flower tunnel match and encap flags. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Eelco Chaudron Oct. 14, 2024, 12:09 p.m. UTC
This patch checks to see if the TCA_FLOWER_KEY_ENC_FLAGS key is supported.

Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
---
 acinclude.m4            |  6 +++---
 include/linux/pkt_cls.h | 28 +++++++++++++++++++++++-
 lib/netdev-offload-tc.c | 47 ++++++++++++++++++++++++++++++++++++++++-
 lib/tc.c                | 29 ++++++++++++++++++++++---
 lib/tc.h                | 15 ++++++++++---
 5 files changed, 114 insertions(+), 11 deletions(-)

Comments

Roi Dayan Oct. 15, 2024, 10:34 a.m. UTC | #1
On 14/10/2024 15:09, Eelco Chaudron wrote:
> This patch checks to see if the TCA_FLOWER_KEY_ENC_FLAGS key is supported.
> 
> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
> ---
>  acinclude.m4            |  6 +++---
>  include/linux/pkt_cls.h | 28 +++++++++++++++++++++++-
>  lib/netdev-offload-tc.c | 47 ++++++++++++++++++++++++++++++++++++++++-
>  lib/tc.c                | 29 ++++++++++++++++++++++---
>  lib/tc.h                | 15 ++++++++++---
>  5 files changed, 114 insertions(+), 11 deletions(-)
> 
> diff --git a/acinclude.m4 b/acinclude.m4
> index 1ace70c92..f4cb4a146 100644
> --- a/acinclude.m4
> +++ b/acinclude.m4
> @@ -163,10 +163,10 @@ dnl Configure Linux tc compat.
>  AC_DEFUN([OVS_CHECK_LINUX_TC], [
>    AC_COMPILE_IFELSE([
>      AC_LANG_PROGRAM([#include <linux/pkt_cls.h>], [
> -        int x = TCA_ACT_FLAGS_SKIP_HW;
> +        int x = TCA_FLOWER_KEY_ENC_FLAGS_MASK;
>      ])],
> -    [AC_DEFINE([HAVE_TCA_ACT_FLAGS_SKIP_HW], [1],
> -               [Define to 1 if TCA_ACT_FLAGS_SKIP_HW is available.])])
> +    [AC_DEFINE([HAVE_TCA_FLOWER_KEY_ENC_FLAGS_MASK], [1],
> +               [Define to 1 if TCA_FLOWER_KEY_ENC_FLAGS_MASK is available.])])
>  
>    AC_CHECK_MEMBERS([struct tcf_t.firstuse], [], [], [#include <linux/pkt_cls.h>])
>  
> diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
> index fb4a7ecea..5890dc157 100644
> --- a/include/linux/pkt_cls.h
> +++ b/include/linux/pkt_cls.h
> @@ -1,7 +1,7 @@
>  #ifndef __LINUX_PKT_CLS_WRAPPER_H
>  #define __LINUX_PKT_CLS_WRAPPER_H 1
>  
> -#if defined(__KERNEL__) || defined(HAVE_TCA_ACT_FLAGS_SKIP_HW)
> +#if defined(__KERNEL__) || defined(HAVE_TCA_FLOWER_KEY_ENC_FLAGS_MASK)
>  #include_next <linux/pkt_cls.h>
>  #else
>  
> @@ -252,6 +252,28 @@ enum {
>  	TCA_FLOWER_KEY_CT_LABELS,	/* u128 */
>  	TCA_FLOWER_KEY_CT_LABELS_MASK,	/* u128 */
>  
> +	TCA_FLOWER_KEY_MPLS_OPTS,
> +
> +	TCA_FLOWER_KEY_HASH,		/* u32 */
> +	TCA_FLOWER_KEY_HASH_MASK,	/* u32 */
> +
> +	TCA_FLOWER_KEY_NUM_OF_VLANS,    /* u8 */
> +
> +	TCA_FLOWER_KEY_PPPOE_SID,	/* be16 */
> +	TCA_FLOWER_KEY_PPP_PROTO,	/* be16 */
> +
> +	TCA_FLOWER_KEY_L2TPV3_SID,	/* be32 */
> +
> +	TCA_FLOWER_L2_MISS,		/* u8 */
> +
> +	TCA_FLOWER_KEY_CFM,		/* nested */
> +
> +	TCA_FLOWER_KEY_SPI,		/* be32 */
> +	TCA_FLOWER_KEY_SPI_MASK,	/* be32 */
> +
> +	TCA_FLOWER_KEY_ENC_FLAGS,	/* be32 */
> +	TCA_FLOWER_KEY_ENC_FLAGS_MASK,	/* be32 */
> +
>  	__TCA_FLOWER_MAX,
>  };
>  
> @@ -306,6 +328,10 @@ enum {
>  enum {
>  	TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT = (1 << 0),
>  	TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST = (1 << 1),
> +	TCA_FLOWER_KEY_FLAGS_TUNNEL_CSUM = (1 << 2),
> +	TCA_FLOWER_KEY_FLAGS_TUNNEL_DONT_FRAGMENT = (1 << 3),
> +	TCA_FLOWER_KEY_FLAGS_TUNNEL_OAM = (1 << 4),
> +	TCA_FLOWER_KEY_FLAGS_TUNNEL_CRIT_OPT = (1 << 5),
>  };
>  
>  /* Match-all classifier */
> diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
> index 3be1c08d2..a2aa4062a 100644
> --- a/lib/netdev-offload-tc.c
> +++ b/lib/netdev-offload-tc.c
> @@ -53,6 +53,7 @@ static bool multi_mask_per_prio = false;
>  static bool block_support = false;
>  static uint16_t ct_state_support;
>  static bool vxlan_gbp_support = false;
> +static bool enc_flags_support = false;
>  
>  struct netlink_field {
>      int offset;
> @@ -407,7 +408,7 @@ get_next_available_prio(ovs_be16 protocol)
>  
>      /* last_prio can overflow if there will be many different kinds of
>       * flows which shouldn't happen organically. */
> -    if (last_prio == UINT16_MAX) {
> +    if (last_prio == TC_MAX_PRIORITY) {
>          return TC_RESERVED_PRIORITY_NONE;
>      }
>  
> @@ -2863,6 +2864,49 @@ out:
>      tc_add_del_qdisc(ifindex, false, block_id, TC_INGRESS);
>  }
>  
> +static void
> +probe_enc_flags_support(int ifindex)
> +{
> +    struct tc_flower flower;
> +    struct tcf_id id;
> +    int block_id = 0;
> +    int prio = TC_RESERVED_PRIORITY_FEATURE_PROBE;
> +    int error;
> +
> +    error = tc_add_del_qdisc(ifindex, true, block_id, TC_INGRESS);
> +    if (error) {
> +        return;
> +    }
> +
> +    memset(&flower, 0, sizeof flower);
> +    flower.tc_policy = TC_POLICY_SKIP_HW;
> +    flower.key.eth_type = htons(ETH_P_IP);
> +    flower.mask.eth_type = OVS_BE16_MAX;
> +    flower.tunnel = true;
> +    flower.mask.tunnel.id = OVS_BE64_MAX;
> +    flower.mask.tunnel.ipv4.ipv4_src = OVS_BE32_MAX;
> +    flower.mask.tunnel.ipv4.ipv4_dst = OVS_BE32_MAX;
> +    flower.mask.tunnel.tp_dst = OVS_BE16_MAX;
> +    flower.mask.tunnel.tc_enc_flags = TCA_FLOWER_KEY_FLAGS_TUNNEL_CRIT_OPT;
> +    flower.key.tunnel.ipv4.ipv4_src = htonl(0x01010101);
> +    flower.key.tunnel.ipv4.ipv4_dst = htonl(0x01010102);
> +    flower.key.tunnel.tp_dst = htons(46354);
> +    flower.key.tunnel.tc_enc_flags = TCA_FLOWER_KEY_FLAGS_TUNNEL_CRIT_OPT;
> +
> +    id = tc_make_tcf_id(ifindex, block_id, prio, TC_INGRESS);
> +    error = tc_replace_flower(&id, &flower);
> +    if (error) {
> +        goto out;
> +    }
> +
> +    tc_del_flower_filter(&id);
> +
> +    enc_flags_support = true;
> +    VLOG_INFO("probe tc: enc flags are supported.");
> +out:
> +    tc_add_del_qdisc(ifindex, false, block_id, TC_INGRESS);
> +}
> +
>  static int
>  tc_get_policer_action_ids(struct hmap *map)
>  {
> @@ -2991,6 +3035,7 @@ netdev_tc_init_flow_api(struct netdev *netdev)
>          probe_multi_mask_per_prio(ifindex);
>          probe_ct_state_support(ifindex);
>          probe_vxlan_gbp_support(ifindex);
> +        probe_enc_flags_support(ifindex);
>  
>          ovs_mutex_lock(&meter_police_ids_mutex);
>          meter_police_ids = id_pool_create(METER_POLICE_IDS_BASE,
> diff --git a/lib/tc.c b/lib/tc.c
> index e55ba3b1b..6c853b8e6 100644
> --- a/lib/tc.c
> +++ b/lib/tc.c
> @@ -454,6 +454,9 @@ static const struct nl_policy tca_flower_policy[] = {
>      [TCA_FLOWER_KEY_ENC_OPTS] = { .type = NL_A_NESTED, .optional = true, },
>      [TCA_FLOWER_KEY_ENC_OPTS_MASK] = { .type = NL_A_NESTED,
>                                         .optional = true, },
> +    [TCA_FLOWER_KEY_ENC_FLAGS] = { .type = NL_A_BE32, .optional = true, },
> +    [TCA_FLOWER_KEY_ENC_FLAGS_MASK] = { .type = NL_A_BE32,
> +                                        .optional = true, },
>      [TCA_FLOWER_KEY_CT_STATE] = { .type = NL_A_U16, .optional = true, },
>      [TCA_FLOWER_KEY_CT_STATE_MASK] = { .type = NL_A_U16, .optional = true, },
>      [TCA_FLOWER_KEY_CT_ZONE] = { .type = NL_A_U16, .optional = true, },
> @@ -865,6 +868,13 @@ nl_parse_flower_tunnel(struct nlattr **attrs, struct tc_flower *flower)
>          flower->tunnel = true;
>      }
>  
> +    if (attrs[TCA_FLOWER_KEY_ENC_FLAGS_MASK]) {
> +        flower->key.tunnel.tc_enc_flags = ntohl(
> +            nl_attr_get_be32(attrs[TCA_FLOWER_KEY_ENC_FLAGS]));
> +        flower->mask.tunnel.tc_enc_flags = ntohl(
> +            nl_attr_get_be32(attrs[TCA_FLOWER_KEY_ENC_FLAGS_MASK]));
> +    }
> +
>      if (attrs[TCA_FLOWER_KEY_ENC_OPTS] &&
>          attrs[TCA_FLOWER_KEY_ENC_OPTS_MASK]) {
>           err = nl_parse_flower_tunnel_opts(attrs[TCA_FLOWER_KEY_ENC_OPTS],
> @@ -3611,6 +3621,7 @@ nl_msg_put_flower_tunnel(struct ofpbuf *request, struct tc_flower *flower)
>      struct in6_addr *ipv6_src = &flower->key.tunnel.ipv6.ipv6_src;
>      struct in6_addr *ipv6_dst = &flower->key.tunnel.ipv6.ipv6_dst;
>      ovs_be32 id = be64_to_be32(flower->key.tunnel.id);
> +    ovs_be32 enc_flags = htonl(flower->key.tunnel.tc_enc_flags);
>      ovs_be16 tp_src = flower->key.tunnel.tp_src;
>      ovs_be16 tp_dst = flower->key.tunnel.tp_dst;
>      uint8_t tos = flower->key.tunnel.tos;
> @@ -3618,6 +3629,7 @@ nl_msg_put_flower_tunnel(struct ofpbuf *request, struct tc_flower *flower)
>      uint8_t tos_mask = flower->mask.tunnel.tos;
>      uint8_t ttl_mask = flower->mask.tunnel.ttl;
>      ovs_be64 id_mask = flower->mask.tunnel.id;
> +    ovs_be32 enc_flags_mask = htonl(flower->mask.tunnel.tc_enc_flags);
>      ovs_be16 tp_src_mask = flower->mask.tunnel.tp_src;
>      ovs_be16 tp_dst_mask = flower->mask.tunnel.tp_dst;
>  
> @@ -3655,6 +3667,11 @@ nl_msg_put_flower_tunnel(struct ofpbuf *request, struct tc_flower *flower)
>          nl_msg_put_be16(request, TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK,
>                          tp_dst_mask);
>      }
> +    if (enc_flags_mask) {
> +        nl_msg_put_be32(request, TCA_FLOWER_KEY_ENC_FLAGS, enc_flags);
> +        nl_msg_put_be32(request, TCA_FLOWER_KEY_ENC_FLAGS_MASK,
> +                        enc_flags_mask);
> +    }
>      if (id_mask) {
>          nl_msg_put_be32(request, TCA_FLOWER_KEY_ENC_KEY_ID, id);
>      }
> @@ -3961,6 +3978,7 @@ tc_replace_flower(struct tcf_id *id, struct tc_flower *flower)
>          struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
>          struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
>          struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
> +        bool is_probe = id->prio == TC_RESERVED_PRIORITY_FEATURE_PROBE;
>  
>          if (!nlmsg || !tc) {
>              COVERAGE_INC(tc_netlink_malformed_reply);
> @@ -3980,9 +3998,14 @@ tc_replace_flower(struct tcf_id *id, struct tc_flower *flower)
>                                               false);
>  
>              if (ret || !cmp_tc_flower_match_action(flower, &flower_out)) {
> -                VLOG_WARN_RL(&error_rl, "Kernel flower acknowledgment does "
> -                             "not match request!  Set dpif_netlink to dbg to "
> -                             "see which rule caused this error.");
> +                if (is_probe) {
> +                    error = EINVAL;
> +                } else {
> +                    VLOG_WARN_RL(&error_rl, "Kernel flower acknowledgment "
> +                                            "does not match request!  Set "
> +                                            "dpif_netlink to dbg to see "
> +                                            "which rule caused this error.");
> +                }
>              }
>          }
>          ofpbuf_delete(reply);
> diff --git a/lib/tc.h b/lib/tc.h
> index 8442c8d8b..c0a1d5d0e 100644
> --- a/lib/tc.h
> +++ b/lib/tc.h
> @@ -46,15 +46,23 @@
>  
>  #define TC_POLICY_DEFAULT "none"
>  
> -enum tc_flower_reserved_prio {
> +enum tc_flower_reserved_prio
> +{
>      TC_RESERVED_PRIORITY_NONE,
>      TC_RESERVED_PRIORITY_POLICE,
>      TC_RESERVED_PRIORITY_IPV4,
>      TC_RESERVED_PRIORITY_IPV6,
>      TC_RESERVED_PRIORITY_VLAN,
> -    __TC_RESERVED_PRIORITY_MAX
> +    __TC_RESERVED_PRIORITY_MAX,
> +
> +    TC_MAX_PRIORITY = UINT16_MAX - 1,
> +    /* This priority is reserved solely for probing purposes.
> +     * Since it's not used in actual traffic flows, we assign it a high value
> +     * to avoid impacting vendor specific hardware offload implementations. */
> +    TC_RESERVED_PRIORITY_FEATURE_PROBE
>  };
> -#define TC_RESERVED_PRIORITY_MAX (__TC_RESERVED_PRIORITY_MAX -1)
> +#define TC_RESERVED_PRIORITY_MAX (__TC_RESERVED_PRIORITY_MAX - 1)
> +
>  
>  enum tc_qdisc_hook {
>      TC_INGRESS,
> @@ -125,6 +133,7 @@ struct tc_flower_tunnel {
>      uint8_t ttl;
>      ovs_be16 tp_src;
>      ovs_be16 tp_dst;
> +    uint32_t tc_enc_flags;
>      struct tc_tunnel_gbp gbp;
>      ovs_be64 id;
>      struct tun_metadata metadata;

Acked-by: Roi Dayan <roid@nvidia.com>
diff mbox series

Patch

diff --git a/acinclude.m4 b/acinclude.m4
index 1ace70c92..f4cb4a146 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -163,10 +163,10 @@  dnl Configure Linux tc compat.
 AC_DEFUN([OVS_CHECK_LINUX_TC], [
   AC_COMPILE_IFELSE([
     AC_LANG_PROGRAM([#include <linux/pkt_cls.h>], [
-        int x = TCA_ACT_FLAGS_SKIP_HW;
+        int x = TCA_FLOWER_KEY_ENC_FLAGS_MASK;
     ])],
-    [AC_DEFINE([HAVE_TCA_ACT_FLAGS_SKIP_HW], [1],
-               [Define to 1 if TCA_ACT_FLAGS_SKIP_HW is available.])])
+    [AC_DEFINE([HAVE_TCA_FLOWER_KEY_ENC_FLAGS_MASK], [1],
+               [Define to 1 if TCA_FLOWER_KEY_ENC_FLAGS_MASK is available.])])
 
   AC_CHECK_MEMBERS([struct tcf_t.firstuse], [], [], [#include <linux/pkt_cls.h>])
 
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index fb4a7ecea..5890dc157 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -1,7 +1,7 @@ 
 #ifndef __LINUX_PKT_CLS_WRAPPER_H
 #define __LINUX_PKT_CLS_WRAPPER_H 1
 
-#if defined(__KERNEL__) || defined(HAVE_TCA_ACT_FLAGS_SKIP_HW)
+#if defined(__KERNEL__) || defined(HAVE_TCA_FLOWER_KEY_ENC_FLAGS_MASK)
 #include_next <linux/pkt_cls.h>
 #else
 
@@ -252,6 +252,28 @@  enum {
 	TCA_FLOWER_KEY_CT_LABELS,	/* u128 */
 	TCA_FLOWER_KEY_CT_LABELS_MASK,	/* u128 */
 
+	TCA_FLOWER_KEY_MPLS_OPTS,
+
+	TCA_FLOWER_KEY_HASH,		/* u32 */
+	TCA_FLOWER_KEY_HASH_MASK,	/* u32 */
+
+	TCA_FLOWER_KEY_NUM_OF_VLANS,    /* u8 */
+
+	TCA_FLOWER_KEY_PPPOE_SID,	/* be16 */
+	TCA_FLOWER_KEY_PPP_PROTO,	/* be16 */
+
+	TCA_FLOWER_KEY_L2TPV3_SID,	/* be32 */
+
+	TCA_FLOWER_L2_MISS,		/* u8 */
+
+	TCA_FLOWER_KEY_CFM,		/* nested */
+
+	TCA_FLOWER_KEY_SPI,		/* be32 */
+	TCA_FLOWER_KEY_SPI_MASK,	/* be32 */
+
+	TCA_FLOWER_KEY_ENC_FLAGS,	/* be32 */
+	TCA_FLOWER_KEY_ENC_FLAGS_MASK,	/* be32 */
+
 	__TCA_FLOWER_MAX,
 };
 
@@ -306,6 +328,10 @@  enum {
 enum {
 	TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT = (1 << 0),
 	TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST = (1 << 1),
+	TCA_FLOWER_KEY_FLAGS_TUNNEL_CSUM = (1 << 2),
+	TCA_FLOWER_KEY_FLAGS_TUNNEL_DONT_FRAGMENT = (1 << 3),
+	TCA_FLOWER_KEY_FLAGS_TUNNEL_OAM = (1 << 4),
+	TCA_FLOWER_KEY_FLAGS_TUNNEL_CRIT_OPT = (1 << 5),
 };
 
 /* Match-all classifier */
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 3be1c08d2..a2aa4062a 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -53,6 +53,7 @@  static bool multi_mask_per_prio = false;
 static bool block_support = false;
 static uint16_t ct_state_support;
 static bool vxlan_gbp_support = false;
+static bool enc_flags_support = false;
 
 struct netlink_field {
     int offset;
@@ -407,7 +408,7 @@  get_next_available_prio(ovs_be16 protocol)
 
     /* last_prio can overflow if there will be many different kinds of
      * flows which shouldn't happen organically. */
-    if (last_prio == UINT16_MAX) {
+    if (last_prio == TC_MAX_PRIORITY) {
         return TC_RESERVED_PRIORITY_NONE;
     }
 
@@ -2863,6 +2864,49 @@  out:
     tc_add_del_qdisc(ifindex, false, block_id, TC_INGRESS);
 }
 
+static void
+probe_enc_flags_support(int ifindex)
+{
+    struct tc_flower flower;
+    struct tcf_id id;
+    int block_id = 0;
+    int prio = TC_RESERVED_PRIORITY_FEATURE_PROBE;
+    int error;
+
+    error = tc_add_del_qdisc(ifindex, true, block_id, TC_INGRESS);
+    if (error) {
+        return;
+    }
+
+    memset(&flower, 0, sizeof flower);
+    flower.tc_policy = TC_POLICY_SKIP_HW;
+    flower.key.eth_type = htons(ETH_P_IP);
+    flower.mask.eth_type = OVS_BE16_MAX;
+    flower.tunnel = true;
+    flower.mask.tunnel.id = OVS_BE64_MAX;
+    flower.mask.tunnel.ipv4.ipv4_src = OVS_BE32_MAX;
+    flower.mask.tunnel.ipv4.ipv4_dst = OVS_BE32_MAX;
+    flower.mask.tunnel.tp_dst = OVS_BE16_MAX;
+    flower.mask.tunnel.tc_enc_flags = TCA_FLOWER_KEY_FLAGS_TUNNEL_CRIT_OPT;
+    flower.key.tunnel.ipv4.ipv4_src = htonl(0x01010101);
+    flower.key.tunnel.ipv4.ipv4_dst = htonl(0x01010102);
+    flower.key.tunnel.tp_dst = htons(46354);
+    flower.key.tunnel.tc_enc_flags = TCA_FLOWER_KEY_FLAGS_TUNNEL_CRIT_OPT;
+
+    id = tc_make_tcf_id(ifindex, block_id, prio, TC_INGRESS);
+    error = tc_replace_flower(&id, &flower);
+    if (error) {
+        goto out;
+    }
+
+    tc_del_flower_filter(&id);
+
+    enc_flags_support = true;
+    VLOG_INFO("probe tc: enc flags are supported.");
+out:
+    tc_add_del_qdisc(ifindex, false, block_id, TC_INGRESS);
+}
+
 static int
 tc_get_policer_action_ids(struct hmap *map)
 {
@@ -2991,6 +3035,7 @@  netdev_tc_init_flow_api(struct netdev *netdev)
         probe_multi_mask_per_prio(ifindex);
         probe_ct_state_support(ifindex);
         probe_vxlan_gbp_support(ifindex);
+        probe_enc_flags_support(ifindex);
 
         ovs_mutex_lock(&meter_police_ids_mutex);
         meter_police_ids = id_pool_create(METER_POLICE_IDS_BASE,
diff --git a/lib/tc.c b/lib/tc.c
index e55ba3b1b..6c853b8e6 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -454,6 +454,9 @@  static const struct nl_policy tca_flower_policy[] = {
     [TCA_FLOWER_KEY_ENC_OPTS] = { .type = NL_A_NESTED, .optional = true, },
     [TCA_FLOWER_KEY_ENC_OPTS_MASK] = { .type = NL_A_NESTED,
                                        .optional = true, },
+    [TCA_FLOWER_KEY_ENC_FLAGS] = { .type = NL_A_BE32, .optional = true, },
+    [TCA_FLOWER_KEY_ENC_FLAGS_MASK] = { .type = NL_A_BE32,
+                                        .optional = true, },
     [TCA_FLOWER_KEY_CT_STATE] = { .type = NL_A_U16, .optional = true, },
     [TCA_FLOWER_KEY_CT_STATE_MASK] = { .type = NL_A_U16, .optional = true, },
     [TCA_FLOWER_KEY_CT_ZONE] = { .type = NL_A_U16, .optional = true, },
@@ -865,6 +868,13 @@  nl_parse_flower_tunnel(struct nlattr **attrs, struct tc_flower *flower)
         flower->tunnel = true;
     }
 
+    if (attrs[TCA_FLOWER_KEY_ENC_FLAGS_MASK]) {
+        flower->key.tunnel.tc_enc_flags = ntohl(
+            nl_attr_get_be32(attrs[TCA_FLOWER_KEY_ENC_FLAGS]));
+        flower->mask.tunnel.tc_enc_flags = ntohl(
+            nl_attr_get_be32(attrs[TCA_FLOWER_KEY_ENC_FLAGS_MASK]));
+    }
+
     if (attrs[TCA_FLOWER_KEY_ENC_OPTS] &&
         attrs[TCA_FLOWER_KEY_ENC_OPTS_MASK]) {
          err = nl_parse_flower_tunnel_opts(attrs[TCA_FLOWER_KEY_ENC_OPTS],
@@ -3611,6 +3621,7 @@  nl_msg_put_flower_tunnel(struct ofpbuf *request, struct tc_flower *flower)
     struct in6_addr *ipv6_src = &flower->key.tunnel.ipv6.ipv6_src;
     struct in6_addr *ipv6_dst = &flower->key.tunnel.ipv6.ipv6_dst;
     ovs_be32 id = be64_to_be32(flower->key.tunnel.id);
+    ovs_be32 enc_flags = htonl(flower->key.tunnel.tc_enc_flags);
     ovs_be16 tp_src = flower->key.tunnel.tp_src;
     ovs_be16 tp_dst = flower->key.tunnel.tp_dst;
     uint8_t tos = flower->key.tunnel.tos;
@@ -3618,6 +3629,7 @@  nl_msg_put_flower_tunnel(struct ofpbuf *request, struct tc_flower *flower)
     uint8_t tos_mask = flower->mask.tunnel.tos;
     uint8_t ttl_mask = flower->mask.tunnel.ttl;
     ovs_be64 id_mask = flower->mask.tunnel.id;
+    ovs_be32 enc_flags_mask = htonl(flower->mask.tunnel.tc_enc_flags);
     ovs_be16 tp_src_mask = flower->mask.tunnel.tp_src;
     ovs_be16 tp_dst_mask = flower->mask.tunnel.tp_dst;
 
@@ -3655,6 +3667,11 @@  nl_msg_put_flower_tunnel(struct ofpbuf *request, struct tc_flower *flower)
         nl_msg_put_be16(request, TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK,
                         tp_dst_mask);
     }
+    if (enc_flags_mask) {
+        nl_msg_put_be32(request, TCA_FLOWER_KEY_ENC_FLAGS, enc_flags);
+        nl_msg_put_be32(request, TCA_FLOWER_KEY_ENC_FLAGS_MASK,
+                        enc_flags_mask);
+    }
     if (id_mask) {
         nl_msg_put_be32(request, TCA_FLOWER_KEY_ENC_KEY_ID, id);
     }
@@ -3961,6 +3978,7 @@  tc_replace_flower(struct tcf_id *id, struct tc_flower *flower)
         struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
         struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
         struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
+        bool is_probe = id->prio == TC_RESERVED_PRIORITY_FEATURE_PROBE;
 
         if (!nlmsg || !tc) {
             COVERAGE_INC(tc_netlink_malformed_reply);
@@ -3980,9 +3998,14 @@  tc_replace_flower(struct tcf_id *id, struct tc_flower *flower)
                                              false);
 
             if (ret || !cmp_tc_flower_match_action(flower, &flower_out)) {
-                VLOG_WARN_RL(&error_rl, "Kernel flower acknowledgment does "
-                             "not match request!  Set dpif_netlink to dbg to "
-                             "see which rule caused this error.");
+                if (is_probe) {
+                    error = EINVAL;
+                } else {
+                    VLOG_WARN_RL(&error_rl, "Kernel flower acknowledgment "
+                                            "does not match request!  Set "
+                                            "dpif_netlink to dbg to see "
+                                            "which rule caused this error.");
+                }
             }
         }
         ofpbuf_delete(reply);
diff --git a/lib/tc.h b/lib/tc.h
index 8442c8d8b..c0a1d5d0e 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -46,15 +46,23 @@ 
 
 #define TC_POLICY_DEFAULT "none"
 
-enum tc_flower_reserved_prio {
+enum tc_flower_reserved_prio
+{
     TC_RESERVED_PRIORITY_NONE,
     TC_RESERVED_PRIORITY_POLICE,
     TC_RESERVED_PRIORITY_IPV4,
     TC_RESERVED_PRIORITY_IPV6,
     TC_RESERVED_PRIORITY_VLAN,
-    __TC_RESERVED_PRIORITY_MAX
+    __TC_RESERVED_PRIORITY_MAX,
+
+    TC_MAX_PRIORITY = UINT16_MAX - 1,
+    /* This priority is reserved solely for probing purposes.
+     * Since it's not used in actual traffic flows, we assign it a high value
+     * to avoid impacting vendor specific hardware offload implementations. */
+    TC_RESERVED_PRIORITY_FEATURE_PROBE
 };
-#define TC_RESERVED_PRIORITY_MAX (__TC_RESERVED_PRIORITY_MAX -1)
+#define TC_RESERVED_PRIORITY_MAX (__TC_RESERVED_PRIORITY_MAX - 1)
+
 
 enum tc_qdisc_hook {
     TC_INGRESS,
@@ -125,6 +133,7 @@  struct tc_flower_tunnel {
     uint8_t ttl;
     ovs_be16 tp_src;
     ovs_be16 tp_dst;
+    uint32_t tc_enc_flags;
     struct tc_tunnel_gbp gbp;
     ovs_be64 id;
     struct tun_metadata metadata;