From: Toms Atteka <tatt...@vmware.com>

IPv6 extension headers carry optional internet layer information
and are placed between the fixed header and the upper-layer
protocol header.

This change adds a new OpenFlow field OFPXMT_OFB_IPV6_EXTHDR and
packets can be filtered using ipv6_ext flag.

Some spacing style issues fixed.

Tested-at: https://github.com/TomCodeLV/ovs/actions/runs/504185214
Signed-off-by: Toms Atteka <cpp.code...@gmail.com>
---
 build-aux/extract-odp-netlink-macros-h        |   1 +
 build-aux/extract-ofp-fields                  |   4 +-
 datapath/flow.c                               | 192 +++++++++++++++---
 datapath/flow.h                               |  14 ++
 datapath/flow_netlink.c                       |  25 ++-
 .../linux/compat/include/linux/openvswitch.h  |   6 +
 include/openvswitch/flow.h                    |   8 +-
 include/openvswitch/match.h                   |   2 +
 include/openvswitch/meta-flow.h               |  18 ++
 lib/dpif-netdev-extract-avx512.c              |   2 +-
 lib/flow.c                                    | 188 ++++++++++++++++-
 lib/flow.h                                    |   6 +-
 lib/match.c                                   |  25 ++-
 lib/meta-flow.c                               |  80 +++++++-
 lib/meta-flow.xml                             |  14 ++
 lib/nx-match.c                                |  13 +-
 lib/odp-execute.c                             |   2 +
 lib/odp-util.c                                | 110 +++++++++-
 lib/odp-util.h                                |  11 +-
 lib/ofp-match.c                               |   2 +-
 lib/packets.c                                 |  28 +++
 lib/packets.h                                 |  12 ++
 ofproto/ofproto-dpif-rid.h                    |   2 +-
 ofproto/ofproto-dpif-sflow.c                  |   1 +
 ofproto/ofproto-dpif-xlate.c                  |   2 +-
 ofproto/ofproto-dpif.c                        |  47 +++++
 tests/ofproto.at                              |   4 +-
 tests/system-traffic.at                       |  31 +++
 28 files changed, 788 insertions(+), 62 deletions(-)

diff --git a/build-aux/extract-odp-netlink-macros-h 
b/build-aux/extract-odp-netlink-macros-h
index 7152f298c..c5a2d91d2 100755
--- a/build-aux/extract-odp-netlink-macros-h
+++ b/build-aux/extract-odp-netlink-macros-h
@@ -55,6 +55,7 @@ generate_fields_macros "ovs_key_icmpv6"
 generate_fields_macros "ovs_key_arp"
 generate_fields_macros "ovs_key_nd"
 generate_fields_macros "ovs_key_nd_extensions"
+generate_fields_macros "ovs_key_ipv6_exthdrs"
 
 echo
 echo "#endif"
diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
index 8766995d9..df71e4dfa 100755
--- a/build-aux/extract-ofp-fields
+++ b/build-aux/extract-ofp-fields
@@ -19,6 +19,7 @@ VERSION = {"1.0": 0x01,
 VERSION_REVERSE = dict((v,k) for k, v in VERSION.items())
 
 TYPES = {"u8":       (1,   False),
+         "u16":      (2,   False),
          "be16":     (2,   False),
          "be32":     (4,   False),
          "MAC":      (6,   False),
@@ -37,7 +38,8 @@ FORMATTING = {"decimal":            ("MFS_DECIMAL",      1,   
8),
               "frag":               ("MFS_FRAG",         1,   1),
               "tunnel flags":       ("MFS_TNL_FLAGS",    2,   2),
               "TCP flags":          ("MFS_TCP_FLAGS",    2,   2),
-              "packet type":        ("MFS_PACKET_TYPE",  4,   4)}
+              "packet type":        ("MFS_PACKET_TYPE",  4,   4),
+              "IPV6 ext hdr":       ("MFS_IPV6_EXTHDR",  2,   2)}
 
 PREREQS = {"none": "MFP_NONE",
            "Ethernet": "MFP_ETHERNET",
diff --git a/datapath/flow.c b/datapath/flow.c
index 5a00c238c..1247c298a 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -98,17 +98,17 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 
tcp_flags,
                         * allocated stats as we have already locked them.
                         */
                        if (likely(flow->stats_last_writer != -1) &&
-                           likely(!rcu_access_pointer(flow->stats[cpu]))) {
+                               likely(!rcu_access_pointer(flow->stats[cpu]))) {
                                /* Try to allocate CPU-specific stats. */
                                struct sw_flow_stats *new_stats;
 
                                new_stats =
                                        kmem_cache_alloc_node(flow_stats_cache,
-                                                              GFP_NOWAIT |
-                                                              __GFP_THISNODE |
-                                                              __GFP_NOWARN |
-                                                             __GFP_NOMEMALLOC,
-                                                             numa_node_id());
+                                                                               
                                          GFP_NOWAIT |
+                                                                               
                                          __GFP_THISNODE |
+                                                                               
                                          __GFP_NOWARN |
+                                                                 
__GFP_NOMEMALLOC,
+                                                                 
numa_node_id());
                                if (likely(new_stats)) {
                                        new_stats->used = jiffies;
                                        new_stats->packet_count = 1;
@@ -211,7 +211,7 @@ static int check_iphdr(struct sk_buff *skb)
 
        ip_len = ip_hdrlen(skb);
        if (unlikely(ip_len < sizeof(struct iphdr) ||
-                    skb->len < nh_ofs + ip_len))
+                        skb->len < nh_ofs + ip_len))
                return -EINVAL;
 
        skb_set_transport_header(skb, nh_ofs + ip_len);
@@ -228,7 +228,7 @@ static bool tcphdr_ok(struct sk_buff *skb)
 
        tcp_len = tcp_hdrlen(skb);
        if (unlikely(tcp_len < sizeof(struct tcphdr) ||
-                    skb->len < th_ofs + tcp_len))
+                        skb->len < th_ofs + tcp_len))
                return false;
 
        return true;
@@ -252,6 +252,144 @@ static bool icmphdr_ok(struct sk_buff *skb)
                                  sizeof(struct icmphdr));
 }
 
+/**
+ * get_ipv6_ext_hdrs() - Parses packet and sets IPv6 extension header flags.
+ *
+ * @skb: buffer where extension header data starts in packet
+ * @nh: ipv6 header
+ * @ext_hdrs: flags are stored here
+ *
+ * OFPIEH12_UNREP is set if more than one of a given IPv6 extension header
+ * is unexpectedly encountered. (Two destination options headers may be
+ * expected and would not cause this bit to be set.)
+ *
+ * OFPIEH12_UNSEQ is set if IPv6 extension headers were not in the order
+ * preferred (but not required) by RFC 2460:
+ *
+ * When more than one extension header is used in the same packet, it is
+ * recommended that those headers appear in the following order:
+ *      IPv6 header
+ *      Hop-by-Hop Options header
+ *      Destination Options header
+ *      Routing header
+ *      Fragment header
+ *      Authentication header
+ *      Encapsulating Security Payload header
+ *      Destination Options header
+ *      upper-layer header
+ */
+static void get_ipv6_ext_hdrs(struct sk_buff *skb, struct ipv6hdr *nh,
+                                                         u16 *ext_hdrs)
+{
+       u8 next_type = nh->nexthdr;
+       unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
+       int dest_options_header_count = 0;
+
+       *ext_hdrs = 0;
+
+       while (ipv6_ext_hdr(next_type)) {
+               struct ipv6_opt_hdr _hdr, *hp;
+
+               switch (next_type) {
+                       case IPPROTO_NONE:
+                               *ext_hdrs |= OFPIEH12_NONEXT;
+                               /* stop parsing */
+                               return;
+
+                       case IPPROTO_ESP:
+                               if (*ext_hdrs & OFPIEH12_ESP)
+                                       *ext_hdrs |= OFPIEH12_UNREP;
+                               if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST 
|
+                                                                  
OFPIEH12_ROUTER | IPPROTO_FRAGMENT |
+                                                                  
OFPIEH12_AUTH | OFPIEH12_UNREP)) ||
+                                       dest_options_header_count >= 2) {
+                                       *ext_hdrs |= OFPIEH12_UNSEQ;
+                               }
+                               *ext_hdrs |= OFPIEH12_ESP;
+                               break;
+
+                       case IPPROTO_AH:
+                               if (*ext_hdrs & OFPIEH12_AUTH)
+                                       *ext_hdrs |= OFPIEH12_UNREP;
+                               if ((*ext_hdrs &
+                                        ~(OFPIEH12_HOP | OFPIEH12_DEST | 
OFPIEH12_ROUTER |
+                                          IPPROTO_FRAGMENT | OFPIEH12_UNREP)) 
||
+                                       dest_options_header_count >= 2) {
+                                       *ext_hdrs |= OFPIEH12_UNSEQ;
+                               }
+                               *ext_hdrs |= OFPIEH12_AUTH;
+                               break;
+
+                       case IPPROTO_DSTOPTS:
+                               if (dest_options_header_count == 0) {
+                                       if (*ext_hdrs &
+                                               ~(OFPIEH12_HOP | 
OFPIEH12_UNREP))
+                                               *ext_hdrs |= OFPIEH12_UNSEQ;
+                                       *ext_hdrs |= OFPIEH12_DEST;
+                               } else if (dest_options_header_count == 1) {
+                                       if (*ext_hdrs &
+                                               ~(OFPIEH12_HOP | OFPIEH12_DEST |
+                                                 OFPIEH12_ROUTER | 
OFPIEH12_FRAG |
+                                                 OFPIEH12_AUTH | OFPIEH12_ESP |
+                                                 OFPIEH12_UNREP)) {
+                                               *ext_hdrs |= OFPIEH12_UNSEQ;
+                                       }
+                               } else {
+                                       *ext_hdrs |= OFPIEH12_UNREP;
+                               }
+                               dest_options_header_count++;
+                               break;
+
+                       case IPPROTO_FRAGMENT:
+                               if (*ext_hdrs & OFPIEH12_FRAG)
+                                       *ext_hdrs |= OFPIEH12_UNREP;
+                               if ((*ext_hdrs & ~(OFPIEH12_HOP |
+                                                                  
OFPIEH12_DEST |
+                                                                  
OFPIEH12_ROUTER |
+                                                                  
OFPIEH12_UNREP)) ||
+                                       dest_options_header_count >= 2) {
+                                       *ext_hdrs |= OFPIEH12_UNSEQ;
+                               }
+                               *ext_hdrs |= OFPIEH12_FRAG;
+                               break;
+
+                       case IPPROTO_ROUTING:
+                               if (*ext_hdrs & OFPIEH12_ROUTER)
+                                       *ext_hdrs |= OFPIEH12_UNREP;
+                               if ((*ext_hdrs & ~(OFPIEH12_HOP |
+                                                                  
OFPIEH12_DEST |
+                                                                  
OFPIEH12_UNREP)) ||
+                                       dest_options_header_count >= 2) {
+                                       *ext_hdrs |= OFPIEH12_UNSEQ;
+                               }
+                               *ext_hdrs |= OFPIEH12_ROUTER;
+                               break;
+
+                       case IPPROTO_HOPOPTS:
+                               if (*ext_hdrs & OFPIEH12_HOP)
+                                       *ext_hdrs |= OFPIEH12_UNREP;
+                               /* OFPIEH12_HOP is set to 1 if a hop-by-hop IPv6
+                                * extension header is present as the first
+                                * extension header in the packet.
+                                */
+                               if (*ext_hdrs == 0)
+                                       *ext_hdrs |= OFPIEH12_HOP;
+                               else
+                                       *ext_hdrs |= OFPIEH12_UNSEQ;
+                               break;
+
+                       default:
+                               return;
+               }
+
+               hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
+               if (!hp)
+                       break;
+               next_type = hp->nexthdr;
+               start += ipv6_optlen(hp);
+       }
+}
+
 static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key)
 {
        unsigned short frag_off;
@@ -267,6 +405,8 @@ static int parse_ipv6hdr(struct sk_buff *skb, struct 
sw_flow_key *key)
 
        nh = ipv6_hdr(skb);
 
+       get_ipv6_ext_hdrs(skb, nh, &key->ipv6.exthdrs);
+
        key->ip.proto = NEXTHDR_NONE;
        key->ip.tos = ipv6_get_dsfield(nh);
        key->ip.ttl = nh->hop_limit;
@@ -408,8 +548,8 @@ static __be16 parse_ethertype(struct sk_buff *skb)
 
        llc = (struct llc_snap_hdr *) skb->data;
        if (llc->dsap != LLC_SAP_SNAP ||
-           llc->ssap != LLC_SAP_SNAP ||
-           (llc->oui[0] | llc->oui[1] | llc->oui[2]) != 0)
+               llc->ssap != LLC_SAP_SNAP ||
+               (llc->oui[0] | llc->oui[1] | llc->oui[2]) != 0)
                return htons(ETH_P_802_2);
 
        __skb_pull(skb, sizeof(struct llc_snap_hdr));
@@ -433,8 +573,8 @@ static int parse_icmpv6(struct sk_buff *skb, struct 
sw_flow_key *key,
        memset(&key->ipv6.nd, 0, sizeof(key->ipv6.nd));
 
        if (icmp->icmp6_code == 0 &&
-           (icmp->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION ||
-            icmp->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT)) {
+               (icmp->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION ||
+                icmp->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT)) {
                int icmp_len = skb->len - skb_transport_offset(skb);
                struct nd_msg *nd;
                int offset;
@@ -466,7 +606,7 @@ static int parse_icmpv6(struct sk_buff *skb, struct 
sw_flow_key *key,
                         * the same link layer option is specified twice.
                         */
                        if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR
-                           && opt_len == 8) {
+                               && opt_len == 8) {
                                if 
(unlikely(!is_zero_ether_addr(key->ipv6.nd.sll)))
                                        goto invalid;
                                ether_addr_copy(key->ipv6.nd.sll,
@@ -527,11 +667,11 @@ static int parse_nsh(struct sk_buff *skb, struct 
sw_flow_key *key)
                if (length != NSH_M_TYPE1_LEN)
                        return -EINVAL;
                memcpy(key->nsh.context, nh->md1.context,
-                      sizeof(nh->md1));
+                          sizeof(nh->md1));
                break;
        case NSH_M_TYPE2:
                memset(key->nsh.context, 0,
-                      sizeof(nh->md1));
+                          sizeof(nh->md1));
                break;
        default:
                return -EINVAL;
@@ -639,10 +779,10 @@ static int key_extract_l3l4(struct sk_buff *skb, struct 
sw_flow_key *key)
                arp = (struct arp_eth_header *)skb_network_header(skb);
 
                if (arp_available &&
-                   arp->ar_hrd == htons(ARPHRD_ETHER) &&
-                   arp->ar_pro == htons(ETH_P_IP) &&
-                   arp->ar_hln == ETH_ALEN &&
-                   arp->ar_pln == 4) {
+                       arp->ar_hrd == htons(ARPHRD_ETHER) &&
+                       arp->ar_pro == htons(ETH_P_IP) &&
+                       arp->ar_hln == ETH_ALEN &&
+                       arp->ar_pln == 4) {
 
                        /* We only match on the lower 8 bits of the opcode. */
                        if (ntohs(arp->ar_op) <= 0xff)
@@ -667,7 +807,7 @@ static int key_extract_l3l4(struct sk_buff *skb, struct 
sw_flow_key *key)
                        __be32 lse;
 
                        error = check_header(skb, skb->mac_len +
-                                            label_count * MPLS_HLEN);
+                                                label_count * MPLS_HLEN);
                        if (unlikely(error))
                                return 0;
 
@@ -675,10 +815,10 @@ static int key_extract_l3l4(struct sk_buff *skb, struct 
sw_flow_key *key)
 
                        if (label_count <= MPLS_LABEL_DEPTH)
                                memcpy(&key->mpls.lse[label_count - 1], &lse,
-                                      MPLS_HLEN);
+                                          MPLS_HLEN);
 
                        skb_set_inner_network_header(skb, skb->mac_len +
-                                                    label_count * MPLS_HLEN);
+                                                        label_count * 
MPLS_HLEN);
                        if (lse & htonl(MPLS_LS_S_MASK))
                                break;
 
@@ -884,7 +1024,7 @@ int ovs_flow_key_extract(const struct ip_tunnel_info 
*tun_info,
                key->tun_proto = ip_tunnel_info_af(tun_info);
                memcpy(&key->tun_key, &tun_info->key, sizeof(key->tun_key));
                BUILD_BUG_ON(((1 << (sizeof(tun_info->options_len) * 8)) - 1) >
-                            sizeof(key->tun_opts));
+                                sizeof(key->tun_opts));
 
                if (tun_info->options_len) {
                        ip_tunnel_info_opts_get(TUN_METADATA_OPTS(key, 
tun_info->options_len),
@@ -961,11 +1101,11 @@ int ovs_flow_key_extract_userspace(struct net *net, 
const struct nlattr *attr,
         * corrupted due to overlapping key fields.
         */
        if (attrs & (1 << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4) &&
-           key->eth.type != htons(ETH_P_IP))
+               key->eth.type != htons(ETH_P_IP))
                return -EINVAL;
        if (attrs & (1 << OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6) &&
-           (key->eth.type != htons(ETH_P_IPV6) ||
-            sw_flow_key_is_nd(key)))
+               (key->eth.type != htons(ETH_P_IPV6) ||
+                sw_flow_key_is_nd(key)))
                return -EINVAL;
 
        return 0;
diff --git a/datapath/flow.h b/datapath/flow.h
index 584d9f565..9b185e771 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -45,6 +45,19 @@ enum sw_flow_mac_proto {
 #define SW_FLOW_KEY_INVALID    0x80
 #define MPLS_LABEL_DEPTH       3
 
+/* Bit definitions for IPv6 Extension Header pseudo-field. */
+enum ofp12_ipv6exthdr_flags {
+       OFPIEH12_NONEXT = 1 << 0,   /* "No next header" encountered. */
+       OFPIEH12_ESP    = 1 << 1,   /* Encrypted Sec Payload header present. */
+       OFPIEH12_AUTH   = 1 << 2,   /* Authentication header present. */
+       OFPIEH12_DEST   = 1 << 3,   /* 1 or 2 dest headers present. */
+       OFPIEH12_FRAG   = 1 << 4,   /* Fragment header present. */
+       OFPIEH12_ROUTER = 1 << 5,   /* Router header present. */
+       OFPIEH12_HOP    = 1 << 6,   /* Hop-by-hop header present. */
+       OFPIEH12_UNREP  = 1 << 7,   /* Unexpected repeats encountered. */
+       OFPIEH12_UNSEQ  = 1 << 8    /* Unexpected sequencing encountered. */
+};
+
 /* Store options at the end of the array if they are less than the
  * maximum size. This allows us to get the benefits of variable length
  * matching for small options.
@@ -134,6 +147,7 @@ struct sw_flow_key {
                                struct in6_addr dst;    /* IPv6 destination 
address. */
                        } addr;
                        __be32 label;                   /* IPv6 flow label. */
+                       u16 exthdrs;            /* IPv6 extension header flags 
*/
                        union {
                                struct {
                                        struct in6_addr src;
diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
index 996041602..81dba2a05 100644
--- a/datapath/flow_netlink.c
+++ b/datapath/flow_netlink.c
@@ -359,7 +359,7 @@ size_t ovs_key_attr_size(void)
        /* Whenever adding new OVS_KEY_ FIELDS, we should consider
         * updating this function.
         */
-       BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 29);
+       BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 30);
 
        return    nla_total_size(4)   /* OVS_KEY_ATTR_PRIORITY */
                + nla_total_size(0)   /* OVS_KEY_ATTR_TUNNEL */
@@ -382,7 +382,8 @@ size_t ovs_key_attr_size(void)
                + nla_total_size(2)   /* OVS_KEY_ATTR_ETHERTYPE */
                + nla_total_size(40)  /* OVS_KEY_ATTR_IPV6 */
                + nla_total_size(2)   /* OVS_KEY_ATTR_ICMPV6 */
-               + nla_total_size(28); /* OVS_KEY_ATTR_ND */
+               + nla_total_size(28)  /* OVS_KEY_ATTR_ND */
+               + nla_total_size(2);  /* OVS_KEY_ATTR_IPV6_EXTHDRS */
 }
 
 static const struct ovs_len_tbl ovs_vxlan_ext_key_lens[OVS_VXLAN_EXT_MAX + 1] 
= {
@@ -449,6 +450,8 @@ static const struct ovs_len_tbl 
ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = {
                .len = sizeof(struct ovs_key_ct_tuple_ipv6) },
        [OVS_KEY_ATTR_NSH]       = { .len = OVS_ATTR_NESTED,
                                     .next = ovs_nsh_key_attr_lens, },
+       [OVS_KEY_ATTR_IPV6_EXTHDRS] = {
+               .len = sizeof(struct ovs_key_ipv6_exthdrs) },
 };
 
 static bool check_attr_len(unsigned int attr_len, unsigned int expected_len)
@@ -1586,6 +1589,16 @@ static int ovs_key_from_nlattrs(struct net *net, struct 
sw_flow_match *match,
                attrs &= ~(1ULL << OVS_KEY_ATTR_IPV6);
        }
 
+       if (attrs & (1ULL << OVS_KEY_ATTR_IPV6_EXTHDRS)) {
+               const struct ovs_key_ipv6_exthdrs *ipv6_exthdrs_key;
+
+               ipv6_exthdrs_key = nla_data(a[OVS_KEY_ATTR_IPV6_EXTHDRS]);
+
+               SW_FLOW_KEY_PUT(match, ipv6.exthdrs, ipv6_exthdrs_key->hdrs, 
is_mask);
+
+               attrs &= ~(1ULL << OVS_KEY_ATTR_IPV6_EXTHDRS);
+       }
+
        if (attrs & (1ULL << OVS_KEY_ATTR_ARP)) {
                const struct ovs_key_arp *arp_key;
 
@@ -2089,6 +2102,7 @@ static int __ovs_nla_put_key(const struct sw_flow_key 
*swkey,
                ipv4_key->ipv4_frag = output->ip.frag;
        } else if (swkey->eth.type == htons(ETH_P_IPV6)) {
                struct ovs_key_ipv6 *ipv6_key;
+               struct ovs_key_ipv6_exthdrs *ipv6_exthdrs_key;
 
                nla = nla_reserve(skb, OVS_KEY_ATTR_IPV6, sizeof(*ipv6_key));
                if (!nla)
@@ -2103,6 +2117,13 @@ static int __ovs_nla_put_key(const struct sw_flow_key 
*swkey,
                ipv6_key->ipv6_tclass = output->ip.tos;
                ipv6_key->ipv6_hlimit = output->ip.ttl;
                ipv6_key->ipv6_frag = output->ip.frag;
+
+               nla = nla_reserve(skb, OVS_KEY_ATTR_IPV6_EXTHDRS,
+                                                 sizeof(*ipv6_exthdrs_key));
+               if (!nla)
+                       goto nla_put_failure;
+               ipv6_exthdrs_key = nla_data(nla);
+               ipv6_exthdrs_key->hdrs = output->ipv6.exthdrs;
        } else if (swkey->eth.type == htons(ETH_P_NSH)) {
                if (nsh_key_to_nlattr(&output->nsh, is_mask, skb))
                        goto nla_put_failure;
diff --git a/datapath/linux/compat/include/linux/openvswitch.h 
b/datapath/linux/compat/include/linux/openvswitch.h
index 8d9300091..b9e74543b 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -387,6 +387,7 @@ enum ovs_key_attr {
        OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4,   /* struct ovs_key_ct_tuple_ipv4 */
        OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6,   /* struct ovs_key_ct_tuple_ipv6 */
        OVS_KEY_ATTR_NSH,       /* Nested set of ovs_nsh_key_* */
+       OVS_KEY_ATTR_IPV6_EXTHDRS,  /* struct ovs_key_ipv6_exthdr */
 
 #ifdef __KERNEL__
        /* Only used within kernel data path. */
@@ -498,6 +499,11 @@ struct ovs_key_ipv6 {
        __u8   ipv6_frag;       /* One of OVS_FRAG_TYPE_*. */
 };
 
+/* separate structure to support backward compatibility with older user space 
*/
+struct ovs_key_ipv6_exthdrs {
+       __u16  hdrs;
+};
+
 struct ovs_key_tcp {
        __be16 tcp_src;
        __be16 tcp_dst;
diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
index 3054015d9..faa0fa412 100644
--- a/include/openvswitch/flow.h
+++ b/include/openvswitch/flow.h
@@ -27,7 +27,7 @@ extern "C" {
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 42
+#define FLOW_WC_SEQ 43
 
 /* Number of Open vSwitch extension 32-bit registers. */
 #define FLOW_N_REGS 16
@@ -136,6 +136,8 @@ struct flow {
     struct in6_addr ipv6_dst;   /* IPv6 destination address. */
     struct in6_addr ct_ipv6_src; /* CT orig tuple IPv6 source address. */
     struct in6_addr ct_ipv6_dst; /* CT orig tuple IPv6 destination address. */
+    uint16_t ipv6_exthdr;       /* IPv6 flow extension headers. */
+    ovs_be16 pad4[3];           /* Pad to 64 bits. */
     ovs_be32 ipv6_label;        /* IPv6 flow label. */
     uint8_t nw_frag;            /* FLOW_FRAG_* flags. */
     uint8_t nw_tos;             /* IP ToS (including DSCP and ECN). */
@@ -167,8 +169,8 @@ BUILD_ASSERT_DECL(sizeof(struct ovs_key_nsh) % 
sizeof(uint64_t) == 0);
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
 BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
-                  == sizeof(struct flow_tnl) + sizeof(struct ovs_key_nsh) + 300
-                  && FLOW_WC_SEQ == 42);
+                  == sizeof(struct flow_tnl) + sizeof(struct ovs_key_nsh) + 308
+                  && FLOW_WC_SEQ == 43);
 
 /* Incremental points at which flow classification may be performed in
  * segments.
diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index 2e8812048..5ba129190 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -234,6 +234,8 @@ void match_set_ipv6_dst_masked(struct match *, const struct 
in6_addr *,
                                const struct in6_addr *);
 void match_set_ipv6_label(struct match *, ovs_be32);
 void match_set_ipv6_label_masked(struct match *, ovs_be32, ovs_be32);
+void match_set_ipv6_exthdr(struct match *, uint16_t);
+void match_set_ipv6_exthdr_masked(struct match *, uint16_t, uint16_t);
 void match_set_nd_target(struct match *, const struct in6_addr *);
 void match_set_nd_target_masked(struct match *, const struct in6_addr *,
                                 const struct in6_addr *);
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index 045dce8f5..6f4d63b42 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -1392,6 +1392,22 @@ enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_IPV6_LABEL,
 
+    /* "ipv6_ext".
+     *
+     * IPV6 extension headers flags.
+     *
+     * Currently there are 9 IPV6 extension headers flags.
+     *
+     * Type: u16.
+     * Maskable: bitwise.
+     * Formatting: IPV6 ext hdr.
+     * Prerequisites: IPv6.
+     * Access: read-only.
+     * NXM: NXM_NX_IPV6_EXTHDR(126) since v2.11.
+     * OXM: OXM_OF_IPV6_EXTHDR(39) since OF1.2 and v2.11.
+     */
+    MFF_IPV6_EXTHDR,
+
 /* ## ----------------------- ## */
 /* ## IPv4/IPv6 common fields ## */
 /* ## ----------------------- ## */
@@ -2112,6 +2128,7 @@ enum OVS_PACKED_ENUM mf_string {
     MFS_TNL_FLAGS,              /* FLOW_TNL_F_* flags */
     MFS_TCP_FLAGS,              /* TCP_* flags */
     MFS_PACKET_TYPE,            /* "(NS,NS_TYPE)" */
+    MFS_IPV6_EXTHDR,
 };
 
 struct mf_field {
@@ -2169,6 +2186,7 @@ union mf_value {
     ovs_be64 be64;
     ovs_be32 be32;
     ovs_be16 be16;
+    uint16_t u16;
     uint8_t u8;
 };
 BUILD_ASSERT_DECL(sizeof(union mf_value) == 128);
diff --git a/lib/dpif-netdev-extract-avx512.c b/lib/dpif-netdev-extract-avx512.c
index c1c1fefb6..3cbe04ca9 100644
--- a/lib/dpif-netdev-extract-avx512.c
+++ b/lib/dpif-netdev-extract-avx512.c
@@ -289,7 +289,7 @@ BUILD_ASSERT_DECL((OFFSETOFEND(struct dp_packet, l4_ofs)
 BUILD_ASSERT_DECL(FLOWMAP_UNITS == 2);
 
 /* Ensure the miniflow-struct ABI is the expected version. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
 /* If the above build assert happens, this means that you might need to make
  * some modifications to the AVX512 miniflow extractor code. In general, the
diff --git a/lib/flow.c b/lib/flow.c
index dd523c889..6a8f5d14c 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -30,6 +30,7 @@
 #include "colors.h"
 #include "coverage.h"
 #include "csum.h"
+#include "openflow/openflow.h"
 #include "openvswitch/dynamic-string.h"
 #include "hash.h"
 #include "jhash.h"
@@ -136,7 +137,7 @@ struct mf_ctx {
  * away.  Some GCC versions gave warnings on ALWAYS_INLINE, so these are
  * defined as macros. */
 
-#if (FLOW_WC_SEQ != 42)
+#if (FLOW_WC_SEQ != 43)
 #define MINIFLOW_ASSERT(X) ovs_assert(X)
 BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
                "assertions enabled. Consider updating FLOW_WC_SEQ after "
@@ -572,6 +573,163 @@ parse_ipv6_ext_hdrs(const void **datap, size_t *sizep, 
uint8_t *nw_proto,
                                  frag_hdr);
 }
 
+uint16_t
+ipv6_ext_header_size__(uint8_t header_type, uint8_t len)
+{
+    /*
+     * Authentication header has different size calculation
+     */
+    if (header_type == IPPROTO_AH) {
+        return (len + 2) << 2;
+    } else {
+        return (len + 1) << 3;
+    }
+}
+
+/**
+ * Parses packet and sets IPv6 extension header flags.
+ *
+ * datap        pointer where extension header data starts in packet
+ * nh           ipv6 header
+ * ext_hdrs     flags are stored here
+ *
+ * OFPIEH12_UNREP is set to 1 if more than one of a given IPv6 extension header
+ * is unexpectedly encountered. (Two destination options headers may be
+ * expected and would not cause this bit to be set.)
+ *
+ * OFPIEH12_UNSEQ is set to 1 if IPv6 extension headers were not in the order
+ * preferred (but not required) by RFC 2460:
+ *
+ * When more than one extension header is used in the same packet, it is
+ * recommended that those headers appear in the following order:
+ *      IPv6 header
+ *      Hop-by-Hop Options header
+ *      Destination Options header
+ *      Routing header
+ *      Fragment header
+ *      Authentication header
+ *      Encapsulating Security Payload header
+ *      Destination Options header
+ *      upper-layer header
+ */
+void
+get_ipv6_ext_hdrs(const void *datap, const struct ovs_16aligned_ip6_hdr *nh,
+        uint16_t *ext_hdrs)
+{
+    uint8_t next_type = nh->ip6_nxt;
+    size_t size = ntohs(nh->ip6_plen);
+    int dest_options_header_count = 0;
+
+    *ext_hdrs = 0;
+
+    while (true) {
+        /*
+         * following switch code is identical to kernel datapath/flow.c
+         * get_ipv6_ext_hdrs() switch code
+         */
+        switch (next_type) {
+            case IPPROTO_NONE:
+                *ext_hdrs |= OFPIEH12_NONEXT;
+                /* stop parsing */
+                return;
+
+            case IPPROTO_ESP:
+                if (*ext_hdrs & OFPIEH12_ESP) {
+                    *ext_hdrs |= OFPIEH12_UNREP;
+                }
+                if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST
+                                   | OFPIEH12_ROUTER | IPPROTO_FRAGMENT
+                                   | OFPIEH12_AUTH | OFPIEH12_UNREP))
+                    || dest_options_header_count >= 2) {
+                    *ext_hdrs |= OFPIEH12_UNSEQ;
+                }
+                *ext_hdrs |= OFPIEH12_ESP;
+                break;
+
+            case IPPROTO_AH:
+                if (*ext_hdrs & OFPIEH12_AUTH) {
+                    *ext_hdrs |= OFPIEH12_UNREP;
+                }
+                if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST
+                                   | OFPIEH12_ROUTER | IPPROTO_FRAGMENT
+                                   | OFPIEH12_UNREP))
+                    || dest_options_header_count >= 2) {
+                    *ext_hdrs |= OFPIEH12_UNSEQ;
+                }
+                *ext_hdrs |= OFPIEH12_AUTH;
+                break;
+
+            case IPPROTO_DSTOPTS:
+                if (dest_options_header_count == 0) {
+                    if (*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_UNREP)) {
+                        *ext_hdrs |= OFPIEH12_UNSEQ;
+                    }
+                    *ext_hdrs |= OFPIEH12_DEST;
+                } else if (dest_options_header_count == 1) {
+                    if (*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST
+                                      | OFPIEH12_ROUTER | OFPIEH12_FRAG
+                                      | OFPIEH12_AUTH | OFPIEH12_ESP
+                                      | OFPIEH12_UNREP)) {
+                        *ext_hdrs |= OFPIEH12_UNSEQ;
+                    }
+                } else {
+                    *ext_hdrs |= OFPIEH12_UNREP;
+                }
+                dest_options_header_count ++;
+                break;
+
+            case IPPROTO_FRAGMENT:
+                if (*ext_hdrs & OFPIEH12_FRAG) {
+                    *ext_hdrs |= OFPIEH12_UNREP;
+                }
+                if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST
+                                   | OFPIEH12_ROUTER | OFPIEH12_UNREP))
+                    || dest_options_header_count >= 2) {
+                    *ext_hdrs |= OFPIEH12_UNSEQ;
+                }
+                *ext_hdrs |= OFPIEH12_FRAG;
+                break;
+
+            case IPPROTO_ROUTING:
+                if (*ext_hdrs & OFPIEH12_ROUTER) {
+                    *ext_hdrs |= OFPIEH12_UNREP;
+                }
+                if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST
+                                   | OFPIEH12_UNREP))
+                    || dest_options_header_count >= 2) {
+                    *ext_hdrs |= OFPIEH12_UNSEQ;
+                }
+                *ext_hdrs |= OFPIEH12_ROUTER;
+                break;
+
+            case IPPROTO_HOPOPTS:
+                if (*ext_hdrs & OFPIEH12_HOP) {
+                    *ext_hdrs |= OFPIEH12_UNREP;
+                }
+                /*
+                 * OFPIEH12_HOP is set to 1 if a hop-by-hop IPv6 extension
+                 * header is present as the first extension header in the
+                 * packet.
+                 */
+                if (*ext_hdrs == 0) {
+                    *ext_hdrs |= OFPIEH12_HOP;
+                } else {
+                    *ext_hdrs |= OFPIEH12_UNSEQ;
+                }
+                break;
+
+            default:
+                return;
+        }
+
+        const struct ip6_ext *ext_hdr = datap;
+        data_try_pull(&datap, &size,
+                      ipv6_ext_header_size__(next_type, ext_hdr->ip6e_len));
+
+        next_type = ext_hdr->ip6e_nxt;
+    }
+}
+
 bool
 parse_nsh(const void **datap, size_t *sizep, struct ovs_key_nsh *key)
 {
@@ -761,7 +919,7 @@ void
 miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
 {
     /* Add code to this function (or its callees) to extract new fields. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     const struct pkt_metadata *md = &packet->md;
     const void *data = dp_packet_data(packet);
@@ -945,12 +1103,20 @@ miniflow_extract(struct dp_packet *packet, struct 
miniflow *dst)
         nw_ttl = nh->ip6_hlim;
         nw_proto = nh->ip6_nxt;
 
+        uint16_t ext_hdrs;
+
+        get_ipv6_ext_hdrs(data, nh, &ext_hdrs);
+
         const struct ovs_16aligned_ip6_frag *frag_hdr;
         if (!parse_ipv6_ext_hdrs__(&data, &size, &nw_proto, &nw_frag,
                                    &frag_hdr)) {
             goto out;
         }
 
+        miniflow_pad_from_64(mf, ipv6_exthdr);
+        miniflow_push_uint16(mf, ipv6_exthdr, ext_hdrs);
+        miniflow_pad_to_64(mf, ipv6_exthdr);
+
         /* This needs to be after the parse_ipv6_ext_hdrs__() call because it
          * leaves the nw_frag word uninitialized. */
         ASSERT_SEQUENTIAL(ipv6_label, nw_frag);
@@ -1247,7 +1413,7 @@ flow_get_metadata(const struct flow *flow, struct match 
*flow_metadata)
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     match_init_catchall(flow_metadata);
     if (flow->tunnel.tun_id != htonll(0)) {
@@ -1833,7 +1999,7 @@ flow_wildcards_init_for_packet(struct flow_wildcards *wc,
     memset(&wc->masks, 0x0, sizeof wc->masks);
 
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     if (flow_tnl_dst_is_set(&flow->tunnel)) {
         if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
@@ -1913,6 +2079,7 @@ flow_wildcards_init_for_packet(struct flow_wildcards *wc,
     } else if (dl_type == htons(ETH_TYPE_IPV6)) {
         WC_MASK_FIELD(wc, ipv6_src);
         WC_MASK_FIELD(wc, ipv6_dst);
+        WC_MASK_FIELD(wc, ipv6_exthdr);
         WC_MASK_FIELD(wc, ipv6_label);
         if (is_nd(flow, wc)) {
             WC_MASK_FIELD(wc, arp_sha);
@@ -1986,7 +2153,7 @@ void
 flow_wc_map(const struct flow *flow, struct flowmap *map)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     flowmap_init(map);
 
@@ -2043,6 +2210,7 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)
     } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
         FLOWMAP_SET(map, ipv6_src);
         FLOWMAP_SET(map, ipv6_dst);
+        FLOWMAP_SET(map, ipv6_exthdr);
         FLOWMAP_SET(map, ipv6_label);
         FLOWMAP_SET(map, nw_proto);
         FLOWMAP_SET(map, nw_frag);
@@ -2089,7 +2257,7 @@ void
 flow_wildcards_clear_non_packet_fields(struct flow_wildcards *wc)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     memset(&wc->masks.metadata, 0, sizeof wc->masks.metadata);
     memset(&wc->masks.regs, 0, sizeof wc->masks.regs);
@@ -2233,7 +2401,7 @@ flow_wildcards_set_xxreg_mask(struct flow_wildcards *wc, 
int idx,
 uint32_t
 miniflow_hash_5tuple(const struct miniflow *flow, uint32_t basis)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
     uint32_t hash = basis;
 
     if (flow) {
@@ -2280,7 +2448,7 @@ ASSERT_SEQUENTIAL(ipv6_src, ipv6_dst);
 uint32_t
 flow_hash_5tuple(const struct flow *flow, uint32_t basis)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
     uint32_t hash = basis;
 
     if (flow) {
@@ -2958,7 +3126,7 @@ flow_push_mpls(struct flow *flow, int n, ovs_be16 
mpls_eth_type,
 
         if (clear_flow_L3) {
             /* Clear all L3 and L4 fields and dp_hash. */
-            BUILD_ASSERT(FLOW_WC_SEQ == 42);
+            BUILD_ASSERT(FLOW_WC_SEQ == 43);
             memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
                    sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
             flow->dp_hash = 0;
@@ -3256,7 +3424,7 @@ flow_compose(struct dp_packet *p, const struct flow *flow,
     /* Add code to this function (or its callees) for emitting new fields or
      * protocols.  (This isn't essential, so it can be skipped for initial
      * testing.) */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     uint32_t pseudo_hdr_csum;
     size_t l4_len;
diff --git a/lib/flow.h b/lib/flow.h
index c647ad83c..1251f2757 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -133,6 +133,10 @@ void packet_expand(struct dp_packet *, const struct flow 
*, size_t size);
 bool parse_ipv6_ext_hdrs(const void **datap, size_t *sizep, uint8_t *nw_proto,
                          uint8_t *nw_frag,
                          const struct ovs_16aligned_ip6_frag **frag_hdr);
+uint16_t ipv6_ext_header_size__(uint8_t header_type, uint8_t len);
+void get_ipv6_ext_hdrs(const void *datap,
+                       const struct ovs_16aligned_ip6_hdr *nh,
+                       uint16_t *ext_hdrs);
 bool parse_nsh(const void **datap, size_t *sizep, struct ovs_key_nsh *key);
 uint16_t parse_tcp_flags(struct dp_packet *packet, ovs_be16 *dl_type_p,
                          uint8_t *nw_frag_p, ovs_be16 *first_vlan_tci_p);
@@ -965,7 +969,7 @@ static inline void
 pkt_metadata_from_flow(struct pkt_metadata *md, const struct flow *flow)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     md->recirc_id = flow->recirc_id;
     md->dp_hash = flow->dp_hash;
diff --git a/lib/match.c b/lib/match.c
index 2ad03e044..9dc7025c8 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1121,7 +1121,6 @@ match_set_ipv6_label(struct match *match, ovs_be32 
ipv6_label)
     match->flow.ipv6_label = ipv6_label;
 }
 
-
 void
 match_set_ipv6_label_masked(struct match *match, ovs_be32 ipv6_label,
                             ovs_be32 mask)
@@ -1130,6 +1129,21 @@ match_set_ipv6_label_masked(struct match *match, 
ovs_be32 ipv6_label,
     match->wc.masks.ipv6_label = mask;
 }
 
+void
+match_set_ipv6_exthdr(struct match *match, uint16_t ipv6_exthdr)
+{
+    match->flow.ipv6_exthdr = ipv6_exthdr;
+    match->wc.masks.ipv6_exthdr = UINT16_MAX;
+}
+
+void
+match_set_ipv6_exthdr_masked(struct match *match, uint16_t ipv6_exthdr,
+                             uint16_t mask)
+{
+    match->flow.ipv6_exthdr = ipv6_exthdr & mask;
+    match->wc.masks.ipv6_exthdr = mask;
+}
+
 void
 match_set_nd_target(struct match *match, const struct in6_addr *target)
 {
@@ -1462,7 +1476,7 @@ match_format(const struct match *match,
     bool is_megaflow = false;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "%spriority=%s%d,",
@@ -1681,6 +1695,13 @@ match_format(const struct match *match,
                               ntohl(wc->masks.ipv6_label));
             }
         }
+        if (f->ipv6_exthdr && wc->masks.ipv6_exthdr) {
+            format_flags_masked(s, "ipv6_ext",
+                                packet_ipv6_exthdr_flag_to_string,
+                                f->ipv6_exthdr, wc->masks.ipv6_exthdr,
+                                UINT16_MAX);
+            ds_put_char(s, ',');
+        }
     } else if (dl_type == htons(ETH_TYPE_ARP) ||
                dl_type == htons(ETH_TYPE_RARP)) {
         format_ip_netmask(s, "arp_spa", f->nw_src, wc->masks.nw_src);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index e03cd8d0c..9935f30bb 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -331,6 +331,9 @@ mf_is_all_wild(const struct mf_field *mf, const struct 
flow_wildcards *wc)
     case MFF_IPV6_LABEL:
         return !wc->masks.ipv6_label;
 
+    case MFF_IPV6_EXTHDR:
+        return !wc->masks.ipv6_exthdr;
+
     case MFF_IP_PROTO:
         return !wc->masks.nw_proto;
     case MFF_IP_DSCP:
@@ -563,6 +566,7 @@ mf_is_value_valid(const struct mf_field *mf, const union 
mf_value *value)
     case MFF_IPV4_DST:
     case MFF_IPV6_SRC:
     case MFF_IPV6_DST:
+    case MFF_IPV6_EXTHDR:
     case MFF_IP_PROTO:
     case MFF_IP_TTL:
     case MFF_ARP_SPA:
@@ -869,6 +873,10 @@ mf_get_value(const struct mf_field *mf, const struct flow 
*flow,
         value->be32 = flow->ipv6_label;
         break;
 
+    case MFF_IPV6_EXTHDR:
+        value->u16 = flow->ipv6_exthdr;
+        break;
+
     case MFF_IP_PROTO:
         value->u8 = flow->nw_proto;
         break;
@@ -989,7 +997,7 @@ mf_get_value(const struct mf_field *mf, const struct flow 
*flow,
  * prerequisites.
  *
  * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
- * with the request or NULL if there is no error. The caller is reponsible
+ * with the request or NULL if there is no error. The caller is responsible
  * for freeing the string. */
 void
 mf_set_value(const struct mf_field *mf,
@@ -1213,6 +1221,10 @@ mf_set_value(const struct mf_field *mf,
         match_set_ipv6_label(match, value->be32);
         break;
 
+    case MFF_IPV6_EXTHDR:
+        match_set_ipv6_exthdr(match, value->u16);
+        break;
+
     case MFF_IP_PROTO:
         match_set_nw_proto(match, value->u8);
         break;
@@ -1633,6 +1645,10 @@ mf_set_flow_value(const struct mf_field *mf,
         flow->ipv6_label = value->be32 & htonl(IPV6_LABEL_MASK);
         break;
 
+    case MFF_IPV6_EXTHDR:
+        flow->ipv6_exthdr = value->u16;
+        break;
+
     case MFF_IP_PROTO:
         flow->nw_proto = value->u8;
         break;
@@ -1865,6 +1881,7 @@ mf_is_pipeline_field(const struct mf_field *mf)
     case MFF_IPV6_SRC:
     case MFF_IPV6_DST:
     case MFF_IPV6_LABEL:
+    case MFF_IPV6_EXTHDR:
     case MFF_IP_PROTO:
     case MFF_IP_DSCP:
     case MFF_IP_DSCP_SHIFTED:
@@ -1935,7 +1952,7 @@ mf_is_set(const struct mf_field *mf, const struct flow 
*flow)
  * prerequisites.
  *
  * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
- * with the request or NULL if there is no error. The caller is reponsible
+ * with the request or NULL if there is no error. The caller is responsible
  * for freeing the string. */
 void
 mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
@@ -2182,6 +2199,11 @@ mf_set_wild(const struct mf_field *mf, struct match 
*match, char **err_str)
         match->flow.ipv6_label = htonl(0);
         break;
 
+    case MFF_IPV6_EXTHDR:
+        match->wc.masks.ipv6_exthdr = 0;
+        match->flow.ipv6_exthdr = 0;
+        break;
+
     case MFF_IP_PROTO:
         match->wc.masks.nw_proto = 0;
         match->flow.nw_proto = 0;
@@ -2307,7 +2329,7 @@ mf_set_wild(const struct mf_field *mf, struct match 
*match, char **err_str)
  * is responsible for ensuring that 'match' meets 'mf''s prerequisites.
  *
  * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
- * with the request or NULL if there is no error. The caller is reponsible
+ * with the request or NULL if there is no error. The caller is responsible
  * for freeing the string.
  *
  * Return a set of enum ofputil_protocol bits (as an uint32_t to avoid circular
@@ -2538,6 +2560,10 @@ mf_set(const struct mf_field *mf,
         }
         break;
 
+    case MFF_IPV6_EXTHDR:
+        match_set_ipv6_exthdr_masked(match, value->u16, mask->u16);
+        break;
+
     case MFF_ND_TARGET:
         match_set_nd_target_masked(match, &value->ipv6, &mask->ipv6);
         break;
@@ -2960,6 +2986,29 @@ parse_mf_flags(const char *s, const char 
*(*bit_to_string)(uint32_t),
     return NULL;
 }
 
+static char *
+parse_mf_flags_le(const char *s, const char *(*bit_to_string)(uint32_t),
+               const char *field_name, uint16_t *flagsp, uint16_t allowed,
+               uint16_t *maskp)
+{
+    int err;
+    char *err_str;
+    uint32_t flags, mask;
+
+    err = parse_flags(s, bit_to_string, '\0', field_name, &err_str,
+                      &flags, allowed, maskp ? &mask : NULL);
+    if (err < 0) {
+        return err_str;
+    }
+
+    *flagsp = flags;
+    if (maskp) {
+        *maskp = mask;
+    }
+
+    return NULL;
+}
+
 static char *
 mf_from_tcp_flags_string(const char *s, ovs_be16 *flagsp, ovs_be16 *maskp)
 {
@@ -2974,6 +3023,14 @@ mf_from_tun_flags_string(const char *s, ovs_be16 
*flagsp, ovs_be16 *maskp)
                           htons(FLOW_TNL_PUB_F_MASK), maskp);
 }
 
+static char *
+mf_from_ipv6_exthdr_flags_string(const char *s, uint16_t *flagsp,
+                                 uint16_t *maskp)
+{
+    return parse_mf_flags_le(s, packet_ipv6_exthdr_flag_to_string, "IPV6_EXT",
+                             flagsp, UINT16_MAX, maskp);
+}
+
 static char *
 mf_from_ct_state_string(const char *s, ovs_be32 *flagsp, ovs_be32 *maskp)
 {
@@ -3064,6 +3121,11 @@ mf_parse(const struct mf_field *mf, const char *s,
         mask->be32 = OVS_BE32_MAX;
         break;
 
+    case MFS_IPV6_EXTHDR:
+        ovs_assert(mf->n_bytes == sizeof(uint16_t));
+        error = mf_from_ipv6_exthdr_flags_string(s, &value->u16, &mask->u16);
+        break;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -3164,6 +3226,13 @@ mf_format_packet_type_string(ovs_be32 value, ovs_be32 
mask, struct ds *s)
     format_packet_type_masked(s, value, mask);
 }
 
+static void
+mf_format_ipv6_exthdr_flags_string(uint16_t value, uint16_t mask, struct ds *s)
+{
+    format_flags_masked(s, NULL, packet_ipv6_exthdr_flag_to_string, value,
+                        mask, UINT16_MAX);
+}
+
 /* Appends to 's' a string representation of field 'mf' whose value is in
  * 'value' and 'mask'.  'mask' may be NULL to indicate an exact match. */
 void
@@ -3237,6 +3306,11 @@ mf_format(const struct mf_field *mf,
                                      mask ? mask->be32 : OVS_BE32_MAX, s);
         break;
 
+    case MFS_IPV6_EXTHDR:
+        mf_format_ipv6_exthdr_flags_string(value->u16,
+                                   mask ? mask->u16 : UINT16_MAX, s);
+        break;
+
     default:
         OVS_NOT_REACHED();
     }
diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
index 28865f88c..ddccaafb5 100644
--- a/lib/meta-flow.xml
+++ b/lib/meta-flow.xml
@@ -4078,6 +4078,20 @@ r r c c c.
         </header>
       </diagram>
     </field>
+    <field id="MFF_IPV6_EXTHDR" title="IPv6 Extension Headers">
+      <p>
+        The least significant 9 bits hold the presence of extension headers
+        in packet header, duplication and order correctness. Other bits are
+        zero:
+      </p>
+
+      <diagram>
+        <header name="MFF_IPV6_EXTHDR">
+          <bits name="zero" above="7" below="0" width=".8"/>
+          <bits name="label" above="9" width="1.0"/>
+        </header>
+      </diagram>
+    </field>
 
     <h2>IPv4/IPv6 Fields</h2>
 
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 440f5f763..a0640e42c 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -862,6 +862,14 @@ nxm_put_16(struct nxm_put_ctx *ctx,
     nxm_put__(ctx, field, version, &value, NULL, sizeof value);
 }
 
+static void
+nxm_put_16m_le(struct nxm_put_ctx *ctx,
+            enum mf_field_id field, enum ofp_version version,
+            uint16_t value, uint16_t mask)
+{
+    nxm_put(ctx, field, version, &value, &mask, sizeof value);
+}
+
 static void
 nxm_put_32m(struct nxm_put_ctx *ctx,
             enum mf_field_id field, enum ofp_version version,
@@ -966,6 +974,9 @@ nxm_put_ip(struct nxm_put_ctx *ctx,
     nxm_put_32m(ctx, MFF_IPV6_LABEL, oxm,
                 flow->ipv6_label, match->wc.masks.ipv6_label);
 
+    nxm_put_16m_le(ctx, MFF_IPV6_EXTHDR, oxm,
+                   flow->ipv6_exthdr, match->wc.masks.ipv6_exthdr);
+
     if (match->wc.masks.nw_proto) {
         nxm_put_8(ctx, MFF_IP_PROTO, oxm, flow->nw_proto);
 
@@ -1051,7 +1062,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const 
struct match *match,
     ovs_be32 spi_mask;
     int match_len;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     struct nxm_put_ctx ctx = { .output = b, .implied_ethernet = false };
 
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 2f4cdd92c..a54b08658 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -553,6 +553,7 @@ odp_execute_set_action(struct dp_packet *packet, const 
struct nlattr *a)
     case OVS_KEY_ATTR_CT_ZONE:
     case OVS_KEY_ATTR_CT_MARK:
     case OVS_KEY_ATTR_CT_LABELS:
+    case OVS_KEY_ATTR_IPV6_EXTHDRS:
     case __OVS_KEY_ATTR_MAX:
     default:
         OVS_NOT_REACHED();
@@ -665,6 +666,7 @@ odp_execute_masked_set_action(struct dp_packet *packet,
     case OVS_KEY_ATTR_ICMP:
     case OVS_KEY_ATTR_ICMPV6:
     case OVS_KEY_ATTR_TCP_FLAGS:
+    case OVS_KEY_ATTR_IPV6_EXTHDRS:
     case __OVS_KEY_ATTR_MAX:
     default:
         OVS_NOT_REACHED();
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 9a705cffa..e3eaa918a 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -178,6 +178,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char 
*namebuf, size_t bufsize)
     case OVS_KEY_ATTR_ETHERTYPE: return "eth_type";
     case OVS_KEY_ATTR_IPV4: return "ipv4";
     case OVS_KEY_ATTR_IPV6: return "ipv6";
+    case OVS_KEY_ATTR_IPV6_EXTHDRS: return "ipv6_ext";
     case OVS_KEY_ATTR_TCP: return "tcp";
     case OVS_KEY_ATTR_TCP_FLAGS: return "tcp_flags";
     case OVS_KEY_ATTR_UDP: return "udp";
@@ -2746,6 +2747,7 @@ const struct attr_len_tbl 
ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
     [OVS_KEY_ATTR_NSH]       = { .len = ATTR_LEN_NESTED,
                                  .next = ovs_nsh_key_attr_lens,
                                  .next_max = OVS_NSH_KEY_ATTR_MAX },
+    [OVS_KEY_ATTR_IPV6_EXTHDRS] = { .len = sizeof(struct ovs_key_ipv6_exthdrs) 
},
 };
 
 /* Returns the correct length of the payload for a flow key attribute of the
@@ -3255,6 +3257,7 @@ odp_mask_is_constant__(enum ovs_key_attr attr, const void 
*mask, size_t size,
      * that -1 becomes all-1-bits and 0 does not change. */
     ovs_be16 be16 = (OVS_FORCE ovs_be16) constant;
     uint32_t u32 = constant;
+    uint16_t u16 = constant;
     uint8_t u8 = constant;
     const struct in6_addr *in6 = constant ? &in6addr_exact : &in6addr_any;
 
@@ -3306,6 +3309,9 @@ odp_mask_is_constant__(enum ovs_key_attr attr, const void 
*mask, size_t size,
                 && ipv6_addr_equals(&ipv6_mask->ipv6_dst, in6));
     }
 
+    case OVS_KEY_ATTR_IPV6_EXTHDRS:
+        return is_all_byte(mask, size, u16);
+
     case OVS_KEY_ATTR_ARP:
         return is_all_byte(mask, OFFSETOFEND(struct ovs_key_arp, arp_tha), u8);
 
@@ -3535,6 +3541,23 @@ format_u8u(struct ds *ds, const char *name, uint8_t key,
     }
 }
 
+static void
+format_u16u(struct ds *ds, const char *name, uint16_t key,
+            const uint16_t *mask, bool verbose)
+{
+    bool mask_empty = mask && !*mask;
+
+    if (verbose || !mask_empty) {
+        bool mask_full = !mask || *mask == UINT16_MAX;
+
+        ds_put_format(ds, "%s=%"PRIu16, name, key);
+        if (!mask_full) { /* Partially masked. */
+            ds_put_format(ds, "/%#"PRIx16, *mask);
+        }
+        ds_put_char(ds, ',');
+    }
+}
+
 static void
 format_be16(struct ds *ds, const char *name, ovs_be16 key,
             const ovs_be16 *mask, bool verbose)
@@ -4323,6 +4346,14 @@ format_odp_key_attr__(const struct nlattr *a, const 
struct nlattr *ma,
                     verbose);
         ds_chomp(ds, ',');
         break;
+    }
+    case OVS_KEY_ATTR_IPV6_EXTHDRS: {
+        const struct ovs_key_ipv6_exthdrs *key = nl_attr_get(a);
+        const struct ovs_key_ipv6_exthdrs *mask = ma ? nl_attr_get(ma) : NULL;
+
+        format_u16u(ds, "hdrs", key->hdrs, MASK(mask, hdrs), verbose);
+        ds_chomp(ds, ',');
+        break;
     }
         /* These have the same structure and format. */
     case OVS_KEY_ATTR_TCP:
@@ -5949,6 +5980,10 @@ parse_odp_key_mask_attr__(struct parse_odp_context 
*context, const char *s,
         SCAN_FIELD("frag=", frag, ipv6_frag);
     } SCAN_END(OVS_KEY_ATTR_IPV6);
 
+    SCAN_BEGIN("ipv6_ext(", struct ovs_key_ipv6_exthdrs) {
+        SCAN_FIELD("hdrs=", u16, hdrs);
+    } SCAN_END(OVS_KEY_ATTR_IPV6_EXTHDRS);
+
     SCAN_BEGIN("tcp(", struct ovs_key_tcp) {
         SCAN_FIELD("src=", be16, tcp_src);
         SCAN_FIELD("dst=", be16, tcp_dst);
@@ -6154,6 +6189,10 @@ static void get_ipv6_key(const struct flow *, struct 
ovs_key_ipv6 *,
                          bool is_mask);
 static void put_ipv6_key(const struct ovs_key_ipv6 *, struct flow *,
                          bool is_mask);
+static void get_ipv6_exthdrs_key(const struct flow *,
+                                 struct ovs_key_ipv6_exthdrs *);
+static void put_ipv6_exthdrs_key(const struct ovs_key_ipv6_exthdrs *,
+                                 struct flow *);
 static void get_arp_key(const struct flow *, struct ovs_key_arp *);
 static void put_arp_key(const struct ovs_key_arp *, struct flow *);
 static void get_nd_key(const struct flow *, struct ovs_key_nd *);
@@ -6180,7 +6219,7 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms 
*parms,
     /* New "struct flow" fields that are visible to the datapath (including all
      * data fields) should be translated into equivalent datapath flow fields
      * here (you will have to add a OVS_KEY_ATTR_* for them). */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     struct ovs_key_ethernet *eth_key;
     size_t encap[FLOW_MAX_VLAN_HEADERS] = {0};
@@ -6326,6 +6365,16 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms 
*parms,
         ipv6_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_IPV6,
                                             sizeof *ipv6_key);
         get_ipv6_key(data, ipv6_key, export_mask);
+
+        if (parms->support.ipv6_exthdrs && data->ipv6_exthdr) {
+            struct ovs_key_ipv6_exthdrs *ipv6_exthdrs_key;
+
+            ipv6_exthdrs_key =
+                    nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_IPV6_EXTHDRS,
+                                             sizeof *ipv6_exthdrs_key);
+
+            get_ipv6_exthdrs_key(data, ipv6_exthdrs_key);
+        }
     } else if (flow->dl_type == htons(ETH_TYPE_ARP) ||
                flow->dl_type == htons(ETH_TYPE_RARP)) {
         struct ovs_key_arp *arp_key;
@@ -6599,6 +6648,7 @@ odp_key_to_dp_packet(const struct nlattr *key, size_t 
key_len,
         case OVS_KEY_ATTR_VLAN:
         case OVS_KEY_ATTR_IPV4:
         case OVS_KEY_ATTR_IPV6:
+        case OVS_KEY_ATTR_IPV6_EXTHDRS:
         case OVS_KEY_ATTR_TCP:
         case OVS_KEY_ATTR_UDP:
         case OVS_KEY_ATTR_ICMP:
@@ -6970,6 +7020,20 @@ parse_l2_5_onward(const struct nlattr 
*attrs[OVS_KEY_ATTR_MAX + 1],
                 expected_bit = OVS_KEY_ATTR_IPV6;
             }
         }
+        if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV6_EXTHDRS)) {
+            const struct ovs_key_ipv6_exthdrs *ipv6_exthdrs_key;
+
+            *expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IPV6_EXTHDRS;
+
+            ipv6_exthdrs_key = nl_attr_get(attrs[OVS_KEY_ATTR_IPV6_EXTHDRS]);
+            put_ipv6_exthdrs_key(ipv6_exthdrs_key, flow);
+
+            if (is_mask) {
+                check_start = ipv6_exthdrs_key;
+                check_len = sizeof *ipv6_exthdrs_key;
+                expected_bit = OVS_KEY_ATTR_IPV6_EXTHDRS;
+            }
+        }
     } else if (src_flow->dl_type == htons(ETH_TYPE_ARP) ||
                src_flow->dl_type == htons(ETH_TYPE_RARP)) {
         if (!is_mask) {
@@ -7286,7 +7350,7 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t 
key_len,
     /* New "struct flow" fields that are visible to the datapath (including all
      * data fields) should be translated from equivalent datapath flow fields
      * here (you will have to add a OVS_KEY_ATTR_* for them).  */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     enum odp_key_fitness fitness = ODP_FIT_ERROR;
     if (errorp) {
@@ -8107,6 +8171,44 @@ commit_set_ipv6_action(const struct flow *flow, struct 
flow *base_flow,
     }
 }
 
+static void
+get_ipv6_exthdrs_key(const struct flow *flow,
+                     struct ovs_key_ipv6_exthdrs *ipv6_exthdrs)
+{
+    ipv6_exthdrs->hdrs = flow->ipv6_exthdr;
+}
+
+static void
+put_ipv6_exthdrs_key(const struct ovs_key_ipv6_exthdrs *ipv6_exthdrs,
+                     struct flow *flow)
+{
+    flow->ipv6_exthdr = ipv6_exthdrs->hdrs;
+}
+
+static void
+commit_set_ipv6_exthdrs_action(const struct flow *flow, struct flow *base_flow,
+                               struct ofpbuf *odp_actions, struct 
flow_wildcards *wc,
+                               bool use_masked)
+{
+    struct ovs_key_ipv6_exthdrs key, mask, orig_mask, base;
+    struct offsetof_sizeof ovs_key_ipv6_exthdrs_offsetof_sizeof_arr[] =
+            OVS_KEY_IPV6_EXTHDRS_OFFSETOF_SIZEOF_ARR;
+
+    get_ipv6_exthdrs_key(flow, &key);
+    get_ipv6_exthdrs_key(base_flow, &base);
+    get_ipv6_exthdrs_key(&wc->masks, &mask);
+    memcpy(&orig_mask, &mask, sizeof mask);
+    mask.hdrs = 0;
+
+    if (commit(OVS_KEY_ATTR_IPV6_EXTHDRS, use_masked, &key, &base, &mask,
+               sizeof key, ovs_key_ipv6_exthdrs_offsetof_sizeof_arr,
+               odp_actions)) {
+        put_ipv6_exthdrs_key(&base, base_flow);
+        or_masks(&mask, &orig_mask, ovs_key_ipv6_exthdrs_offsetof_sizeof_arr);
+        put_ipv6_exthdrs_key(&mask, &wc->masks);
+    }
+}
+
 static void
 get_arp_key(const struct flow *flow, struct ovs_key_arp *arp)
 {
@@ -8308,6 +8410,8 @@ commit_set_nw_action(const struct flow *flow, struct flow 
*base,
 
     case ETH_TYPE_IPV6:
         commit_set_ipv6_action(flow, base, odp_actions, wc, use_masked);
+        commit_set_ipv6_exthdrs_action(flow, base, odp_actions, wc,
+                                       use_masked);
         if (base->nw_proto == IPPROTO_ICMPV6) {
             /* Commit extended attrs first to make sure
                correct options are added.*/
@@ -8706,7 +8810,7 @@ commit_odp_actions(const struct flow *flow, struct flow 
*base,
     /* If you add a field that OpenFlow actions can change, and that is visible
      * to the datapath (including all data fields), then you should also add
      * code here to commit changes to the field. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     enum slow_path_reason slow1, slow2;
     bool mpls_done = false;
diff --git a/lib/odp-util.h b/lib/odp-util.h
index a1d0d0fba..9a8301228 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -138,16 +138,17 @@ void odp_portno_name_format(const struct hmap 
*portno_names,
  *  OVS_KEY_ATTR_ENCAP                   0    --     4      4  (VLAN 
encapsulation)
  *  OVS_KEY_ATTR_ETHERTYPE               2     2     4      8  (inner VLAN 
ethertype)
  *  OVS_KEY_ATTR_IPV6                   40    --     4     44
+ *  OVS_KEY_ATTR_IPV6_EXTHDRS            2     2     4      8
  *  OVS_KEY_ATTR_ICMPV6                  2     2     4      8
  *  OVS_KEY_ATTR_ND                     28    --     4     32
  *  ----------------------------------------------------------
- *  total                                                 616
+ *  total                                                 624
  *
  * We include some slack space in case the calculation isn't quite right or we
  * add another field and forget to adjust this value.
  */
-#define ODPUTIL_FLOW_KEY_BYTES 640
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+#define ODPUTIL_FLOW_KEY_BYTES 648
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
 /* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
  * key.  An array of "struct nlattr" might not, in theory, be sufficiently
@@ -207,7 +208,9 @@ int odp_flow_from_string(const char *s, const struct simap 
*port_names,
                                                                              \
     /* If true, it means that the datapath supports the IPv6 Neigh           \
      * Discovery Extension bits. */                                          \
-    ODP_SUPPORT_FIELD(bool, nd_ext, "IPv6 ND Extension")
+    ODP_SUPPORT_FIELD(bool, nd_ext, "IPv6 ND Extension")                     \
+                                                                             \
+    ODP_SUPPORT_FIELD(bool, ipv6_exthdrs, "IPv6 Extension headers")
 
 /* Indicates support for various fields. This defines how flows will be
  * serialised. */
diff --git a/lib/ofp-match.c b/lib/ofp-match.c
index 86a082dde..6525922ef 100644
--- a/lib/ofp-match.c
+++ b/lib/ofp-match.c
@@ -65,7 +65,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
 void
 ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
diff --git a/lib/packets.c b/lib/packets.c
index d0fba8176..ff38f1961 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -34,6 +34,7 @@
 #include "odp-util.h"
 #include "dp-packet.h"
 #include "unaligned.h"
+#include "openflow/openflow.h"
 
 const struct in6_addr in6addr_exact = IN6ADDR_EXACT_INIT;
 const struct in6_addr in6addr_all_hosts = IN6ADDR_ALL_HOSTS_INIT;
@@ -1082,6 +1083,33 @@ ipv6_is_cidr(const struct in6_addr *netmask)
     return true;
 }
 
+const char *
+packet_ipv6_exthdr_flag_to_string(uint32_t flag)
+{
+    switch (flag) {
+        case OFPIEH12_NONEXT:
+            return "nonext";
+        case OFPIEH12_ESP:
+            return "esp";
+        case OFPIEH12_AUTH:
+            return "auth";
+        case OFPIEH12_DEST:
+            return "dest";
+        case OFPIEH12_FRAG:
+            return "frag";
+        case OFPIEH12_ROUTER:
+            return "router";
+        case OFPIEH12_HOP:
+            return "hop";
+        case OFPIEH12_UNREP:
+            return "unrep";
+        case OFPIEH12_UNSEQ:
+            return "unseq";
+        default:
+            return NULL;
+    }
+}
+
 /* Populates 'b' with an Ethernet II packet headed with the given 'eth_dst',
  * 'eth_src' and 'eth_type' parameters.  A payload of 'size' bytes is allocated
  * in 'b' and returned.  This payload may be populated with appropriate
diff --git a/lib/packets.h b/lib/packets.h
index 5bdf6e4bb..3becc1faf 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -901,6 +901,17 @@ struct tcp_header {
 };
 BUILD_ASSERT_DECL(TCP_HEADER_LEN == sizeof(struct tcp_header));
 
+/* IPV6 extension header OpenFlow flags */
+#define OFPIEH12_NONEXT 0x001
+#define OFPIEH12_ESP    0x002
+#define OFPIEH12_AUTH   0x004
+#define OFPIEH12_DEST   0x008
+#define OFPIEH12_FRAG   0x010
+#define OFPIEH12_ROUTER 0x020
+#define OFPIEH12_HOP    0x040
+#define OFPIEH12_UNREP  0x080
+#define OFPIEH12_UNSEQ  0x100
+
 /* Connection states.
  *
  * Names like CS_RELATED are bit values, e.g. 1 << 2.
@@ -1612,6 +1623,7 @@ void packet_set_igmp3_query(struct dp_packet *, uint8_t 
max_resp,
                             uint8_t qqic);
 void packet_format_tcp_flags(struct ds *, uint16_t);
 const char *packet_tcp_flag_to_string(uint32_t flag);
+const char *packet_ipv6_exthdr_flag_to_string(uint32_t flag);
 void *compose_ipv6(struct dp_packet *packet, uint8_t proto,
                    const struct in6_addr *src, const struct in6_addr *dst,
                    uint8_t key_tc, ovs_be32 key_fl, uint8_t key_hl, int size);
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index 4df630c62..21f32b935 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -100,7 +100,7 @@ struct rule;
 /* Metadata for restoring pipeline context after recirculation.  Helpers
  * are inlined below to keep them together with the definition for easier
  * updates. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
 
 struct frozen_metadata {
     /* Metadata in struct flow. */
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 30e7caf54..e60abb00b 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1065,6 +1065,7 @@ sflow_read_set_action(const struct nlattr *attr,
     case OVS_KEY_ATTR_UNSPEC:
     case OVS_KEY_ATTR_PACKET_TYPE:
     case OVS_KEY_ATTR_NSH:
+    case OVS_KEY_ATTR_IPV6_EXTHDRS:
     case __OVS_KEY_ATTR_MAX:
     default:
         break;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index cc9c1c628..2ff954782 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4183,7 +4183,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t 
ofp_port,
 
     /* If 'struct flow' gets additional metadata, we'll need to zero it out
      * before traversing a patch port. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 42);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 43);
     memset(&flow_tnl, 0, sizeof flow_tnl);
 
     if (!check_output_prerequisites(ctx, xport, flow, check_stp)) {
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index a4c44052d..c0ecefde8 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1535,6 +1535,52 @@ check_nd_extensions(struct dpif_backer *backer)
     return !error;
 }
 
+/* Tests whether 'backer''s datapath supports IPv6 Extension headers.
+ *
+ * Returns false if 'backer' definitely does not support, true if it seems
+ * to support. */
+static bool
+check_ipv6_extension_headers(struct dpif_backer *backer)
+{
+struct eth_header *eth;
+struct ofpbuf actions;
+struct dp_packet packet;
+struct flow flow;
+int error;
+struct ovs_key_nd_extensions key, mask;
+
+ofpbuf_init(&actions, 64);
+memset(&key, 0x53, sizeof key);
+memset(&mask, 0x7f, sizeof mask);
+commit_masked_set_action(&actions, OVS_KEY_ATTR_IPV6_EXTHDRS, &key, &mask,
+ sizeof key);
+
+/* Compose a dummy ethernet packet. */
+dp_packet_init(&packet, ETH_HEADER_LEN);
+eth = dp_packet_put_zeros(&packet, ETH_HEADER_LEN);
+eth->eth_type = htons(0x1234);
+
+flow_extract(&packet, &flow);
+
+/* Execute the actions.  On datapaths without support fails with EINVAL. */
+struct dpif_execute execute = {
+.actions = actions.data,
+.actions_len = actions.size,
+.packet = &packet,
+.flow = &flow,
+.probe = true,
+};
+error = dpif_execute(backer->dpif, &execute);
+
+dp_packet_uninit(&packet);
+ofpbuf_uninit(&actions);
+
+VLOG_INFO("%s: Datapath %s IPv6 Extension headers", dpif_name(backer->dpif),
+  error ? "does not support" : "supports");
+
+return !error;
+}
+
 /* Tests whether 'backer''s datapath supports the OVS_ACTION_ATTR_ADD_MPLS
  * action. */
 static bool
@@ -1653,6 +1699,7 @@ check_support(struct dpif_backer *backer)
     backer->rt_support.odp.ct_orig_tuple = check_ct_orig_tuple(backer);
     backer->rt_support.odp.ct_orig_tuple6 = check_ct_orig_tuple6(backer);
     backer->rt_support.odp.nd_ext = check_nd_extensions(backer);
+backer->rt_support.odp.ipv6_exthdrs = check_ipv6_extension_headers(backer);
 }
 
 static int
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 156d3e058..282bdc338 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2291,7 +2291,7 @@ head_table() {
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl 
dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
         supported on Set-Field: metadata in_port_oxm eth_{src,dst} 
vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label} ip_dscp nw_ecn 
arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} sctp_{src,dst} 
icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
     matching:
-      exact match or wildcard: metadata in_port_oxm eth_{src,dst,type} 
vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label} nw_proto 
ip_dscp nw_ecn arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} 
sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
+      exact match or wildcard: metadata in_port_oxm eth_{src,dst,type} 
vlan_{vid,pcp} mpls_{label,tc} ip_{src,dst} ipv6_{src,dst,label,ext} nw_proto 
ip_dscp nw_ecn arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} 
sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} nd_{target,sll,tll}
 
 ' "$1"
 }
@@ -2353,7 +2353,7 @@ head_table () {
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl 
dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
         supported on Set-Field: 
tun_{id,src,dst,ipv6_{src,dst},flags,gbp_{id,flags},erspan_{idx,ver,dir,hwid},metadata0...metadata63}
 metadata in_{port,port_oxm} pkt_mark ct_{mark,label} reg0...reg15 
xreg0...xreg7 xxreg0...xxreg3 eth_{src,dst} vlan_{tci,vid,pcp} 
mpls_{label,tc,ttl} ip_{src,dst} ipv6_{src,dst,label} nw_tos ip_dscp 
nw_{ecn,ttl} arp_{op,spa,tpa,sha,tha} tcp_{src,dst} udp_{src,dst} 
sctp_{src,dst} icmp_{type,code} icmpv6_{type,code} 
nd_{target,sll,tll,reserved,options_type} nsh_{flags,spi,si,c1...c4,ttl}
     matching:
-      arbitrary mask: dp_hash 
tun_{id,src,dst,ipv6_{src,dst},flags,gbp_{id,flags},erspan_{idx,ver,dir,hwid},gtpu_{flags,msgtype},metadata0...metadata63}
 metadata pkt_mark 
ct_{state,mark,label,nw_{src,dst},ipv6_{src,dst},tp_{src,dst}} reg0...reg15 
xreg0...xreg7 xxreg0...xxreg3 eth_{src,dst} vlan_{tci,vid} ip_{src,dst} 
ipv6_{src,dst,label} ip_frag arp_{spa,tpa,sha,tha} tcp_{src,dst,flags} 
udp_{src,dst} sctp_{src,dst} nd_{target,sll,tll} nsh_{flags,c1...c4}
+      arbitrary mask: dp_hash 
tun_{id,src,dst,ipv6_{src,dst},flags,gbp_{id,flags},erspan_{idx,ver,dir,hwid},gtpu_{flags,msgtype},metadata0...metadata63}
 metadata pkt_mark 
ct_{state,mark,label,nw_{src,dst},ipv6_{src,dst},tp_{src,dst}} reg0...reg15 
xreg0...xreg7 xxreg0...xxreg3 eth_{src,dst} vlan_{tci,vid} ip_{src,dst} 
ipv6_{src,dst,label,ext} ip_frag arp_{spa,tpa,sha,tha} tcp_{src,dst,flags} 
udp_{src,dst} sctp_{src,dst} nd_{target,sll,tll} nsh_{flags,c1...c4}
       exact match or wildcard: recirc_id packet_type conj_id 
in_{port,port_oxm} actset_output ct_{zone,nw_proto} eth_type vlan_pcp 
mpls_{label,tc,bos,ttl} nw_{proto,tos} ip_dscp nw_{ecn,ttl} arp_op 
icmp_{type,code} icmpv6_{type,code} nd_{reserved,options_type} 
nsh_{mdtype,np,spi,si,ttl}
 
 ' "$1"
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index 95383275a..b54dcbf47 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -7143,3 +7143,34 @@ OVS_WAIT_UNTIL([cat p2.pcap | egrep "0x0050: *0000 *0000 
*5002 *2000 *b85e *0000
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([ipv6 extension headers])
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns1, at_ns2)
+
+AT_CHECK([ovs-vsctl add-port br0 p1 -- set interface p1 type=internal])
+AT_CHECK([ip link set p1 netns at_ns1])
+AT_CHECK([ip netns exec at_ns1 ip -6 addr add dev p1 fe80::1/64])
+AT_CHECK([ip netns exec at_ns1 ip link set p1 up])
+
+AT_CHECK([ovs-vsctl add-port br0 p2 -- set interface p2 type=internal])
+AT_CHECK([ip link set p2 netns at_ns2])
+AT_CHECK([ip netns exec at_ns2 ip -6 addr add dev p2 fe80::2/64])
+AT_CHECK([ip netns exec at_ns2 ip link set p2 up])
+
+AT_CHECK([ovs-ofctl add-flow br0 "priority=2,ipv6_ext=frag,action=normal"])
+
+NS_CHECK_EXEC([at_ns2], [tcpdump -l -n -xx -U -i p2 > p2.pcap &])
+
+sleep 1
+
+NS_CHECK_EXEC([at_ns1], [$PYTHON3 $srcdir/sendpkt.py p1 8E 07 06 D3 C9 B5 F2 
AF 1A 44 D3 5B 86 DD 60 00 00 00 00 08 2C 40 FE 80 00 00 00 00 00 00 00 00 00 
00 00 00 00 01 FE 80 00 00 00 00 00 00 00 00 00 00 00 00 00 02 3B 00 00 00 00 
00 00 00 > /dev/null])
+
+OVS_WAIT_UNTIL([cat p2.pcap | egrep "0x0000:  8e07 06d3 c9b5 f2af 1a44 d35b 
86dd 6000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p2.pcap | egrep "0x0010:  0000 0008 2c40 fe80 0000 0000 
0000 0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p2.pcap | egrep "0x0020:  0000 0000 0001 fe80 0000 0000 
0000 0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([cat p2.pcap | egrep "0x0030:  0000 0000 0002 3b00 0000 0000 
0000" 2>&1 1>/dev/null])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
-- 
2.25.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to