diff mbox series

[ovs-dev,v3] Support for match & set ICMPv6 reserved and options type fields

Message ID 1548291226-4686-2-git-send-email-vishal.deep.ajmera@ericsson.com
State Superseded
Headers show
Series [ovs-dev,v3] Support for match & set ICMPv6 reserved and options type fields | expand

Commit Message

Vishal Deep Ajmera Jan. 23, 2019, 4:52 p.m. UTC
Currently OVS supports all ARP protocol fields as OXM match fields to
implement the relevant ARP procedures for IPv4. This includes support
for matching copying and setting ARP fields. In IPv6 ARP has been
replaced by ICMPv6 neighbor discovery (ND) procedures, neighbor
advertisement and neighbor solicitation.

The support for ICMPv6 fields in OVS is not complete for the use cases
equivalent to ARP in IPv4. OVS lacks support for matching, copying and
setting the “ND option type” and “ND reserved” fields. Without these user
cannot implement all ICMPv6 ND procedures for IPv6 support.

This commit adds additional OXM fields to OVS for ICMPv6 “ND option type“
and ICMPv6 “ND reserved” using the OXM extension mechanism. This allows
support for parsing these fields from an ICMPv6 packet header and extending
the OpenFlow protocol with specifications for these new OXM fields for
matching, copying and setting.

Signed-off-by: Vishal Deep Ajmera <vishal.deep.ajmera@ericsson.com>
Co-authored-by: Ashvin Lakshmikantha <ashvin.lakshmikantha@ericsson.com>
Signed-off-by: Ashvin Lakshmikantha <ashvin.lakshmikantha@ericsson.com>
---
 NEWS                                              |   2 +
 build-aux/extract-ofp-fields                      |   1 +
 datapath/linux/compat/include/linux/openvswitch.h |   8 ++
 include/openvswitch/flow.h                        |   6 +-
 include/openvswitch/match.h                       |   3 +
 include/openvswitch/meta-flow.h                   |  28 +++++
 lib/flow.c                                        |  58 +++++++++--
 lib/match.c                                       |  21 ++++
 lib/meta-flow.c                                   |  38 +++++++
 lib/meta-flow.xml                                 |  12 +++
 lib/nx-match.c                                    |   8 ++
 lib/odp-execute.c                                 |  32 ++++++
 lib/odp-util.c                                    | 120 +++++++++++++++++++++-
 lib/packets.c                                     |  33 ++++++
 lib/packets.h                                     |   3 +
 ofproto/ofproto-dpif-sflow.c                      |   1 +
 tests/ofproto.at                                  |   4 +-
 17 files changed, 364 insertions(+), 14 deletions(-)

Comments

0-day Robot Jan. 23, 2019, 5:56 p.m. UTC | #1
Bleep bloop.  Greetings Vishal Deep Ajmera, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


git-am:
Failed to merge in the changes.
Patch failed at 0001 Support for match & set ICMPv6 reserved and options type fields
The copy of the patch that failed is found in:
   /var/lib/jenkins/jobs/upstream_build_from_pw/workspace/.git/rebase-apply/patch
When you have resolved this problem, run "git am --resolved".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".


Please check this out.  If you feel there has been an error, please email aconole@bytheb.org

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 358c9b9..6ffac90 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,8 @@  Post-v2.10.0
      * Add option for simple round-robin based Rxq to PMD assignment.
        It can be set with pmd-rxq-assign.
      * Add support for DPDK 18.11
+     * ICMPv6 ND enhancements: support for match and set ND options type
+       and reserved fields.
    - Add 'symmetric_l3' hash function.
    - OVS now honors 'updelay' and 'downdelay' for bonds with LACP configured.
    - ovs-vswitchd:
diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
index 3592594..e159a1d 100755
--- a/build-aux/extract-ofp-fields
+++ b/build-aux/extract-ofp-fields
@@ -71,6 +71,7 @@  OXM_CLASSES = {"NXM_OF_":        (0,          0x0000, 'extension'),
                "OXM_OF_":        (0,          0x8000, 'standard'),
                "OXM_OF_PKT_REG": (0,          0x8001, 'standard'),
                "ONFOXM_ET_":     (0x4f4e4600, 0xffff, 'standard'),
+               "ERICOXM_OF_":    (0,          0x1000, 'extension'),
 
                # This is the experimenter OXM class for Nicira, which is the
                # one that OVS would be using instead of NXM_OF_ and NXM_NX_
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 9b087f1..d5aa09d 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -375,6 +375,7 @@  enum ovs_key_attr {
 #ifndef __KERNEL__
 	/* Only used within userspace data path. */
 	OVS_KEY_ATTR_PACKET_TYPE,  /* be32 packet type */
+	OVS_KEY_ATTR_ND_EXTENSIONS, /* struct ovs_key_nd_extensions */
 #endif
 
 	__OVS_KEY_ATTR_MAX
@@ -489,6 +490,13 @@  struct ovs_key_nd {
 	__u8	nd_tll[ETH_ALEN];
 };
 
+#ifndef __KERNEL__
+struct ovs_key_nd_extensions {
+    __be32  nd_reserved;
+    __u8    nd_options_type;
+};
+#endif
+
 #define OVS_CT_LABELS_LEN_32	4
 #define OVS_CT_LABELS_LEN	(OVS_CT_LABELS_LEN_32 * sizeof(__u32))
 struct ovs_key_ct_labels {
diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
index 5d2cf09..57b6c92 100644
--- a/include/openvswitch/flow.h
+++ b/include/openvswitch/flow.h
@@ -144,7 +144,8 @@  struct flow {
     struct in6_addr nd_target;  /* IPv6 neighbor discovery (ND) target. */
     struct eth_addr arp_sha;    /* ARP/ND source hardware address. */
     struct eth_addr arp_tha;    /* ARP/ND target hardware address. */
-    ovs_be16 tcp_flags;         /* TCP flags. With L3 to avoid matching L4. */
+    ovs_be16 tcp_flags;         /* TCP flags/ICMPv6 ND options type.
+                                 * With L3 to avoid matching L4. */
     ovs_be16 pad2;              /* Pad to 64 bits. */
     struct ovs_key_nsh nsh;     /* Network Service Header keys */
 
@@ -153,7 +154,8 @@  struct flow {
     ovs_be16 tp_dst;            /* TCP/UDP/SCTP destination port/ICMP code. */
     ovs_be16 ct_tp_src;         /* CT original tuple source port/ICMP type. */
     ovs_be16 ct_tp_dst;         /* CT original tuple dst port/ICMP code. */
-    ovs_be32 igmp_group_ip4;    /* IGMP group IPv4 address.
+    ovs_be32 igmp_group_ip4;    /* IGMP group IPv4 address/ICMPv6 ND reserved
+                                 * field.
                                  * Keep last for BUILD_ASSERT_DECL below. */
     ovs_be32 pad3;              /* Pad to 64 bits. */
 };
diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index e8c80dd..05ecee7 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -219,6 +219,9 @@  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 *);
 
+void match_set_nd_reserved(struct match *, ovs_be32);
+void match_set_nd_options_type(struct match *, uint8_t);
+
 bool match_equal(const struct match *, const struct match *);
 uint32_t match_hash(const struct match *, uint32_t basis);
 
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index ffd8945..65d99f4 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -1796,6 +1796,34 @@  enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_ND_TLL,
 
+    /* "nd_reserved".
+     *
+     * The reserved field in IPv6 Neighbor Discovery message.
+     *
+     * Type: be32.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: ND.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: ERICOXM_OF_ICMPV6_ND_RESERVED(1) since v2.11.
+     */
+    MFF_ND_RESERVED,
+
+    /* "nd_options_type".
+     *
+     * The type of the option in IPv6 Neighbor Discovery message.
+     *
+     * Type: u8.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: ND.
+     * Access: read/write.
+     * NXM: none.
+     * OXM: ERICOXM_OF_ICMPV6_ND_OPTIONS_TYPE(2) since v2.11.
+     */
+    MFF_ND_OPTIONS_TYPE,
+
 /* ## ---- ## */
 /* ## NSH  ## */
 /* ## ---- ## */
diff --git a/lib/flow.c b/lib/flow.c
index c60446f..a83a840 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -396,13 +396,14 @@  parse_ethertype(const void **datap, size_t *sizep)
 }
 
 /* Returns 'true' if the packet is an ND packet. In that case the '*nd_target'
- * and 'arp_buf[]' are filled in.  If the packet is not an ND pacet, 'false' is
- * returned and no values are filled in on '*nd_target' or 'arp_buf[]'. */
+ * and 'arp_buf[]' are filled in.  If the packet is not an ND packet, 'false'
+ * is returned and no values are filled in on '*nd_target' or 'arp_buf[]'. */
 static inline bool
 parse_icmpv6(const void **datap, size_t *sizep, const struct icmp6_hdr *icmp,
-             const struct in6_addr **nd_target,
-             struct eth_addr arp_buf[2])
+             uint32_t *rso_flags, const struct in6_addr **nd_target,
+             struct eth_addr arp_buf[2], uint8_t *opt_type)
 {
+    const uint32_t *reserved;
     if (icmp->icmp6_code != 0 ||
         (icmp->icmp6_type != ND_NEIGHBOR_SOLICIT &&
          icmp->icmp6_type != ND_NEIGHBOR_ADVERT)) {
@@ -411,6 +412,15 @@  parse_icmpv6(const void **datap, size_t *sizep, const struct icmp6_hdr *icmp,
 
     arp_buf[0] = eth_addr_zero;
     arp_buf[1] = eth_addr_zero;
+    *opt_type = 0;
+
+    reserved = data_try_pull(datap, sizep, sizeof(uint32_t));
+    if (OVS_UNLIKELY(!reserved)) {
+        /* Invalid ND packet. */
+        return false;
+    }
+    *rso_flags = *reserved;
+
     *nd_target = data_try_pull(datap, sizep, sizeof **nd_target);
     if (OVS_UNLIKELY(!*nd_target)) {
         return true;
@@ -432,12 +442,20 @@  parse_icmpv6(const void **datap, size_t *sizep, const struct icmp6_hdr *icmp,
         if (lla_opt->type == ND_OPT_SOURCE_LINKADDR && opt_len == 8) {
             if (OVS_LIKELY(eth_addr_is_zero(arp_buf[0]))) {
                 arp_buf[0] = lla_opt->mac;
+                /* We use only first option type present in ND packet. */
+                if (*opt_type == 0) {
+                    *opt_type = lla_opt->type;
+                }
             } else {
                 goto invalid;
             }
         } else if (lla_opt->type == ND_OPT_TARGET_LINKADDR && opt_len == 8) {
             if (OVS_LIKELY(eth_addr_is_zero(arp_buf[1]))) {
                 arp_buf[1] = lla_opt->mac;
+                /* We use only first option type present in ND packet. */
+                if (*opt_type == 0) {
+                    *opt_type = lla_opt->type;
+                }
             } else {
                 goto invalid;
             }
@@ -987,18 +1005,38 @@  miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
             if (OVS_LIKELY(size >= sizeof(struct icmp6_hdr))) {
                 const struct in6_addr *nd_target;
                 struct eth_addr arp_buf[2];
-                const struct icmp6_hdr *icmp = data_pull(&data, &size,
-                                                         sizeof *icmp);
-                if (parse_icmpv6(&data, &size, icmp, &nd_target, arp_buf)) {
+                /* This will populate whether we received Option 1
+                 * or Option 2. */
+                uint8_t opt_type;
+                /* This holds the ND Reserved field. */
+                uint32_t rso_flags;
+                const struct icmp6_hdr *icmp = data_pull(&data,
+                                               &size,ICMP6_HEADER_LEN);
+                if (parse_icmpv6(&data, &size, icmp,
+                                 &rso_flags, &nd_target, arp_buf, &opt_type)) {
                     if (nd_target) {
                         miniflow_push_words(mf, nd_target, nd_target,
                                             sizeof *nd_target / sizeof(uint64_t));
                     }
                     miniflow_push_macs(mf, arp_sha, arp_buf);
-                    miniflow_pad_to_64(mf, arp_tha);
+                    /* Populate options field and set the padding
+                     * accordingly. */
+                    if (opt_type != 0) {
+                        miniflow_push_be16(mf, tcp_flags, htons(opt_type));
+                        /* Pad to align with 64 bits.
+                         * This will zero out the pad3 field. */
+                        miniflow_pad_to_64(mf, tcp_flags);
+                    } else {
+                        /* Pad to align with 64 bits.
+                         * This will zero out the tcp_flags & pad3 field. */
+                        miniflow_pad_to_64(mf, arp_tha);
+                    }
                     miniflow_push_be16(mf, tp_src, htons(icmp->icmp6_type));
                     miniflow_push_be16(mf, tp_dst, htons(icmp->icmp6_code));
                     miniflow_pad_to_64(mf, tp_dst);
+                    /* Fill ND reserved field. */
+                    miniflow_push_be32(mf, igmp_group_ip4, htonl(rso_flags));
+                    miniflow_pad_to_64(mf, igmp_group_ip4);
                 } else {
                     /* ICMPv6 but not ND. */
                     miniflow_push_be16(mf, tp_src, htons(icmp->icmp6_type));
@@ -1927,6 +1965,8 @@  flow_wc_map(const struct flow *flow, struct flowmap *map)
             FLOWMAP_SET(map, nd_target);
             FLOWMAP_SET(map, arp_sha);
             FLOWMAP_SET(map, arp_tha);
+            FLOWMAP_SET(map, tcp_flags);
+            FLOWMAP_SET(map, igmp_group_ip4);
         } else {
             FLOWMAP_SET(map, ct_nw_proto);
             FLOWMAP_SET(map, ct_ipv6_src);
@@ -2968,6 +3008,8 @@  flow_compose_l4(struct dp_packet *p, const struct flow *flow,
             struct icmp6_hdr *icmp = dp_packet_put_zeros(p, sizeof *icmp);
             icmp->icmp6_type = ntohs(flow->tp_src);
             icmp->icmp6_code = ntohs(flow->tp_dst);
+            uint32_t *reserved = &icmp->icmp6_dataun.icmp6_un_data32[0];
+            *reserved = ntohl(flow->igmp_group_ip4);
 
             if (icmp->icmp6_code == 0 &&
                 (icmp->icmp6_type == ND_NEIGHBOR_SOLICIT ||
diff --git a/lib/match.c b/lib/match.c
index a1407a8..052daee 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1080,6 +1080,19 @@  match_set_nd_target_masked(struct match *match,
     match->wc.masks.nd_target = *mask;
 }
 
+void
+match_set_nd_reserved (struct match *match, ovs_be32 value)
+{
+   match->flow.igmp_group_ip4 = value;
+   match->wc.masks.igmp_group_ip4 = OVS_BE32_MAX;
+}
+
+void
+match_set_nd_options_type(struct match *match, uint8_t option)
+{
+    match_set_tcp_flags(match, htons(option));
+}
+
 /* Returns true if 'a' and 'b' wildcard the same fields and have the same
  * values for fixed fields, otherwise false. */
 bool
@@ -1688,6 +1701,14 @@  match_format(const struct match *match,
                             &wc->masks.nd_target);
         format_eth_masked(s, "nd_sll", f->arp_sha, wc->masks.arp_sha);
         format_eth_masked(s, "nd_tll", f->arp_tha, wc->masks.arp_tha);
+        if (wc->masks.igmp_group_ip4) {
+            format_be32_masked(s,"nd_reserved", f->igmp_group_ip4,
+                               wc->masks.igmp_group_ip4);
+        }
+        if (wc->masks.tcp_flags) {
+            format_be16_masked(s,"nd_options_type", f->tcp_flags,
+                               wc->masks.tcp_flags);
+        }
     } else {
         format_be16_masked(s, "tp_src", f->tp_src, wc->masks.tp_src);
         format_be16_masked(s, "tp_dst", f->tp_dst, wc->masks.tp_dst);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index b6d9e92..1f5c011 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -344,6 +344,11 @@  mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
     case MFF_ND_TARGET:
         return ipv6_mask_is_any(&wc->masks.nd_target);
 
+    case MFF_ND_RESERVED:
+        return !wc->masks.igmp_group_ip4;
+    case MFF_ND_OPTIONS_TYPE:
+        return !wc->masks.tcp_flags;
+
     case MFF_IP_FRAG:
         return !(wc->masks.nw_frag & FLOW_NW_FRAG_MASK);
 
@@ -571,6 +576,8 @@  mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
     case MFF_ND_TARGET:
     case MFF_ND_SLL:
     case MFF_ND_TLL:
+    case MFF_ND_RESERVED:
+    case MFF_ND_OPTIONS_TYPE:
         return true;
 
     case MFF_IN_PORT_OXM:
@@ -909,9 +916,14 @@  mf_get_value(const struct mf_field *mf, const struct flow *flow,
         break;
 
     case MFF_TCP_FLAGS:
+    case MFF_ND_OPTIONS_TYPE:
         value->be16 = flow->tcp_flags;
         break;
 
+    case MFF_ND_RESERVED:
+        value->be32 = flow->igmp_group_ip4;
+        break;
+
     case MFF_ICMPV4_TYPE:
     case MFF_ICMPV6_TYPE:
         value->u8 = ntohs(flow->tp_src);
@@ -1259,6 +1271,14 @@  mf_set_value(const struct mf_field *mf,
         match_set_nd_target(match, &value->ipv6);
         break;
 
+    case MFF_ND_RESERVED:
+        match_set_nd_reserved(match, value->be32);
+        break;
+
+    case MFF_ND_OPTIONS_TYPE:
+        match_set_nd_options_type(match, value->u8);
+        break;
+
     case MFF_NSH_FLAGS:
         MATCH_SET_FIELD_UINT8(match, nsh.flags, value->u8);
         break;
@@ -1668,6 +1688,14 @@  mf_set_flow_value(const struct mf_field *mf,
         flow->nd_target = value->ipv6;
         break;
 
+    case MFF_ND_RESERVED:
+        flow->igmp_group_ip4 = value->be32;
+        break;
+
+    case MFF_ND_OPTIONS_TYPE:
+        flow->tcp_flags = htons(value->u8);
+        break;
+
     case MFF_NSH_FLAGS:
         flow->nsh.flags = value->u8;
         break;
@@ -1823,6 +1851,8 @@  mf_is_pipeline_field(const struct mf_field *mf)
     case MFF_ND_TARGET:
     case MFF_ND_SLL:
     case MFF_ND_TLL:
+    case MFF_ND_RESERVED:
+    case MFF_ND_OPTIONS_TYPE:
     case MFF_NSH_FLAGS:
     case MFF_NSH_TTL:
     case MFF_NSH_MDTYPE:
@@ -2150,6 +2180,11 @@  mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
         match->wc.masks.arp_tha = eth_addr_zero;
         break;
 
+    case MFF_ND_RESERVED:
+        match->wc.masks.igmp_group_ip4 = htonl(0);
+        match->flow.igmp_group_ip4 = htonl(0);
+        break;
+
     case MFF_TCP_SRC:
     case MFF_UDP_SRC:
     case MFF_SCTP_SRC:
@@ -2169,6 +2204,7 @@  mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
         break;
 
     case MFF_TCP_FLAGS:
+    case MFF_ND_OPTIONS_TYPE:
         match->wc.masks.tcp_flags = htons(0);
         match->flow.tcp_flags = htons(0);
         break;
@@ -2285,6 +2321,8 @@  mf_set(const struct mf_field *mf,
     case MFF_ICMPV4_CODE:
     case MFF_ICMPV6_TYPE:
     case MFF_ICMPV6_CODE:
+    case MFF_ND_RESERVED:
+    case MFF_ND_OPTIONS_TYPE:
         return OFPUTIL_P_NONE;
 
     case MFF_DP_HASH:
diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
index 0b5c1d0..31db761 100644
--- a/lib/meta-flow.xml
+++ b/lib/meta-flow.xml
@@ -4586,6 +4586,18 @@  r r c c c.
            title="ICMPv6 Neighbor Discovery Source Ethernet Address"/>
     <field id="MFF_ND_TLL"
            title="ICMPv6 Neighbor Discovery Target Ethernet Address"/>
+    <field id="MFF_ND_RESERVED"
+           title="ICMPv6 Neighbor Discovery Reserved Field"/>
+      <p>
+        This is used to set the R,S,O bits in Neighbor Advertisement Messages
+      </p>
+    <field id="MFF_ND_OPTIONS_TYPE"
+          title="ICMPv6 Neighbor Discovery Options Type Field"/>
+    <p>
+       A value of 1 indicates that the option is Source Link Layer.
+       A value of 2 indicates that the options is Target Link Layer.
+       See RFC 4861 for further details.
+    </p>
   </group>
 
   <h1>References</h1>
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 8f98032..17caff0 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -990,8 +990,16 @@  nxm_put_ip(struct nxm_put_ctx *ctx,
                           ntohs(flow->tp_dst));
             }
             if (is_nd(flow, NULL)) {
+                if (match->wc.masks.igmp_group_ip4) {
+                    nxm_put_32(ctx, MFF_ND_RESERVED, oxm,
+                           flow->igmp_group_ip4);
+                }
                 nxm_put_ipv6(ctx, MFF_ND_TARGET, oxm,
                              &flow->nd_target, &match->wc.masks.nd_target);
+                if (match->wc.masks.tcp_flags) {
+                   nxm_put_8(ctx, MFF_ND_OPTIONS_TYPE, oxm,
+                             ntohs(flow->tcp_flags));
+                }
                 if (flow->tp_src == htons(ND_NEIGHBOR_SOLICIT)) {
                     nxm_put_eth_masked(ctx, MFF_ND_SLL, oxm,
                                        flow->arp_sha, match->wc.masks.arp_sha);
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 3b6890e..5d07133 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -228,6 +228,23 @@  set_arp(struct dp_packet *packet, const struct ovs_key_arp *key,
 }
 
 static void
+odp_set_nd_ext(struct dp_packet *packet, const struct ovs_key_nd_extensions
+        *key, const struct ovs_key_nd_extensions *mask)
+{
+    const struct ovs_nd_msg *ns = dp_packet_l4(packet);
+    ovs_16aligned_be32 reserved = ns->rso_flags;
+    uint8_t opt_type = ns->options[0].type;
+
+    if (mask->nd_reserved) {
+        put_16aligned_be32(&reserved, key->nd_reserved);
+    }
+    if (mask->nd_options_type) {
+        opt_type = key->nd_options_type;
+    }
+    packet_set_nd_ext(packet, reserved, opt_type);
+}
+
+static void
 odp_set_nd(struct dp_packet *packet, const struct ovs_key_nd *key,
            const struct ovs_key_nd *mask)
 {
@@ -438,6 +455,16 @@  odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
         }
         break;
 
+    case OVS_KEY_ATTR_ND_EXTENSIONS:
+        if (OVS_LIKELY(dp_packet_get_nd_payload(packet))) {
+            const struct ovs_key_nd_extensions *nd_ext_key
+                 = nl_attr_get_unspec(a, sizeof(struct ovs_key_nd_extensions));
+            ovs_16aligned_be32 rso_flags;
+            put_16aligned_be32(&rso_flags, nd_ext_key->nd_reserved);
+            packet_set_nd_ext(packet, rso_flags, nd_ext_key->nd_options_type);
+        }
+        break;
+
     case OVS_KEY_ATTR_DP_HASH:
         md->dp_hash = nl_attr_get_u32(a);
         break;
@@ -540,6 +567,11 @@  odp_execute_masked_set_action(struct dp_packet *packet,
                    get_mask(a, struct ovs_key_nd));
         break;
 
+    case OVS_KEY_ATTR_ND_EXTENSIONS:
+        odp_set_nd_ext(packet, nl_attr_get(a),
+                       get_mask(a, struct ovs_key_nd_extensions));
+        break;
+
     case OVS_KEY_ATTR_DP_HASH:
         md->dp_hash = nl_attr_get_u32(a)
             | (md->dp_hash & ~*get_mask(a, uint32_t));
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 1e8c5f1..2d7c856 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -173,6 +173,7 @@  ovs_key_attr_to_string(enum ovs_key_attr attr, char *namebuf, size_t bufsize)
     case OVS_KEY_ATTR_ICMPV6: return "icmpv6";
     case OVS_KEY_ATTR_ARP: return "arp";
     case OVS_KEY_ATTR_ND: return "nd";
+    case OVS_KEY_ATTR_ND_EXTENSIONS: return "nd_ext";
     case OVS_KEY_ATTR_MPLS: return "mpls";
     case OVS_KEY_ATTR_DP_HASH: return "dp_hash";
     case OVS_KEY_ATTR_RECIRC_ID: return "recirc_id";
@@ -2500,6 +2501,7 @@  const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {
     [OVS_KEY_ATTR_ICMPV6]    = { .len = sizeof(struct ovs_key_icmpv6) },
     [OVS_KEY_ATTR_ARP]       = { .len = sizeof(struct ovs_key_arp) },
     [OVS_KEY_ATTR_ND]        = { .len = sizeof(struct ovs_key_nd) },
+    [OVS_KEY_ATTR_ND_EXTENSIONS] = { .len = sizeof(struct ovs_key_nd_extensions) },
     [OVS_KEY_ATTR_CT_STATE]  = { .len = 4 },
     [OVS_KEY_ATTR_CT_ZONE]   = { .len = 2 },
     [OVS_KEY_ATTR_CT_MARK]   = { .len = 4 },
@@ -2950,6 +2952,7 @@  odp_mask_is_constant__(enum ovs_key_attr attr, const void *mask, size_t size,
     case OVS_KEY_ATTR_ICMP:
     case OVS_KEY_ATTR_ICMPV6:
     case OVS_KEY_ATTR_ND:
+    case OVS_KEY_ATTR_ND_EXTENSIONS:
     case OVS_KEY_ATTR_SKB_MARK:
     case OVS_KEY_ATTR_TUNNEL:
     case OVS_KEY_ATTR_SCTP:
@@ -4044,6 +4047,21 @@  format_odp_key_attr__(const struct nlattr *a, const struct nlattr *ma,
         ds_chomp(ds, ',');
         break;
     }
+    case OVS_KEY_ATTR_ND_EXTENSIONS: {
+        const struct ovs_key_nd_extensions *mask = ma ? nl_attr_get(ma) : NULL;
+        const struct ovs_key_nd_extensions *key = nl_attr_get(a);
+
+        bool first = true;
+        format_be32_masked(ds, &first, "nd_reserved", key->nd_reserved,
+                           OVS_BE32_MAX);
+        ds_put_char(ds, ',');
+
+        format_u8u(ds, "nd_options_type", key->nd_options_type,
+                   MASK(mask, nd_options_type), verbose);
+
+        ds_chomp(ds, ',');
+        break;
+    }
     case OVS_KEY_ATTR_NSH: {
         format_odp_nsh_attr(a, ma, ds);
         break;
@@ -5550,6 +5568,11 @@  parse_odp_key_mask_attr(struct parse_odp_context *context, const char *s,
         SCAN_FIELD("tll=", eth, nd_tll);
     } SCAN_END(OVS_KEY_ATTR_ND);
 
+    SCAN_BEGIN("nd_ext(", struct ovs_key_nd_extensions) {
+        SCAN_FIELD("reserved=", be32, nd_reserved);
+        SCAN_FIELD("nd_options_type=", u8, nd_options_type);
+    } SCAN_END(OVS_KEY_ATTR_ND_EXTENSIONS);
+
     struct packet_type {
         ovs_be16 ns;
         ovs_be16 id;
@@ -5927,14 +5950,25 @@  odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
                  * xlate_wc_finish() for details. */
                 && (!export_mask || (data->tp_src == htons(0xff)
                                      && data->tp_dst == htons(0xff)))) {
-
                 struct ovs_key_nd *nd_key;
-
+                struct ovs_key_nd_extensions *nd_ext_key;
                 nd_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_ND,
                                                     sizeof *nd_key);
                 nd_key->nd_target = data->nd_target;
                 nd_key->nd_sll = data->arp_sha;
                 nd_key->nd_tll = data->arp_tha;
+
+                /* Add ND Extensions Attr only if reserved field
+                 * or options type is set. */
+                if (data->igmp_group_ip4 != 0 ||
+                    data->tcp_flags != 0) {
+                    nd_ext_key =
+                         nl_msg_put_unspec_uninit(buf,
+                                            OVS_KEY_ATTR_ND_EXTENSIONS,
+                                            sizeof *nd_ext_key);
+                    nd_ext_key->nd_reserved = data->igmp_group_ip4;
+                    nd_ext_key->nd_options_type = ntohs(data->tcp_flags);
+                }
             }
         }
     }
@@ -6118,6 +6152,7 @@  odp_key_to_dp_packet(const struct nlattr *key, size_t key_len,
         case OVS_KEY_ATTR_ICMPV6:
         case OVS_KEY_ATTR_ARP:
         case OVS_KEY_ATTR_ND:
+        case OVS_KEY_ATTR_ND_EXTENSIONS:
         case OVS_KEY_ATTR_SCTP:
         case OVS_KEY_ATTR_TCP_FLAGS:
         case OVS_KEY_ATTR_MPLS:
@@ -6557,6 +6592,35 @@  parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                         }
                     }
                 }
+                if (present_attrs &
+                    (UINT64_C(1) << OVS_KEY_ATTR_ND_EXTENSIONS)) {
+                    const struct ovs_key_nd_extensions *nd_ext_key;
+                    if (!is_mask) {
+                        expected_attrs |=
+                                UINT64_C(1) << OVS_KEY_ATTR_ND_EXTENSIONS;
+                    }
+
+                    nd_ext_key =
+                        nl_attr_get(attrs[OVS_KEY_ATTR_ND_EXTENSIONS]);
+                    flow->igmp_group_ip4 = nd_ext_key->nd_reserved;
+                    flow->tcp_flags = htons(nd_ext_key->nd_options_type);
+
+                    if (is_mask) {
+                        /* Even though 'tp_src' and 'tp_dst' are 16 bits wide,
+                         * ICMP type and code are 8 bits wide.  Therefore, an
+                         * exact match looks like htons(0xff), not
+                         * htons(0xffff).  See xlate_wc_finish() for details.
+                         * */
+                        if (!is_all_zeros(nd_ext_key, sizeof *nd_ext_key) &&
+                            (flow->tp_src != htons(0xff) ||
+                             flow->tp_dst != htons(0xff))) {
+                            return ODP_FIT_ERROR;
+                        } else {
+                            expected_attrs |=
+                                UINT64_C(1) << OVS_KEY_ATTR_ND_EXTENSIONS;
+                        }
+                    }
+                }
             }
         }
     } else if (src_flow->nw_proto == IPPROTO_IGMP
@@ -7453,6 +7517,24 @@  put_nd_key(const struct ovs_key_nd *nd, struct flow *flow)
     flow->arp_tha = nd->nd_tll;
 }
 
+static void
+get_nd_extensions_key(const struct flow *flow,
+                      struct ovs_key_nd_extensions *nd_ext)
+{
+    /* ND Extensions key has padding, clear it. */
+    memset(nd_ext, 0, sizeof *nd_ext);
+    nd_ext->nd_reserved = flow->igmp_group_ip4;
+    nd_ext->nd_options_type = ntohs(flow->tcp_flags);
+}
+
+static void
+put_nd_extensions_key(const struct ovs_key_nd_extensions *nd_ext,
+                      struct flow *flow)
+{
+    flow->igmp_group_ip4 = nd_ext->nd_reserved;
+    flow->tcp_flags = htons(nd_ext->nd_options_type);
+}
+
 static enum slow_path_reason
 commit_set_nd_action(const struct flow *flow, struct flow *base_flow,
                      struct ofpbuf *odp_actions,
@@ -7475,10 +7557,33 @@  commit_set_nd_action(const struct flow *flow, struct flow *base_flow,
 }
 
 static enum slow_path_reason
+commit_set_nd_extensions_action(const struct flow *flow,
+                                struct flow *base_flow,
+                                struct ofpbuf *odp_actions,
+                                struct flow_wildcards *wc, bool use_masked)
+{
+    struct ovs_key_nd_extensions key, mask, base;
+
+    get_nd_extensions_key(flow, &key);
+    get_nd_extensions_key(base_flow, &base);
+    get_nd_extensions_key(&wc->masks, &mask);
+
+    if (commit(OVS_KEY_ATTR_ND_EXTENSIONS, use_masked, &key,
+               &base, &mask, sizeof key, odp_actions)) {
+        put_nd_extensions_key(&base, base_flow);
+        put_nd_extensions_key(&mask, &wc->masks);
+        return SLOW_ACTION;
+    }
+    return 0;
+}
+
+static enum slow_path_reason
 commit_set_nw_action(const struct flow *flow, struct flow *base,
                      struct ofpbuf *odp_actions, struct flow_wildcards *wc,
                      bool use_masked)
 {
+    uint32_t reason;
+
     /* Check if 'flow' really has an L3 header. */
     if (!flow->nw_proto) {
         return 0;
@@ -7491,7 +7596,16 @@  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);
-        return commit_set_nd_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.*/
+            reason = commit_set_nd_extensions_action(flow, base,
+                                         odp_actions, wc, use_masked);
+            reason |= commit_set_nd_action(flow, base, odp_actions,
+                                         wc, use_masked);
+            return reason;
+        }
+        break;
 
     case ETH_TYPE_ARP:
         return commit_set_arp_action(flow, base, odp_actions, wc);
diff --git a/lib/packets.c b/lib/packets.c
index 2d6f77b..f5a2005 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -1282,6 +1282,39 @@  packet_set_icmp(struct dp_packet *packet, uint8_t type, uint8_t code)
 }
 
 void
+packet_set_nd_ext(struct dp_packet *packet, const ovs_16aligned_be32 rso_flags,
+                  const uint8_t opt_type)
+{
+    struct ovs_nd_msg *ns;
+    struct ovs_nd_lla_opt *opt;
+    int bytes_remain = dp_packet_l4_size(packet);
+    struct ovs_16aligned_ip6_hdr * nh = dp_packet_l3(packet);
+    uint32_t pseudo_hdr_csum = 0;
+
+    if (OVS_UNLIKELY(bytes_remain < sizeof(*ns))) {
+        return;
+    }
+
+    if (nh) {
+        pseudo_hdr_csum = packet_csum_pseudoheader6(nh);
+    }
+
+    ns = dp_packet_l4(packet);
+    opt = &ns->options[0];
+
+    /* set RSO flags and option type */
+    ns->rso_flags = rso_flags;
+    opt->type = opt_type;
+
+    /* recalculate checksum */
+    ovs_be16 *csum_value = &(ns->icmph.icmp6_cksum);
+    *csum_value = 0;
+    *csum_value = csum_finish(csum_continue(pseudo_hdr_csum,
+                              &(ns->icmph), bytes_remain));
+
+}
+
+void
 packet_set_nd(struct dp_packet *packet, const struct in6_addr *target,
               const struct eth_addr sll, const struct eth_addr tll)
 {
diff --git a/lib/packets.h b/lib/packets.h
index 09a0ac3..fcd90b3 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1543,6 +1543,9 @@  void packet_set_sctp_port(struct dp_packet *, ovs_be16 src, ovs_be16 dst);
 void packet_set_icmp(struct dp_packet *, uint8_t type, uint8_t code);
 void packet_set_nd(struct dp_packet *, const struct in6_addr *target,
                    const struct eth_addr sll, const struct eth_addr tll);
+void packet_set_nd_ext(struct dp_packet *packet,
+                       const ovs_16aligned_be32 rso_flags,
+                       const uint8_t opt_type);
 
 void packet_format_tcp_flags(struct ds *, uint16_t);
 const char *packet_tcp_flag_to_string(uint32_t flag);
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 7da3175..bc4ffee 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1055,6 +1055,7 @@  sflow_read_set_action(const struct nlattr *attr,
     case OVS_KEY_ATTR_ICMPV6:
     case OVS_KEY_ATTR_ARP:
     case OVS_KEY_ATTR_ND:
+    case OVS_KEY_ATTR_ND_EXTENSIONS:
     case OVS_KEY_ATTR_CT_STATE:
     case OVS_KEY_ATTR_CT_ZONE:
     case OVS_KEY_ATTR_CT_MARK:
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 6a2cf27..a426e47 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2445,7 +2445,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 tun_src tun_dst tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_erspan_idx tun_erspan_ver tun_erspan_dir tun_erspan_hwid tun_metadata0 dnl
 tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 dnl
-metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 reg9 reg10 reg11 reg12 reg13 reg14 reg15 xreg0 xreg1 xreg2 xreg3 xreg4 xreg5 xreg6 xreg7 xxreg0 xxreg1 xxreg2 xxreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc mpls_ttl ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll nsh_flags nsh_spi nsh_si nsh_c1 nsh_c2 nsh_c3 nsh_c4 nsh_ttl
+metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 reg9 reg10 reg11 reg12 reg13 reg14 reg15 xreg0 xreg1 xreg2 xreg3 xreg4 xreg5 xreg6 xreg7 xxreg0 xxreg1 xxreg2 xxreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc mpls_ttl ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll nd_reserved nd_options_type nsh_flags nsh_spi nsh_si nsh_c1 nsh_c2 nsh_c3 nsh_c4 nsh_ttl
     matching:
       dp_hash: arbitrary mask
       recirc_id: exact match or wildcard
@@ -2611,6 +2611,8 @@  metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4
       nd_target: arbitrary mask
       nd_sll: arbitrary mask
       nd_tll: arbitrary mask
+      nd_reserved: exact match or wildcard
+      nd_options_type: exact match or wildcard
       nsh_flags: arbitrary mask
       nsh_mdtype: exact match or wildcard
       nsh_np: exact match or wildcard