get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/804216/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 804216,
    "url": "http://patchwork.ozlabs.org/api/patches/804216/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/1503360901-30356-2-git-send-email-yi.y.yang@intel.com/",
    "project": {
        "id": 47,
        "url": "http://patchwork.ozlabs.org/api/projects/47/?format=api",
        "name": "Open vSwitch",
        "link_name": "openvswitch",
        "list_id": "ovs-dev.openvswitch.org",
        "list_email": "ovs-dev@openvswitch.org",
        "web_url": "http://openvswitch.org/",
        "scm_url": "git@github.com:openvswitch/ovs.git",
        "webscm_url": "https://github.com/openvswitch/ovs",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<1503360901-30356-2-git-send-email-yi.y.yang@intel.com>",
    "list_archive_url": null,
    "date": "2017-08-22T00:15:00",
    "name": "[ovs-dev,v3,1/2] nsh: rework NSH netlink keys and actions",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "7a924e53b558416b98c419d0f892b9c8093cfed3",
    "submitter": {
        "id": 68962,
        "url": "http://patchwork.ozlabs.org/api/people/68962/?format=api",
        "name": "Yang, Yi",
        "email": "yi.y.yang@intel.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/1503360901-30356-2-git-send-email-yi.y.yang@intel.com/mbox/",
    "series": [],
    "comments": "http://patchwork.ozlabs.org/api/patches/804216/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/804216/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<ovs-dev-bounces@openvswitch.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "dev@openvswitch.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@bilbo.ozlabs.org",
            "ovs-dev@mail.linuxfoundation.org"
        ],
        "Authentication-Results": "ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=openvswitch.org\n\t(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;\n\tenvelope-from=ovs-dev-bounces@openvswitch.org;\n\treceiver=<UNKNOWN>)",
        "Received": [
            "from mail.linuxfoundation.org (mail.linuxfoundation.org\n\t[140.211.169.12])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256\n\tbits)) (No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xbrlp2GZZz9s8V\n\tfor <incoming@patchwork.ozlabs.org>;\n\tTue, 22 Aug 2017 10:19:34 +1000 (AEST)",
            "from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id C93D8A7A;\n\tTue, 22 Aug 2017 00:18:56 +0000 (UTC)",
            "from smtp1.linuxfoundation.org (smtp1.linux-foundation.org\n\t[172.17.192.35])\n\tby mail.linuxfoundation.org (Postfix) with ESMTPS id 40F02A91\n\tfor <dev@openvswitch.org>; Tue, 22 Aug 2017 00:18:53 +0000 (UTC)",
            "from mga02.intel.com (mga02.intel.com [134.134.136.20])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id 08089450\n\tfor <dev@openvswitch.org>; Tue, 22 Aug 2017 00:18:53 +0000 (UTC)",
            "from orsmga004.jf.intel.com ([10.7.209.38])\n\tby orsmga101.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384;\n\t21 Aug 2017 17:18:52 -0700",
            "from unknown (HELO localhost.localdomain.bj.intel.com)\n\t([10.240.224.185])\n\tby orsmga004.jf.intel.com with ESMTP; 21 Aug 2017 17:18:50 -0700"
        ],
        "X-Greylist": "domain auto-whitelisted by SQLgrey-1.7.6",
        "X-ExtLoop1": "1",
        "X-IronPort-AV": "E=Sophos;i=\"5.41,410,1498546800\"; d=\"scan'208\";a=\"121308831\"",
        "From": "Yi Yang <yi.y.yang@intel.com>",
        "To": "dev@openvswitch.org",
        "Date": "Tue, 22 Aug 2017 08:15:00 +0800",
        "Message-Id": "<1503360901-30356-2-git-send-email-yi.y.yang@intel.com>",
        "X-Mailer": "git-send-email 2.1.0",
        "In-Reply-To": "<1503360901-30356-1-git-send-email-yi.y.yang@intel.com>",
        "References": "<1503360901-30356-1-git-send-email-yi.y.yang@intel.com>",
        "Subject": "[ovs-dev] [PATCH v3 1/2] nsh: rework NSH netlink keys and actions",
        "X-BeenThere": "ovs-dev@openvswitch.org",
        "X-Mailman-Version": "2.1.12",
        "Precedence": "list",
        "List-Id": "<ovs-dev.openvswitch.org>",
        "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>",
        "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>",
        "List-Post": "<mailto:ovs-dev@openvswitch.org>",
        "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>",
        "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=subscribe>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"us-ascii\"",
        "Content-Transfer-Encoding": "7bit",
        "Sender": "ovs-dev-bounces@openvswitch.org",
        "Errors-To": "ovs-dev-bounces@openvswitch.org"
    },
    "content": "Per kernel data path requirements, this patch changes OVS_KEY_ATTR_NSH\nto nested attribute and adds three new NSH sub attribute keys:\n\n    OVS_NSH_KEY_ATTR_BASE: for length-fixed NSH base header\n    OVS_NSH_KEY_ATTR_MD1:  for length-fixed MD type 1 context\n    OVS_NSH_KEY_ATTR_MD2:  for length-variable MD type 2 metadata\n\nNSH match fields, set and PUSH_NSH action all use the below\nnested attribute format:\n\nOVS_KEY_ATTR_NSH begin\n    OVS_NSH_KEY_ATTR_BASE\n    OVS_NSH_KEY_ATTR_MD1\nOVS_KEY_ATTR_NSH end\n\nor\n\nOVS_KEY_ATTR_NSH begin\n    OVS_NSH_KEY_ATTR_BASE\n    OVS_NSH_KEY_ATTR_MD2\nOVS_KEY_ATTR_NSH end\n\nIn addition, NSH encap and decap actions are renamed as push_nsh\nand pop_nsh to meet action naming convention.\n\nSigned-off-by: Yi Yang <yi.y.yang@intel.com>\n---\n datapath/linux/compat/include/linux/openvswitch.h |  57 +-\n include/openvswitch/nsh.h                         |  32 +-\n include/openvswitch/packets.h                     |  11 +-\n lib/dpif-netdev.c                                 |   4 +-\n lib/dpif.c                                        |   4 +-\n lib/flow.c                                        |  55 +-\n lib/match.c                                       |  12 +-\n lib/meta-flow.c                                   |  13 +-\n lib/nx-match.c                                    |   4 +-\n lib/odp-execute.c                                 |  76 ++-\n lib/odp-util.c                                    | 745 ++++++++++++++++++----\n lib/odp-util.h                                    |   4 +\n lib/packets.c                                     |  23 +-\n lib/packets.h                                     |   5 +-\n ofproto/ofproto-dpif-ipfix.c                      |   4 +-\n ofproto/ofproto-dpif-sflow.c                      |   4 +-\n ofproto/ofproto-dpif-xlate.c                      |  24 +-\n tests/nsh.at                                      |  28 +-\n 18 files changed, 820 insertions(+), 285 deletions(-)",
    "diff": "diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h\nindex bc6c94b..d7f9029 100644\n--- a/datapath/linux/compat/include/linux/openvswitch.h\n+++ b/datapath/linux/compat/include/linux/openvswitch.h\n@@ -369,7 +369,7 @@ enum ovs_key_attr {\n #ifndef __KERNEL__\n \t/* Only used within userspace data path. */\n \tOVS_KEY_ATTR_PACKET_TYPE,  /* be32 packet type */\n-\tOVS_KEY_ATTR_NSH,\t   /* struct ovs_key_nsh */\n+\tOVS_KEY_ATTR_NSH,\t   /* Nested set of ovs_nsh_key_* */\n #endif\n \n \t__OVS_KEY_ATTR_MAX\n@@ -492,13 +492,27 @@ struct ovs_key_ct_labels {\n \t};\n };\n \n-struct ovs_key_nsh {\n-    __u8 flags;\n-    __u8 mdtype;\n-    __u8 np;\n-    __u8 pad;\n-    __be32 path_hdr;\n-    __be32 c[4];\n+enum ovs_nsh_key_attr {\n+\tOVS_NSH_KEY_ATTR_BASE,          /* struct ovs_nsh_key_base. */\n+\tOVS_NSH_KEY_ATTR_MD1,           /* struct ovs_nsh_key_md1. */\n+\tOVS_NSH_KEY_ATTR_MD2,           /* variable-length octets. */\n+\t__OVS_NSH_KEY_ATTR_MAX\n+};\n+\n+#define OVS_NSH_KEY_ATTR_MAX (__OVS_NSH_KEY_ATTR_MAX - 1)\n+\n+struct ovs_nsh_key_base {\n+\t__u8 flags;\n+\t__u8 mdtype;\n+\t__u8 np;\n+\t__u8 pad;\n+\t__be32 path_hdr;\n+};\n+\n+#define NSH_MD1_CONTEXT_SIZE 4\n+\n+struct ovs_nsh_key_md1 {\n+\t__be32 context[NSH_MD1_CONTEXT_SIZE];\n };\n \n /* OVS_KEY_ATTR_CT_STATE flags */\n@@ -793,24 +807,7 @@ struct ovs_action_push_eth {\n \tstruct ovs_key_ethernet addresses;\n };\n \n-#define OVS_ENCAP_NSH_MAX_MD_LEN 16\n-/*\n- * struct ovs_action_encap_nsh - %OVS_ACTION_ATTR_ENCAP_NSH\n- * @flags: NSH header flags.\n- * @mdtype: NSH metadata type.\n- * @mdlen: Length of NSH metadata in bytes.\n- * @np: NSH next_protocol: Inner packet type.\n- * @path_hdr: NSH service path id and service index.\n- * @metadata: NSH metadata for MD type 1 or 2\n- */\n-struct ovs_action_encap_nsh {\n-    uint8_t flags;\n-    uint8_t mdtype;\n-    uint8_t mdlen;\n-    uint8_t np;\n-    __be32 path_hdr;\n-    uint8_t metadata[OVS_ENCAP_NSH_MAX_MD_LEN];\n-};\n+#define OVS_PUSH_NSH_MAX_MD_LEN 248\n \n /**\n  * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT.\n@@ -887,8 +884,8 @@ enum ovs_nat_attr {\n  * @OVS_ACTION_ATTR_PUSH_ETH: Push a new outermost Ethernet header onto the\n  * packet.\n  * @OVS_ACTION_ATTR_POP_ETH: Pop the outermost Ethernet header off the packet.\n- * @OVS_ACTION_ATTR_ENCAP_NSH: encap NSH action to push NSH header.\n- * @OVS_ACTION_ATTR_DECAP_NSH: decap NSH action to remove NSH header.\n+ * @OVS_ACTION_ATTR_PUSH_NSH: push NSH header to the packet.\n+ * @OVS_ACTION_ATTR_POP_NSH: pop the outermost NSH header off the packet.\n  *\n  * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.  Not all\n  * fields within a header are modifiable, e.g. the IPv4 protocol and fragment\n@@ -930,8 +927,8 @@ enum ovs_action_attr {\n \tOVS_ACTION_ATTR_TUNNEL_POP,    /* u32 port number. */\n \tOVS_ACTION_ATTR_CLONE,         /* Nested OVS_CLONE_ATTR_*.  */\n \tOVS_ACTION_ATTR_METER,         /* u32 meter number. */\n-\tOVS_ACTION_ATTR_ENCAP_NSH,    /* struct ovs_action_encap_nsh. */\n-\tOVS_ACTION_ATTR_DECAP_NSH,    /* No argument. */\n+\tOVS_ACTION_ATTR_PUSH_NSH,      /* Nested OVS_NSH_KEY_ATTR_*. */\n+\tOVS_ACTION_ATTR_POP_NSH,       /* No argument. */\n #endif\n \t__OVS_ACTION_ATTR_MAX,\t      /* Nothing past this will be accepted\n \t\t\t\t       * from userspace. */\ndiff --git a/include/openvswitch/nsh.h b/include/openvswitch/nsh.h\nindex f4ccadc..5f2db20 100644\n--- a/include/openvswitch/nsh.h\n+++ b/include/openvswitch/nsh.h\n@@ -51,7 +51,7 @@ extern \"C\" {\n  * @nshc<1-4>: NSH Contexts.\n  */\n struct nsh_md1_ctx {\n-    ovs_16aligned_be32 c[4];\n+    ovs_16aligned_be32 context[4];\n };\n \n struct nsh_md2_tlv {\n@@ -110,10 +110,20 @@ struct nsh_hdr {\n /* NSH MD Type 1 header Length. */\n #define NSH_M_TYPE1_LEN   24\n \n+/* NSH header maximum Length. */\n+#define NSH_HDR_MAX_LEN 256\n+\n+/* NSH context headers maximum Length. */\n+#define NSH_CTX_HDRS_MAX_LEN 248\n+\n+#define NSH_MD1_CTX(nsh_hdr_ptr) (&(nsh_hdr_ptr)->md1)\n+\n+#define NSH_MD2_CTX(nsh_hdr_ptr) (&(nsh_hdr_ptr)->md2)\n+\n static inline uint16_t\n nsh_hdr_len(const struct nsh_hdr *nsh)\n {\n-    return 4 * (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT;\n+    return ((ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT) << 2;\n }\n \n static inline struct nsh_md1_ctx *\n@@ -128,6 +138,24 @@ nsh_md2_ctx(struct nsh_hdr *nsh)\n     return &nsh->md2;\n }\n \n+static inline uint8_t\n+nsh_get_ver(const struct nsh_hdr *nsh)\n+{\n+    return (ntohs(nsh->ver_flags_len) & NSH_VER_MASK) >> NSH_VER_SHIFT;\n+}\n+\n+static inline uint8_t\n+nsh_get_len(const struct nsh_hdr *nsh)\n+{\n+    return (ntohs(nsh->ver_flags_len) & NSH_LEN_MASK) >> NSH_LEN_SHIFT;\n+}\n+\n+static inline uint8_t\n+nsh_get_flags(const struct nsh_hdr *nsh)\n+{\n+    return (ntohs(nsh->ver_flags_len) & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;\n+}\n+\n #ifdef  __cplusplus\n }\n #endif\ndiff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h\nindex be91e02..5ee3099 100644\n--- a/include/openvswitch/packets.h\n+++ b/include/openvswitch/packets.h\n@@ -84,7 +84,16 @@ struct flow_nsh {\n     uint8_t np;\n     uint8_t si;\n     ovs_be32 spi;\n-    ovs_be32 c[4];\n+    ovs_be32 context[4];\n+};\n+\n+struct ovs_key_nsh {\n+    uint8_t flags;\n+    uint8_t mdtype;\n+    uint8_t np;\n+    uint8_t pad;\n+    ovs_be32 path_hdr;\n+    ovs_be32 context[4];\n };\n \n /* NSH flags */\ndiff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c\nindex e2cd931..527fa0e 100644\n--- a/lib/dpif-netdev.c\n+++ b/lib/dpif-netdev.c\n@@ -5407,8 +5407,8 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,\n     case OVS_ACTION_ATTR_PUSH_ETH:\n     case OVS_ACTION_ATTR_POP_ETH:\n     case OVS_ACTION_ATTR_CLONE:\n-    case OVS_ACTION_ATTR_ENCAP_NSH:\n-    case OVS_ACTION_ATTR_DECAP_NSH:\n+    case OVS_ACTION_ATTR_PUSH_NSH:\n+    case OVS_ACTION_ATTR_POP_NSH:\n     case __OVS_ACTION_ATTR_MAX:\n         OVS_NOT_REACHED();\n     }\ndiff --git a/lib/dpif.c b/lib/dpif.c\nindex 79b2e6c..32669ea 100644\n--- a/lib/dpif.c\n+++ b/lib/dpif.c\n@@ -1271,8 +1271,8 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,\n     case OVS_ACTION_ATTR_PUSH_ETH:\n     case OVS_ACTION_ATTR_POP_ETH:\n     case OVS_ACTION_ATTR_CLONE:\n-    case OVS_ACTION_ATTR_ENCAP_NSH:\n-    case OVS_ACTION_ATTR_DECAP_NSH:\n+    case OVS_ACTION_ATTR_PUSH_NSH:\n+    case OVS_ACTION_ATTR_POP_NSH:\n     case OVS_ACTION_ATTR_UNSPEC:\n     case __OVS_ACTION_ATTR_MAX:\n         OVS_NOT_REACHED();\ndiff --git a/lib/flow.c b/lib/flow.c\nindex b2b10aa..e137880 100644\n--- a/lib/flow.c\n+++ b/lib/flow.c\n@@ -533,50 +533,52 @@ bool\n parse_nsh(const void **datap, size_t *sizep, struct flow_nsh *key)\n {\n     const struct nsh_hdr *nsh = (const struct nsh_hdr *) *datap;\n-    uint16_t ver_flags_len;\n     uint8_t version, length, flags;\n     uint32_t path_hdr;\n \n-    /* Check if it is long enough for NSH header, doesn't support\n-     * MD type 2 yet\n-     */\n-    if (OVS_UNLIKELY(*sizep < NSH_M_TYPE1_LEN)) {\n+    if (OVS_UNLIKELY(*sizep < NSH_BASE_HDR_LEN)) {\n         return false;\n     }\n \n-    memset(key, 0, sizeof(struct flow_nsh));\n+    version = nsh_get_ver(nsh);\n+    flags = nsh_get_flags(nsh);\n \n-    ver_flags_len = ntohs(nsh->ver_flags_len);\n-    version = (ver_flags_len & NSH_VER_MASK) >> NSH_VER_SHIFT;\n-    flags = (ver_flags_len & NSH_FLAGS_MASK) >> NSH_FLAGS_SHIFT;\n-\n-    /* NSH header length is in 4 byte words. */\n-    length = ((ver_flags_len & NSH_LEN_MASK) >> NSH_LEN_SHIFT) << 2;\n+    length = nsh_hdr_len(nsh);\n \n     if (version != 0) {\n         return false;\n     }\n \n-    if (length != NSH_M_TYPE1_LEN) {\n-        return false;\n-    }\n-\n     key->flags = flags;\n     key->mdtype = nsh->md_type;\n     key->np = nsh->next_proto;\n \n+    if (OVS_UNLIKELY(*sizep < length)) {\n+        return false;\n+    }\n+\n     path_hdr = ntohl(get_16aligned_be32(&nsh->path_hdr));\n     key->si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;\n     key->spi = htonl((path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT);\n \n     switch (key->mdtype) {\n         case NSH_M_TYPE1:\n+            if (length != NSH_M_TYPE1_LEN) {\n+                return false;\n+            }\n+\n             for (size_t i = 0; i < 4; i++) {\n-                key->c[i] = get_16aligned_be32(&nsh->md1.c[i]);\n+                key->context[i] = get_16aligned_be32(&nsh->md1.context[i]);\n             }\n             break;\n         case NSH_M_TYPE2:\n-            /* Don't support MD type 2 yet, so return false */\n+            /* Don't support MD type 2 metedata parsing yet */\n+            if (length < NSH_BASE_HDR_LEN) {\n+                return false;\n+            }\n+\n+            memset(key->context, 0, sizeof(key->context));\n+            break;\n         default:\n             return false;\n     }\n@@ -879,16 +881,9 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)\n             struct flow_nsh nsh;\n \n             if (OVS_LIKELY(parse_nsh(&data, &size, &nsh))) {\n-                if (nsh.mdtype == NSH_M_TYPE1) {\n-                    miniflow_push_words(mf, nsh, &nsh,\n-                                        sizeof(struct flow_nsh) /\n-                                        sizeof(uint64_t));\n-                }\n-                else if (nsh.mdtype == NSH_M_TYPE2) {\n-                    /* parse_nsh has stopped it from arriving here for\n-                     * MD type 2, will add MD type 2 support code here later\n-                     */\n-                }\n+                miniflow_push_words(mf, nsh, &nsh,\n+                                    sizeof(struct flow_nsh) /\n+                                    sizeof(uint64_t));\n             }\n         }\n         goto out;\n@@ -1692,7 +1687,7 @@ flow_wildcards_init_for_packet(struct flow_wildcards *wc,\n         WC_MASK_FIELD(wc, nsh.np);\n         WC_MASK_FIELD(wc, nsh.spi);\n         WC_MASK_FIELD(wc, nsh.si);\n-        WC_MASK_FIELD(wc, nsh.c);\n+        WC_MASK_FIELD(wc, nsh.context);\n     } else {\n         return; /* Unknown ethertype. */\n     }\n@@ -1826,7 +1821,7 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)\n         FLOWMAP_SET(map, nsh.np);\n         FLOWMAP_SET(map, nsh.spi);\n         FLOWMAP_SET(map, nsh.si);\n-        FLOWMAP_SET(map, nsh.c);\n+        FLOWMAP_SET(map, nsh.context);\n     }\n }\n \ndiff --git a/lib/match.c b/lib/match.c\nindex 36c78eb..8952c99 100644\n--- a/lib/match.c\n+++ b/lib/match.c\n@@ -1266,10 +1266,14 @@ format_nsh_masked(struct ds *s, const struct flow *f, const struct flow *m)\n     format_be32_masked_hex(s, \"nsh_spi\", f->nsh.spi, m->nsh.spi);\n     format_uint8_masked(s, \"nsh_si\", f->nsh.si, m->nsh.si);\n     if (m->nsh.mdtype == UINT8_MAX && f->nsh.mdtype == NSH_M_TYPE1) {\n-        format_be32_masked_hex(s, \"nsh_c1\", f->nsh.c[0], m->nsh.c[0]);\n-        format_be32_masked_hex(s, \"nsh_c2\", f->nsh.c[1], m->nsh.c[1]);\n-        format_be32_masked_hex(s, \"nsh_c3\", f->nsh.c[2], m->nsh.c[2]);\n-        format_be32_masked_hex(s, \"nsh_c4\", f->nsh.c[3], m->nsh.c[3]);\n+        format_be32_masked_hex(s, \"nsh_c1\", f->nsh.context[0],\n+                               m->nsh.context[0]);\n+        format_be32_masked_hex(s, \"nsh_c2\", f->nsh.context[1],\n+                               m->nsh.context[1]);\n+        format_be32_masked_hex(s, \"nsh_c3\", f->nsh.context[2],\n+                               m->nsh.context[2]);\n+        format_be32_masked_hex(s, \"nsh_c4\", f->nsh.context[3],\n+                               m->nsh.context[3]);\n     }\n }\n \ndiff --git a/lib/meta-flow.c b/lib/meta-flow.c\nindex 64a8cf1..beeddf1 100644\n--- a/lib/meta-flow.c\n+++ b/lib/meta-flow.c\n@@ -373,7 +373,7 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)\n     case MFF_NSH_C2:\n     case MFF_NSH_C3:\n     case MFF_NSH_C4:\n-        return !wc->masks.nsh.c[mf->id - MFF_NSH_C1];\n+        return !wc->masks.nsh.context[mf->id - MFF_NSH_C1];\n \n     case MFF_N_IDS:\n     default:\n@@ -915,7 +915,7 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,\n     case MFF_NSH_C2:\n     case MFF_NSH_C3:\n     case MFF_NSH_C4:\n-        value->be32 = flow->nsh.c[mf->id - MFF_NSH_C1];\n+        value->be32 = flow->nsh.context[mf->id - MFF_NSH_C1];\n         break;\n \n     case MFF_N_IDS:\n@@ -1230,7 +1230,8 @@ mf_set_value(const struct mf_field *mf,\n     case MFF_NSH_C2:\n     case MFF_NSH_C3:\n     case MFF_NSH_C4:\n-        MATCH_SET_FIELD_BE32(match, nsh.c[mf->id - MFF_NSH_C1], value->be32);\n+        MATCH_SET_FIELD_BE32(match, nsh.context[mf->id - MFF_NSH_C1],\n+                             value->be32);\n         break;\n \n     case MFF_N_IDS:\n@@ -1621,7 +1622,7 @@ mf_set_flow_value(const struct mf_field *mf,\n     case MFF_NSH_C2:\n     case MFF_NSH_C3:\n     case MFF_NSH_C4:\n-        flow->nsh.c[mf->id - MFF_NSH_C1] = value->be32;\n+        flow->nsh.context[mf->id - MFF_NSH_C1] = value->be32;\n         break;\n \n     case MFF_N_IDS:\n@@ -2112,7 +2113,7 @@ mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)\n     case MFF_NSH_C2:\n     case MFF_NSH_C3:\n     case MFF_NSH_C4:\n-        MATCH_SET_FIELD_MASKED(match, nsh.c[mf->id - MFF_NSH_C1],\n+        MATCH_SET_FIELD_MASKED(match, nsh.context[mf->id - MFF_NSH_C1],\n                                htonl(0), htonl(0));\n         break;\n \n@@ -2372,7 +2373,7 @@ mf_set(const struct mf_field *mf,\n     case MFF_NSH_C2:\n     case MFF_NSH_C3:\n     case MFF_NSH_C4:\n-        MATCH_SET_FIELD_MASKED(match, nsh.c[mf->id - MFF_NSH_C1],\n+        MATCH_SET_FIELD_MASKED(match, nsh.context[mf->id - MFF_NSH_C1],\n                                value->be32, mask->be32);\n         break;\n \ndiff --git a/lib/nx-match.c b/lib/nx-match.c\nindex b782e8c..8f2a442 100644\n--- a/lib/nx-match.c\n+++ b/lib/nx-match.c\n@@ -1165,8 +1165,8 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,\n                 match->wc.masks.nsh.spi);\n     nxm_put_8m(&ctx, MFF_NSH_SI, oxm, flow->nsh.si, match->wc.masks.nsh.si);\n     for (int i = 0; i < 4; i++) {\n-        nxm_put_32m(&ctx, MFF_NSH_C1 + i, oxm, flow->nsh.c[i],\n-                    match->wc.masks.nsh.c[i]);\n+        nxm_put_32m(&ctx, MFF_NSH_C1 + i, oxm, flow->nsh.context[i],\n+                    match->wc.masks.nsh.context[i]);\n     }\n \n     /* Registers. */\ndiff --git a/lib/odp-execute.c b/lib/odp-execute.c\nindex 5f4d23a..f6ecc86 100644\n--- a/lib/odp-execute.c\n+++ b/lib/odp-execute.c\n@@ -273,19 +273,22 @@ odp_set_nd(struct dp_packet *packet, const struct ovs_key_nd *key,\n /* Set the NSH header. Assumes the NSH header is present and matches the\n  * MD format of the key. The slow path must take case of that. */\n static void\n-odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,\n-            const struct ovs_key_nsh *mask)\n+odp_set_nsh(struct dp_packet *packet, const struct flow_nsh *key,\n+            const struct flow_nsh *mask)\n {\n     struct nsh_hdr *nsh = dp_packet_l3(packet);\n+    ovs_be32 path_hdr;\n \n     if (!mask) {\n         nsh->ver_flags_len = htons(key->flags << NSH_FLAGS_SHIFT) |\n                              (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));\n-        put_16aligned_be32(&nsh->path_hdr, key->path_hdr);\n+        path_hdr = htonl((ntohl(key->spi) << NSH_SPI_SHIFT) |\n+                         key->si);\n+        put_16aligned_be32(&nsh->path_hdr, path_hdr);\n         switch (nsh->md_type) {\n             case NSH_M_TYPE1:\n                 for (int i = 0; i < 4; i++) {\n-                    put_16aligned_be32(&nsh->md1.c[i], key->c[i]);\n+                    put_16aligned_be32(&nsh->md1.context[i], key->context[i]);\n                 }\n                 break;\n             case NSH_M_TYPE2:\n@@ -300,16 +303,24 @@ odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,\n         nsh->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT) |\n                              (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));\n \n-        ovs_be32 path_hdr = get_16aligned_be32(&nsh->path_hdr);\n-        path_hdr = key->path_hdr | (path_hdr & ~mask->path_hdr);\n+        path_hdr = get_16aligned_be32(&nsh->path_hdr);\n+        uint32_t spi = (ntohl(path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;\n+        uint8_t si = (ntohl(path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;\n+        uint32_t spi_mask = ntohl(mask->spi);\n+        if (spi_mask == 0x00ffffff) {\n+            spi_mask = UINT32_MAX;\n+        }\n+        spi = ntohl(key->spi) | (spi & ~spi_mask);\n+        si = key->si | (si & ~mask->si);\n+        path_hdr = htonl((spi << NSH_SPI_SHIFT) | si);\n         put_16aligned_be32(&nsh->path_hdr, path_hdr);\n         switch (nsh->md_type) {\n             case NSH_M_TYPE1:\n                 for (int i = 0; i < 4; i++) {\n-                    ovs_be32 p = get_16aligned_be32(&nsh->md1.c[i]);\n-                    ovs_be32 k = key->c[i];\n-                    ovs_be32 m = mask->c[i];\n-                    put_16aligned_be32(&nsh->md1.c[i], k | (p & ~m));\n+                    ovs_be32 p = get_16aligned_be32(&nsh->md1.context[i]);\n+                    ovs_be32 k = key->context[i];\n+                    ovs_be32 m = mask->context[i];\n+                    put_16aligned_be32(&nsh->md1.context[i], k | (p & ~m));\n                 }\n                 break;\n             case NSH_M_TYPE2:\n@@ -345,9 +356,12 @@ odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)\n         odp_eth_set_addrs(packet, nl_attr_get(a), NULL);\n         break;\n \n-    case OVS_KEY_ATTR_NSH:\n-        odp_set_nsh(packet, nl_attr_get(a), NULL);\n+    case OVS_KEY_ATTR_NSH: {\n+        struct flow_nsh nsh;\n+        odp_nsh_key_from_attr(a, &nsh);\n+        odp_set_nsh(packet, &nsh, NULL);\n         break;\n+    }\n \n     case OVS_KEY_ATTR_IPV4:\n         ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));\n@@ -473,10 +487,25 @@ odp_execute_masked_set_action(struct dp_packet *packet,\n                           get_mask(a, struct ovs_key_ethernet));\n         break;\n \n-    case OVS_KEY_ATTR_NSH:\n-        odp_set_nsh(packet, nl_attr_get(a),\n-                    get_mask(a, struct ovs_key_nsh));\n+    case OVS_KEY_ATTR_NSH: {\n+        struct flow_nsh nsh, nsh_mask;\n+        size_t size = nl_attr_get_size(a) / 2;\n+        struct {\n+            struct nlattr nla;\n+            uint8_t data[size];\n+        } attr, mask;\n+\n+        mask.nla.nla_type = attr.nla.nla_type = nl_attr_type(a);\n+        mask.nla.nla_len = attr.nla.nla_len = NLA_HDRLEN + size;\n+        memcpy(attr.data, (char *)(a + 1), size);\n+        memcpy(mask.data, (char *)(a + 1) + size, size);\n+\n+        odp_nsh_key_from_attr(&attr.nla, &nsh);\n+        odp_nsh_key_from_attr(&mask.nla, &nsh_mask);\n+        odp_set_nsh(packet, &nsh, &nsh_mask);\n+\n         break;\n+    }\n \n     case OVS_KEY_ATTR_IPV4:\n         odp_set_ipv4(packet, nl_attr_get(a),\n@@ -652,8 +681,8 @@ requires_datapath_assistance(const struct nlattr *a)\n     case OVS_ACTION_ATTR_PUSH_ETH:\n     case OVS_ACTION_ATTR_POP_ETH:\n     case OVS_ACTION_ATTR_CLONE:\n-    case OVS_ACTION_ATTR_ENCAP_NSH:\n-    case OVS_ACTION_ATTR_DECAP_NSH:\n+    case OVS_ACTION_ATTR_PUSH_NSH:\n+    case OVS_ACTION_ATTR_POP_NSH:\n         return false;\n \n     case OVS_ACTION_ATTR_UNSPEC:\n@@ -818,18 +847,21 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,\n             }\n             break;\n \n-        case OVS_ACTION_ATTR_ENCAP_NSH: {\n-            const struct ovs_action_encap_nsh *enc_nsh = nl_attr_get(a);\n+        case OVS_ACTION_ATTR_PUSH_NSH: {\n+            uint8_t buffer[NSH_HDR_MAX_LEN];\n+            struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);\n+            const struct nsh_hdr *nsh_hdr_src = nsh_hdr;\n+            odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr, NSH_HDR_MAX_LEN);\n             DP_PACKET_BATCH_FOR_EACH (packet, batch) {\n-                encap_nsh(packet, enc_nsh);\n+                push_nsh(packet, nsh_hdr_src);\n             }\n             break;\n         }\n-        case OVS_ACTION_ATTR_DECAP_NSH: {\n+        case OVS_ACTION_ATTR_POP_NSH: {\n             size_t i, num = batch->count;\n \n             DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) {\n-                if (decap_nsh(packet)) {\n+                if (pop_nsh(packet)) {\n                     dp_packet_batch_refill(batch, packet, i);\n                 } else {\n                     dp_packet_delete(packet);\ndiff --git a/lib/odp-util.c b/lib/odp-util.c\nindex 4f1499e..800ef34 100644\n--- a/lib/odp-util.c\n+++ b/lib/odp-util.c\n@@ -129,8 +129,8 @@ odp_action_len(uint16_t type)\n     case OVS_ACTION_ATTR_PUSH_ETH: return sizeof(struct ovs_action_push_eth);\n     case OVS_ACTION_ATTR_POP_ETH: return 0;\n     case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;\n-    case OVS_ACTION_ATTR_ENCAP_NSH: return ATTR_LEN_VARIABLE;\n-    case OVS_ACTION_ATTR_DECAP_NSH: return 0;\n+    case OVS_ACTION_ATTR_PUSH_NSH: return ATTR_LEN_VARIABLE;\n+    case OVS_ACTION_ATTR_POP_NSH: return 0;\n \n     case OVS_ACTION_ATTR_UNSPEC:\n     case __OVS_ACTION_ATTR_MAX:\n@@ -264,7 +264,7 @@ format_nsh_key(struct ds *ds, const struct ovs_key_nsh *key)\n     switch (key->mdtype) {\n         case NSH_M_TYPE1:\n             for (int i = 0; i < 4; i++) {\n-                ds_put_format(ds, \",c%d=0x%x\", i + 1, ntohl(key->c[i]));\n+                ds_put_format(ds, \",c%d=0x%x\", i + 1, ntohl(key->context[i]));\n             }\n             break;\n         case NSH_M_TYPE2:\n@@ -334,41 +334,50 @@ format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key,\n         format_uint8_masked(ds, &first, \"np\", key->np, mask->np);\n         format_be32_masked(ds, &first, \"spi\", htonl(spi), htonl(spi_mask));\n         format_uint8_masked(ds, &first, \"si\", si, si_mask);\n-        format_be32_masked(ds, &first, \"c1\", key->c[0], mask->c[0]);\n-        format_be32_masked(ds, &first, \"c2\", key->c[1], mask->c[1]);\n-        format_be32_masked(ds, &first, \"c3\", key->c[2], mask->c[2]);\n-        format_be32_masked(ds, &first, \"c4\", key->c[3], mask->c[3]);\n+        format_be32_masked(ds, &first, \"c1\", key->context[0],\n+                           mask->context[0]);\n+        format_be32_masked(ds, &first, \"c2\", key->context[1],\n+                           mask->context[1]);\n+        format_be32_masked(ds, &first, \"c3\", key->context[2],\n+                           mask->context[2]);\n+        format_be32_masked(ds, &first, \"c4\", key->context[3],\n+                           mask->context[3]);\n     }\n }\n \n static void\n-format_odp_encap_nsh_action(struct ds *ds,\n-                            const struct ovs_action_encap_nsh *encap_nsh)\n+format_odp_push_nsh_action(struct ds *ds,\n+                           const struct nsh_hdr *nsh_hdr)\n  {\n-    uint32_t path_hdr = ntohl(encap_nsh->path_hdr);\n+    size_t mdlen = (((ntohs(nsh_hdr->ver_flags_len) & NSH_LEN_MASK)\n+                         >> NSH_LEN_SHIFT) << 2) - NSH_BASE_HDR_LEN;\n+    uint32_t path_hdr = ntohl(get_16aligned_be32(&nsh_hdr->path_hdr));\n     uint32_t spi = (path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT;\n     uint8_t si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT;\n+    uint8_t flags = (ntohs(nsh_hdr->ver_flags_len) & NSH_FLAGS_MASK)\n+                        >> NSH_FLAGS_SHIFT;\n \n-    ds_put_cstr(ds, \"encap_nsh(\");\n-    ds_put_format(ds, \"flags=%d\", encap_nsh->flags);\n-    ds_put_format(ds, \",mdtype=%d\", encap_nsh->mdtype);\n-    ds_put_format(ds, \",np=%d\", encap_nsh->np);\n+    ds_put_cstr(ds, \"push_nsh(\");\n+    ds_put_format(ds, \"flags=%d\", flags);\n+    ds_put_format(ds, \",mdtype=%d\", nsh_hdr->md_type);\n+    ds_put_format(ds, \",np=%d\", nsh_hdr->next_proto);\n     ds_put_format(ds, \",spi=0x%x\", spi);\n     ds_put_format(ds, \",si=%d\", si);\n-    switch (encap_nsh->mdtype) {\n+    switch (nsh_hdr->md_type) {\n     case NSH_M_TYPE1: {\n-        struct nsh_md1_ctx *md1_ctx =\n-            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh->metadata);\n+        const struct nsh_md1_ctx *md1_ctx = NSH_MD1_CTX(nsh_hdr);\n         for (int i = 0; i < 4; i++) {\n             ds_put_format(ds, \",c%d=0x%x\", i + 1,\n-                          ntohl(get_16aligned_be32(&md1_ctx->c[i])));\n+                          ntohl(get_16aligned_be32(&md1_ctx->context[i])));\n         }\n         break;\n     }\n-    case NSH_M_TYPE2:\n+    case NSH_M_TYPE2: {\n+        const struct nsh_md2_tlv *md2_ctx = NSH_MD2_CTX(nsh_hdr);\n         ds_put_cstr(ds, \",md2=\");\n-        ds_put_hex(ds, encap_nsh->metadata, encap_nsh->mdlen);\n+        ds_put_hex(ds, md2_ctx, mdlen);\n         break;\n+    }\n     default:\n         OVS_NOT_REACHED();\n     }\n@@ -1057,11 +1066,16 @@ format_odp_action(struct ds *ds, const struct nlattr *a,\n     case OVS_ACTION_ATTR_CLONE:\n         format_odp_clone_action(ds, a, portno_names);\n         break;\n-    case OVS_ACTION_ATTR_ENCAP_NSH:\n-        format_odp_encap_nsh_action(ds, nl_attr_get(a));\n+    case OVS_ACTION_ATTR_PUSH_NSH: {\n+        uint8_t buffer[NSH_HDR_MAX_LEN];\n+        struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);\n+        const struct nsh_hdr *nsh_hdr_src = nsh_hdr;\n+        odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr, NSH_HDR_MAX_LEN);\n+        format_odp_push_nsh_action(ds, nsh_hdr_src);\n         break;\n-    case OVS_ACTION_ATTR_DECAP_NSH:\n-        ds_put_cstr(ds, \"decap_nsh()\");\n+    }\n+    case OVS_ACTION_ATTR_POP_NSH:\n+        ds_put_cstr(ds, \"pop_nsh()\");\n         break;\n     case OVS_ACTION_ATTR_UNSPEC:\n     case __OVS_ACTION_ATTR_MAX:\n@@ -1780,27 +1794,74 @@ find_end:\n     return s - s_;\n }\n \n+static void\n+nsh_key_to_attr(struct ofpbuf *buf, const struct flow_nsh *nsh,\n+                uint8_t * metadata, size_t md_size,\n+                bool is_mask)\n+{\n+    size_t nsh_key_ofs;\n+    struct ovs_nsh_key_base base;\n+    struct ovs_nsh_key_md1 md1;\n+\n+    base.flags = nsh->flags;\n+    base.mdtype = nsh->mdtype;\n+    base.np = nsh->np;\n+    base.path_hdr = htonl((ntohl(nsh->spi) << NSH_SPI_SHIFT) |\n+                          nsh->si);\n+\n+    nsh_key_ofs = nl_msg_start_nested(buf, OVS_KEY_ATTR_NSH);\n+    nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_BASE, &base, sizeof base);\n+\n+    if (is_mask) {\n+        for (int i = 0; i < 4; i++) {\n+            md1.context[i] = nsh->context[i];\n+        }\n+        nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof md1);\n+    } else {\n+        switch (nsh->mdtype) {\n+        case NSH_M_TYPE1:\n+            for (int i = 0; i < 4; i++) {\n+                md1.context[i] = nsh->context[i];\n+            }\n+            nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD1, &md1, sizeof md1);\n+            break;\n+        case NSH_M_TYPE2:\n+            if (metadata && md_size > 0) {\n+                nl_msg_put_unspec(buf, OVS_NSH_KEY_ATTR_MD2, metadata,\n+                                  md_size);\n+            }\n+            break;\n+        default:\n+            /* No match support for other MD formats yet. */\n+            break;\n+        }\n+    }\n+    nl_msg_end_nested(buf, nsh_key_ofs);\n+}\n+\n+\n static int\n-parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)\n+parse_odp_push_nsh_action(const char *s, struct ofpbuf *actions)\n {\n     int n = 0;\n     int ret = 0;\n-    struct ovs_action_encap_nsh encap_nsh;\n-    uint32_t spi;\n-    uint8_t si;\n     uint32_t cd;\n+    struct flow_nsh nsh;\n+    uint8_t *metadata = NULL;\n+    uint8_t md_size = 0;\n \n-    if (!ovs_scan_len(s, &n, \"encap_nsh(\")) {\n+    if (!ovs_scan_len(s, &n, \"push_nsh(\")) {\n         ret = -EINVAL;\n         goto out;\n     }\n \n     /* The default is NSH_M_TYPE1 */\n-    encap_nsh.flags = 0;\n-    encap_nsh.mdtype = NSH_M_TYPE1;\n-    encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;\n-    encap_nsh.path_hdr = htonl(255);\n-    memset(encap_nsh.metadata, 0, NSH_M_TYPE1_MDLEN);\n+    nsh.flags = 0;\n+    nsh.mdtype = NSH_M_TYPE1;\n+    nsh.np = NSH_P_ETHERNET;\n+    nsh.spi = 0;\n+    nsh.si = 255;\n+    memset(nsh.context, 0, NSH_M_TYPE1_MDLEN);\n \n     for (;;) {\n         n += strspn(s + n, delimiters);\n@@ -1808,17 +1869,17 @@ parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)\n             break;\n         }\n \n-        if (ovs_scan_len(s, &n, \"flags=%\"SCNi8, &encap_nsh.flags)) {\n+        if (ovs_scan_len(s, &n, \"flags=%\"SCNi8, &nsh.flags)) {\n             continue;\n         }\n-        if (ovs_scan_len(s, &n, \"mdtype=%\"SCNi8, &encap_nsh.mdtype)) {\n-            switch (encap_nsh.mdtype) {\n+        if (ovs_scan_len(s, &n, \"mdtype=%\"SCNi8, &nsh.mdtype)) {\n+            switch (nsh.mdtype) {\n             case NSH_M_TYPE1:\n                 /* This is the default format. */;\n                 break;\n             case NSH_M_TYPE2:\n                 /* Length will be updated later. */\n-                encap_nsh.mdlen = 0;\n+                md_size = 0;\n                 break;\n             default:\n                 ret = -EINVAL;\n@@ -1826,65 +1887,60 @@ parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions)\n             }\n             continue;\n         }\n-        if (ovs_scan_len(s, &n, \"np=%\"SCNi8, &encap_nsh.np)) {\n+        if (ovs_scan_len(s, &n, \"np=%\"SCNi8, &nsh.np)) {\n             continue;\n         }\n-        if (ovs_scan_len(s, &n, \"spi=0x%\"SCNx32, &spi)) {\n-            encap_nsh.path_hdr =\n-                    htonl(((spi << NSH_SPI_SHIFT) & NSH_SPI_MASK) |\n-                            (ntohl(encap_nsh.path_hdr) & ~NSH_SPI_MASK));\n+        if (ovs_scan_len(s, &n, \"spi=0x%\"SCNx32, &nsh.spi)) {\n+            nsh.spi = htonl(nsh.spi);\n             continue;\n         }\n-        if (ovs_scan_len(s, &n, \"si=%\"SCNi8, &si)) {\n-            encap_nsh.path_hdr =\n-                    htonl((si << NSH_SI_SHIFT) |\n-                            (ntohl(encap_nsh.path_hdr) & ~NSH_SI_MASK));\n+        if (ovs_scan_len(s, &n, \"si=%\"SCNi8, &nsh.si)) {\n             continue;\n         }\n-        if (encap_nsh.mdtype == NSH_M_TYPE1) {\n-            struct nsh_md1_ctx *md1 =\n-                ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);\n+        if (nsh.mdtype == NSH_M_TYPE1) {\n             if (ovs_scan_len(s, &n, \"c1=0x%\"SCNx32, &cd)) {\n-                put_16aligned_be32(&md1->c[0], htonl(cd));\n+                nsh.context[0] = htonl(cd);\n                 continue;\n             }\n             if (ovs_scan_len(s, &n, \"c2=0x%\"SCNx32, &cd)) {\n-                put_16aligned_be32(&md1->c[1], htonl(cd));\n+                nsh.context[1] = htonl(cd);\n                 continue;\n             }\n             if (ovs_scan_len(s, &n, \"c3=0x%\"SCNx32, &cd)) {\n-                put_16aligned_be32(&md1->c[2], htonl(cd));\n+                nsh.context[2] = htonl(cd);\n                 continue;\n             }\n             if (ovs_scan_len(s, &n, \"c4=0x%\"SCNx32, &cd)) {\n-                put_16aligned_be32(&md1->c[3], htonl(cd));\n+                nsh.context[3] = htonl(cd);\n                 continue;\n             }\n         }\n-        else if (encap_nsh.mdtype == NSH_M_TYPE2) {\n+        else if (nsh.mdtype == NSH_M_TYPE2) {\n             struct ofpbuf b;\n             char buf[512];\n             size_t mdlen;\n             if (ovs_scan_len(s, &n, \"md2=0x%511[0-9a-fA-F]\", buf)) {\n-                ofpbuf_use_stub(&b, encap_nsh.metadata,\n-                                OVS_ENCAP_NSH_MAX_MD_LEN);\n+                metadata = xmalloc(NSH_CTX_HDRS_MAX_LEN);\n+                ofpbuf_use_stub(&b, metadata,\n+                                NSH_CTX_HDRS_MAX_LEN);\n                 ofpbuf_put_hex(&b, buf, &mdlen);\n-                encap_nsh.mdlen = mdlen;\n+                md_size = mdlen;\n                 ofpbuf_uninit(&b);\n             }\n             continue;\n         }\n     }\n out:\n-    if (ret < 0) {\n-        return ret;\n-    } else {\n-        size_t size = offsetof(struct ovs_action_encap_nsh, metadata)\n-                + ROUND_UP(encap_nsh.mdlen, 4);\n-        nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ENCAP_NSH,\n-                          &encap_nsh, size);\n-        return n;\n+    if (ret >= 0) {\n+        size_t offset = nl_msg_start_nested(actions, OVS_ACTION_ATTR_PUSH_NSH);\n+        nsh_key_to_attr(actions, &nsh, metadata, md_size, false);\n+        nl_msg_end_nested(actions, offset);\n+        ret = n;\n+    }\n+    if (metadata != NULL) {\n+        free(metadata);\n     }\n+    return ret;\n }\n \n static int\n@@ -2089,8 +2145,8 @@ parse_odp_action(const char *s, const struct simap *port_names,\n     }\n \n     {\n-        if (!strncmp(s, \"encap_nsh(\", 10)) {\n-            int retval = parse_odp_encap_nsh_action(s, actions);\n+        if (!strncmp(s, \"push_nsh(\", 9)) {\n+            int retval = parse_odp_push_nsh_action(s, actions);\n             if (retval < 0) {\n                 return retval;\n             }\n@@ -2100,8 +2156,8 @@ parse_odp_action(const char *s, const struct simap *port_names,\n \n     {\n         int n;\n-        if (ovs_scan(s, \"decap_nsh()%n\", &n)) {\n-            nl_msg_put_flag(actions, OVS_ACTION_ATTR_DECAP_NSH);\n+        if (ovs_scan(s, \"pop_nsh()%n\", &n)) {\n+            nl_msg_put_flag(actions, OVS_ACTION_ATTR_POP_NSH);\n             return n;\n         }\n     }\n@@ -2198,6 +2254,13 @@ static const struct attr_len_tbl ovs_tun_key_attr_lens[OVS_TUNNEL_KEY_ATTR_MAX +\n     [OVS_TUNNEL_KEY_ATTR_IPV6_DST]      = { .len = 16 },\n };\n \n+static const struct attr_len_tbl\n+ovs_nsh_key_attr_lens[OVS_NSH_KEY_ATTR_MAX + 1] = {\n+    [OVS_NSH_KEY_ATTR_BASE]     = { .len = 8 },\n+    [OVS_NSH_KEY_ATTR_MD1]      = { .len = 16 },\n+    [OVS_NSH_KEY_ATTR_MD2]      = { .len = ATTR_LEN_VARIABLE },\n+};\n+\n static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = {\n     [OVS_KEY_ATTR_ENCAP]     = { .len = ATTR_LEN_NESTED },\n     [OVS_KEY_ATTR_PRIORITY]  = { .len = 4 },\n@@ -2229,7 +2292,9 @@ static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =\n     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct ovs_key_ct_tuple_ipv4) },\n     [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct ovs_key_ct_tuple_ipv6) },\n     [OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4  },\n-    [OVS_KEY_ATTR_NSH]       = { .len = sizeof(struct ovs_key_nsh) },\n+    [OVS_KEY_ATTR_NSH]       = { .len = ATTR_LEN_NESTED,\n+                                 .next = ovs_nsh_key_attr_lens,\n+                                 .next_max = OVS_NSH_KEY_ATTR_MAX },\n };\n \n /* Returns the correct length of the payload for a flow key attribute of the\n@@ -2280,6 +2345,142 @@ ovs_frag_type_to_string(enum ovs_frag_type type)\n     }\n }\n \n+enum odp_key_fitness\n+odp_nsh_hdr_from_attr(const struct nlattr *attr,\n+                      struct nsh_hdr *nsh_hdr, size_t size)\n+{\n+    unsigned int left;\n+    const struct nlattr *a;\n+    bool unknown = false;\n+    uint8_t flags = 0;\n+    size_t mdlen = 0;\n+    bool has_md1 = false;\n+    bool has_md2 = false;\n+\n+    NL_NESTED_FOR_EACH (a, left, attr) {\n+        uint16_t type = nl_attr_type(a);\n+        size_t len = nl_attr_get_size(a);\n+        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,\n+                                            OVS_NSH_KEY_ATTR_MAX, type);\n+\n+        if (len != expected_len && expected_len >= 0) {\n+            return ODP_FIT_ERROR;\n+        }\n+\n+        switch (type) {\n+        case OVS_NSH_KEY_ATTR_BASE: {\n+            const struct ovs_nsh_key_base *base = nl_attr_get(a);\n+            nsh_hdr->next_proto = base->np;\n+            nsh_hdr->md_type = base->mdtype;\n+            put_16aligned_be32(&nsh_hdr->path_hdr, base->path_hdr);\n+            flags = base->flags;\n+            break;\n+        }\n+        case OVS_NSH_KEY_ATTR_MD1: {\n+            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);\n+            struct nsh_md1_ctx *md1_dst = nsh_md1_ctx(nsh_hdr);\n+            has_md1 = true;\n+            mdlen = nl_attr_get_size(a);\n+            if ((mdlen + NSH_BASE_HDR_LEN != NSH_M_TYPE1_LEN) ||\n+                (mdlen + NSH_BASE_HDR_LEN > size)) {\n+                return ODP_FIT_ERROR;\n+            }\n+            memcpy(md1_dst, md1, mdlen);\n+            break;\n+        }\n+        case OVS_NSH_KEY_ATTR_MD2: {\n+            struct nsh_md2_tlv *md2_dst = nsh_md2_ctx(nsh_hdr);\n+            const uint8_t *md2 = nl_attr_get(a);\n+            has_md2 = true;\n+            mdlen = nl_attr_get_size(a);\n+            if (mdlen + NSH_BASE_HDR_LEN > size) {\n+                return ODP_FIT_ERROR;\n+            }\n+            memcpy(md2_dst, md2, mdlen);\n+            break;\n+        }\n+        default:\n+            /* Allow this to show up as unexpected, if there are unknown\n+             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH. */\n+            unknown = true;\n+            break;\n+        }\n+    }\n+\n+    if (unknown) {\n+        return ODP_FIT_TOO_MUCH;\n+    }\n+\n+    if ((has_md1 && nsh_hdr->md_type != NSH_M_TYPE1)\n+        || (has_md2 && nsh_hdr->md_type != NSH_M_TYPE2)) {\n+        return ODP_FIT_ERROR;\n+    }\n+\n+    /* nsh header length  = NSH_BASE_HDR_LEN + mdlen */\n+    nsh_hdr->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT |\n+                               (NSH_BASE_HDR_LEN + mdlen) >> 2);\n+\n+    return ODP_FIT_PERFECT;\n+}\n+\n+enum odp_key_fitness\n+odp_nsh_key_from_attr(const struct nlattr *attr, struct flow_nsh *nsh)\n+{\n+    unsigned int left;\n+    const struct nlattr *a;\n+    bool unknown = false;\n+    bool has_md1 = false;\n+\n+    NL_NESTED_FOR_EACH (a, left, attr) {\n+        uint16_t type = nl_attr_type(a);\n+        size_t len = nl_attr_get_size(a);\n+        int expected_len = odp_key_attr_len(ovs_nsh_key_attr_lens,\n+                                            OVS_NSH_KEY_ATTR_MAX, type);\n+\n+        if (len != expected_len && expected_len >= 0) {\n+            return ODP_FIT_ERROR;\n+        }\n+\n+        switch (type) {\n+        case OVS_NSH_KEY_ATTR_BASE: {\n+            const struct ovs_nsh_key_base *base = nl_attr_get(a);\n+            nsh->flags = base->flags;\n+            nsh->mdtype = base->mdtype;\n+            nsh->np = base->np;\n+            nsh->spi = htonl((ntohl(base->path_hdr) & NSH_SPI_MASK) >>\n+                                 NSH_SPI_SHIFT);\n+            nsh->si = (ntohl(base->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;\n+            break;\n+        }\n+        case OVS_NSH_KEY_ATTR_MD1: {\n+            const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);\n+            has_md1 = true;\n+            nsh->context[0] = md1->context[0];\n+            nsh->context[1] = md1->context[1];\n+            nsh->context[2] = md1->context[2];\n+            nsh->context[3] = md1->context[3];\n+            break;\n+        }\n+        case OVS_NSH_KEY_ATTR_MD2:\n+        default:\n+            /* Allow this to show up as unexpected, if there are unknown\n+             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH. */\n+            unknown = true;\n+            break;\n+        }\n+    }\n+\n+    if (unknown) {\n+        return ODP_FIT_TOO_MUCH;\n+    }\n+\n+    if (has_md1 && nsh->mdtype != NSH_M_TYPE1) {\n+        return ODP_FIT_ERROR;\n+    }\n+\n+    return ODP_FIT_PERFECT;\n+}\n+\n static enum odp_key_fitness\n odp_tun_key_from_attr__(const struct nlattr *attr, bool is_mask,\n                         struct flow_tnl *tun)\n@@ -2971,6 +3172,80 @@ format_odp_tun_geneve(const struct nlattr *attr,\n }\n \n static void\n+format_odp_nsh_attr(const struct nlattr *attr, const struct nlattr *mask_attr,\n+                    struct ds *ds)\n+{\n+    unsigned int left;\n+    const struct nlattr *a;\n+    struct ovs_key_nsh nsh;\n+    struct ovs_key_nsh nsh_mask;\n+\n+    memset(&nsh, 0, sizeof nsh);\n+    memset(&nsh_mask, 0xff, sizeof nsh_mask);\n+\n+    NL_NESTED_FOR_EACH (a, left, attr) {\n+        enum ovs_nsh_key_attr type = nl_attr_type(a);\n+        const struct nlattr *ma = NULL;\n+\n+        if (mask_attr) {\n+            ma = nl_attr_find__(nl_attr_get(mask_attr),\n+                                nl_attr_get_size(mask_attr), type);\n+        }\n+\n+        if (!check_attr_len(ds, a, ma, ovs_nsh_key_attr_lens,\n+                            OVS_NSH_KEY_ATTR_MAX, true)) {\n+            continue;\n+        }\n+\n+        switch (type) {\n+        case OVS_NSH_KEY_ATTR_BASE: {\n+            const struct ovs_nsh_key_base * base = nl_attr_get(a);\n+            const struct ovs_nsh_key_base * base_mask\n+                = ma ? nl_attr_get(ma) : NULL;\n+            nsh.flags = base->flags;\n+            nsh.mdtype = base->mdtype;\n+            nsh.np = base->np;\n+            nsh.path_hdr = base->path_hdr;\n+            if (base_mask) {\n+                nsh_mask.flags = base_mask->flags;\n+                nsh_mask.mdtype = base_mask->mdtype;\n+                nsh_mask.np = base_mask->np;\n+                nsh_mask.path_hdr = base_mask->path_hdr;\n+            }\n+            break;\n+        }\n+        case OVS_NSH_KEY_ATTR_MD1: {\n+            const struct ovs_nsh_key_md1 * md1 = nl_attr_get(a);\n+            const struct ovs_nsh_key_md1 * md1_mask\n+                = ma ? nl_attr_get(ma) : NULL;\n+            nsh.context[0] = md1->context[0];\n+            nsh.context[1] = md1->context[1];\n+            nsh.context[2] = md1->context[2];\n+            nsh.context[3] = md1->context[3];\n+            if (md1_mask) {\n+                nsh_mask.context[0] = md1_mask->context[0];\n+                nsh_mask.context[1] = md1_mask->context[1];\n+                nsh_mask.context[2] = md1_mask->context[2];\n+                nsh_mask.context[3] = md1_mask->context[3];\n+            }\n+            break;\n+        }\n+        case OVS_NSH_KEY_ATTR_MD2:\n+        case __OVS_NSH_KEY_ATTR_MAX:\n+        default:\n+            /* No support for matching other metadata formats yet. */\n+            break;\n+        }\n+    }\n+\n+    if (mask_attr) {\n+        format_nsh_key_mask(ds, &nsh, &nsh_mask);\n+    } else {\n+        format_nsh_key(ds, &nsh);\n+    }\n+}\n+\n+static void\n format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr,\n                     struct ds *ds, bool verbose)\n {\n@@ -3448,9 +3723,7 @@ format_odp_key_attr__(const struct nlattr *a, const struct nlattr *ma,\n         break;\n     }\n     case OVS_KEY_ATTR_NSH: {\n-        const struct ovs_key_nsh *mask = ma ? nl_attr_get(ma) : NULL;\n-        const struct ovs_key_nsh *key = nl_attr_get(a);\n-        format_nsh_key_mask(ds, key, mask);\n+        format_odp_nsh_attr(a, ma, ds);\n         break;\n     }\n     case OVS_KEY_ATTR_UNSPEC:\n@@ -4549,6 +4822,129 @@ geneve_to_attr(struct ofpbuf *a, const void *data_)\n     } SCAN_END_SINGLE(ATTR)\n \n static int\n+parse_odp_nsh_key_mask_attr(const char *s, struct ofpbuf *key,\n+                            struct ofpbuf *mask)\n+{\n+    if (strncmp(s, \"nsh(\", 4) == 0) {\n+        const char *start = s;\n+        int len;\n+        struct flow_nsh skey, smask;\n+\n+        s += 4;\n+\n+        memset(&skey, 0, sizeof skey);\n+        memset(&smask, 0, sizeof smask);\n+        do {\n+            len = 0;\n+\n+            if (strncmp(s, \"flags=\", 6) == 0) {\n+                s += 6;\n+                len = scan_u8(s, &skey.flags, mask ? &smask.flags : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"mdtype=\", 7) == 0) {\n+                s += 7;\n+                len = scan_u8(s, &skey.mdtype, mask ? &smask.mdtype : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"np=\", 3) == 0) {\n+                s += 3;\n+                len = scan_u8(s, &skey.np, mask ? &smask.np : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"spi=\", 4) == 0) {\n+                s += 4;\n+                len = scan_be32(s, &skey.spi, mask ? &smask.spi : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"si=\", 3) == 0) {\n+                s += 3;\n+                len = scan_u8(s, &skey.si, mask ? &smask.si : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"c1=\", 3) == 0) {\n+                s += 3;\n+                len = scan_be32(s, &skey.context[0],\n+                                mask ? &smask.context[0] : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"c2=\", 3) == 0) {\n+                s += 3;\n+                len = scan_be32(s, &skey.context[1],\n+                                mask ? &smask.context[1] : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"c3=\", 3) == 0) {\n+                s += 3;\n+                len = scan_be32(s, &skey.context[2],\n+                                mask ? &smask.context[2] : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+\n+            if (strncmp(s, \"c4=\", 3) == 0) {\n+                s += 3;\n+                len = scan_be32(s, &skey.context[3],\n+                                mask ? &smask.context[3] : NULL);\n+                if (len == 0) {\n+                    return -EINVAL;\n+                }\n+                s += len;\n+                continue;\n+            }\n+        } while (*s++ == ',' && len != 0);\n+        if (s[-1] != ')') {\n+            return -EINVAL;\n+        }\n+\n+        nsh_key_to_attr(key, &skey, NULL, 0, false);\n+        if (mask) {\n+            nsh_key_to_attr(mask, &smask, NULL, 0, true);\n+        }\n+        return s - start;\n+    }\n+    return 0;\n+}\n+\n+static int\n parse_odp_key_mask_attr(const char *s, const struct simap *port_names,\n                         struct ofpbuf *key, struct ofpbuf *mask)\n {\n@@ -4694,16 +5090,13 @@ parse_odp_key_mask_attr(const char *s, const struct simap *port_names,\n         SCAN_FIELD(\"id=\", be16, id);\n     } SCAN_END(OVS_KEY_ATTR_PACKET_TYPE);\n \n-    SCAN_BEGIN(\"nsh(\", struct ovs_key_nsh) {\n-        SCAN_FIELD(\"flags=\", u8, flags);\n-        SCAN_FIELD(\"mdtype=\", u8, mdtype);\n-        SCAN_FIELD(\"np=\", u8, np);\n-        SCAN_FIELD(\"path_hdr=\", be32, path_hdr);\n-        SCAN_FIELD(\"c1=\", be32, c[0]);\n-        SCAN_FIELD(\"c2=\", be32, c[1]);\n-        SCAN_FIELD(\"c3=\", be32, c[2]);\n-        SCAN_FIELD(\"c4=\", be32, c[2]);\n-    } SCAN_END(OVS_KEY_ATTR_NSH);\n+    /* nsh is nested, it needs special process */\n+    int ret = parse_odp_nsh_key_mask_attr(s, key, mask);\n+    if (ret < 0) {\n+       return ret;\n+    } else {\n+       s += ret;\n+    }\n \n     /* Encap open-coded. */\n     if (!strncmp(s, \"encap(\", 6)) {\n@@ -4994,11 +5387,7 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,\n             mpls_key[i].mpls_lse = data->mpls_lse[i];\n         }\n     } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {\n-        struct ovs_key_nsh *nsh_key;\n-\n-        nsh_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_NSH,\n-                                            sizeof *nsh_key);\n-        get_nsh_key(data, nsh_key, export_mask);\n+        nsh_key_to_attr(buf, &data->nsh, NULL, 0, export_mask);\n     }\n \n     if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {\n@@ -5558,13 +5947,10 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],\n             expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_NSH;\n         }\n         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_NSH)) {\n-            const struct ovs_key_nsh *nsh_key;\n-\n-            nsh_key = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);\n-            put_nsh_key(nsh_key, flow, false);\n+            odp_nsh_key_from_attr(attrs[OVS_KEY_ATTR_NSH], &flow->nsh);\n             if (is_mask) {\n-                check_start = nsh_key;\n-                check_len = sizeof *nsh_key;\n+                check_start = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);\n+                check_len = nl_attr_get_size(attrs[OVS_KEY_ATTR_NSH]);\n                 expected_bit = OVS_KEY_ATTR_NSH;\n             }\n         }\n@@ -6620,13 +7006,13 @@ get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh, bool is_mask)\n                           flow->nsh.si);\n     if (is_mask) {\n         for (int i = 0; i < 4; i++) {\n-            nsh->c[i] = flow->nsh.c[i];\n+            nsh->context[i] = flow->nsh.context[i];\n         }\n     } else {\n         switch (nsh->mdtype) {\n         case NSH_M_TYPE1:\n             for (int i = 0; i < 4; i++) {\n-                nsh->c[i] = flow->nsh.c[i];\n+                nsh->context[i] = flow->nsh.context[i];\n             }\n             break;\n         case NSH_M_TYPE2:\n@@ -6650,15 +7036,123 @@ put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow,\n     switch (nsh->mdtype) {\n         case NSH_M_TYPE1:\n             for (int i = 0; i < 4; i++) {\n-                flow->nsh.c[i] = nsh->c[i];\n+                flow->nsh.context[i] = nsh->context[i];\n+            }\n+            break;\n+        case NSH_M_TYPE2:\n+        default:\n+            /* No match support for other MD formats yet. */\n+            memset(flow->nsh.context, 0, sizeof flow->nsh.context);\n+            break;\n+    }\n+}\n+\n+static bool\n+commit_nsh(const struct flow_nsh * flow_nsh, bool use_masked_set,\n+           const struct ovs_key_nsh *key, struct ovs_key_nsh *base,\n+           struct ovs_key_nsh *mask, size_t size,\n+           struct ofpbuf *odp_actions)\n+{\n+    enum ovs_key_attr attr = OVS_KEY_ATTR_NSH;\n+\n+    if (memcmp(key, base, size)  == 0) {\n+        /* Mask bits are set when we have either read or set the corresponding\n+         * values.  Masked bits will be exact-matched, no need to set them\n+         * if the value did not actually change. */\n+        return false;\n+    }\n+\n+    bool fully_masked = odp_mask_is_exact(attr, mask, size);\n+\n+    if (use_masked_set && !fully_masked) {\n+        size_t nsh_key_ofs;\n+        struct ovs_nsh_key_base nsh_base;\n+        struct ovs_nsh_key_base nsh_base_mask;\n+        struct ovs_nsh_key_md1 md1;\n+        struct ovs_nsh_key_md1 md1_mask;\n+        size_t offset = nl_msg_start_nested(odp_actions,\n+                                            OVS_ACTION_ATTR_SET_MASKED);\n+\n+        nsh_base.flags = key->flags;\n+        nsh_base.mdtype = key->mdtype;\n+        nsh_base.np = key->np;\n+        nsh_base.path_hdr = key->path_hdr;\n+\n+        nsh_base_mask.flags = mask->flags;\n+        nsh_base_mask.mdtype = mask->mdtype;\n+        nsh_base_mask.np = mask->np;\n+        nsh_base_mask.path_hdr = mask->path_hdr;\n+\n+        /* OVS_KEY_ATTR_NSH keys */\n+        nsh_key_ofs = nl_msg_start_nested(odp_actions, OVS_KEY_ATTR_NSH);\n+\n+        char *data = nl_msg_put_unspec_uninit(odp_actions,\n+                                              OVS_NSH_KEY_ATTR_BASE,\n+                                              sizeof(nsh_base));\n+        const char *lkey = (char *)&nsh_base, *lmask = (char *)&nsh_base_mask;\n+        size_t lkey_size = sizeof(nsh_base);\n+\n+        while (lkey_size--) {\n+            *data++ = *lkey++ & *lmask++;\n+        }\n+\n+        switch (key->mdtype) {\n+        case NSH_M_TYPE1:\n+            for (int i = 0; i < 4; i++) {\n+                md1.context[i] = key->context[i];\n+                md1_mask.context[i] = mask->context[i];\n+            }\n+            data = nl_msg_put_unspec_uninit(odp_actions,\n+                                            OVS_NSH_KEY_ATTR_MD1,\n+                                            sizeof(md1));\n+            lkey = (char *)&md1;\n+            lmask = (char *)&md1_mask;\n+            lkey_size = sizeof(md1);\n+\n+            while (lkey_size--) {\n+                *data++ = *lkey++ & *lmask++;\n             }\n             break;\n         case NSH_M_TYPE2:\n         default:\n             /* No match support for other MD formats yet. */\n-            memset(flow->nsh.c, 0, sizeof flow->nsh.c);\n             break;\n+        }\n+\n+        /* OVS_KEY_ATTR_NSH masks */\n+        data = nl_msg_put_unspec_uninit(odp_actions,\n+                                        OVS_NSH_KEY_ATTR_BASE,\n+                                        sizeof(nsh_base_mask));\n+        lmask = (char *)&nsh_base_mask;\n+\n+        memcpy(data, lmask, sizeof(nsh_base_mask));\n+\n+        switch (key->mdtype) {\n+        case NSH_M_TYPE1:\n+            data = nl_msg_put_unspec_uninit(odp_actions,\n+                                            OVS_NSH_KEY_ATTR_MD1,\n+                                            sizeof(md1_mask));\n+            lmask = (char *)&md1_mask;\n+            memcpy(data, lmask, sizeof(md1_mask));\n+            break;\n+        case NSH_M_TYPE2:\n+        default:\n+            /* No match support for other MD formats yet. */\n+            break;\n+        }\n+        nl_msg_end_nested(odp_actions, nsh_key_ofs);\n+\n+        nl_msg_end_nested(odp_actions, offset);\n+    } else {\n+        if (!fully_masked) {\n+            memset(mask, 0xff, size);\n+        }\n+        size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);\n+        nsh_key_to_attr(odp_actions, flow_nsh, NULL, 0, false);\n+        nl_msg_end_nested(odp_actions, offset);\n     }\n+    memcpy(base, key, size);\n+    return true;\n }\n \n static void\n@@ -6684,8 +7178,8 @@ commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,\n     mask.mdtype = 0;     /* Not writable. */\n     mask.np = 0;         /* Not writable. */\n \n-    if (commit(OVS_KEY_ATTR_NSH, use_masked, &key, &base, &mask, sizeof key,\n-               odp_actions)) {\n+    if (commit_nsh(&base_flow->nsh, use_masked, &key, &base, &mask,\n+            sizeof key, odp_actions)) {\n         put_nsh_key(&base, base_flow, false);\n         if (mask.mdtype != 0) { /* Mask was changed by commit(). */\n             put_nsh_key(&mask, &wc->masks, true);\n@@ -6788,49 +7282,36 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,\n }\n \n static void\n-odp_put_decap_nsh_action(struct ofpbuf *odp_actions)\n+odp_put_pop_nsh_action(struct ofpbuf *odp_actions)\n {\n-    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_DECAP_NSH);\n+    nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_NSH);\n }\n \n static void\n-odp_put_encap_nsh_action(struct ofpbuf *odp_actions,\n+odp_put_push_nsh_action(struct ofpbuf *odp_actions,\n                          const struct flow *flow,\n                          struct ofpbuf *encap_data)\n {\n-    struct ovs_action_encap_nsh encap_nsh;\n-\n-    encap_nsh.flags = flow->nsh.flags;\n-    encap_nsh.mdtype = flow->nsh.mdtype;\n-    encap_nsh.np = flow->nsh.np;\n-    encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) |\n-                                   flow->nsh.si);\n+    uint8_t * metadata = NULL;\n+    uint8_t md_size = 0;\n \n-    switch (encap_nsh.mdtype) {\n-    case NSH_M_TYPE1: {\n-        struct nsh_md1_ctx *md1 =\n-            ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata);\n-        encap_nsh.mdlen = NSH_M_TYPE1_MDLEN;\n-        for (int i = 0; i < 4; i++) {\n-            put_16aligned_be32(&md1->c[i], flow->nsh.c[i]);\n-        }\n-        break;\n-    }\n+    switch (flow->nsh.mdtype) {\n     case NSH_M_TYPE2:\n         if (encap_data) {\n-            ovs_assert(encap_data->size < OVS_ENCAP_NSH_MAX_MD_LEN);\n-            encap_nsh.mdlen = encap_data->size;\n-            memcpy(encap_nsh.metadata, encap_data->data, encap_data->size);\n+            ovs_assert(encap_data->size < OVS_PUSH_NSH_MAX_MD_LEN);\n+            metadata = encap_data->data;\n+            md_size = encap_data->size;\n         } else {\n-            encap_nsh.mdlen = 0;\n+            md_size = 0;\n         }\n         break;\n     default:\n-        encap_nsh.mdlen = 0;\n+        md_size = 0;\n         break;\n     }\n-    nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_ENCAP_NSH,\n-                      &encap_nsh, sizeof(encap_nsh));\n+    size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_PUSH_NSH);\n+    nsh_key_to_attr(odp_actions, &flow->nsh, metadata, md_size, false);\n+    nl_msg_end_nested(odp_actions, offset);\n }\n \n static void\n@@ -6857,8 +7338,8 @@ commit_packet_type_change(const struct flow *flow,\n             break;\n         }\n         case PT_NSH:\n-            /* encap_nsh */\n-            odp_put_encap_nsh_action(odp_actions, flow, encap_data);\n+            /* push_nsh */\n+            odp_put_push_nsh_action(odp_actions, flow, encap_data);\n             base_flow->packet_type = flow->packet_type;\n             /* Update all packet headers in base_flow. */\n             memcpy(&base_flow->dl_dst, &flow->dl_dst,\n@@ -6883,8 +7364,8 @@ commit_packet_type_change(const struct flow *flow,\n              * No need to update the base flow here. */\n             switch (ntohl(base_flow->packet_type)) {\n             case PT_NSH:\n-                /* decap_nsh. */\n-                odp_put_decap_nsh_action(odp_actions);\n+                /* pop_nsh. */\n+                odp_put_pop_nsh_action(odp_actions);\n                 break;\n             default:\n                 /* Checks are done during translation. */\ndiff --git a/lib/odp-util.h b/lib/odp-util.h\nindex 27c2ab4..10693cb 100644\n--- a/lib/odp-util.h\n+++ b/lib/odp-util.h\n@@ -158,6 +158,10 @@ struct odputil_keybuf {\n \n enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,\n                                            struct flow_tnl *);\n+enum odp_key_fitness odp_nsh_key_from_attr(const struct nlattr *,\n+                                           struct flow_nsh *);\n+enum odp_key_fitness odp_nsh_hdr_from_attr(const struct nlattr *,\n+                                           struct nsh_hdr *, size_t size);\n \n int odp_ufid_from_string(const char *s_, ovs_u128 *ufid);\n void odp_format_ufid(const ovs_u128 *ufid, struct ds *);\ndiff --git a/lib/packets.c b/lib/packets.c\nindex 74d87ed..db98ab3 100644\n--- a/lib/packets.c\n+++ b/lib/packets.c\n@@ -403,10 +403,10 @@ pop_mpls(struct dp_packet *packet, ovs_be16 ethtype)\n }\n \n void\n-encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)\n+push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src)\n {\n     struct nsh_hdr *nsh;\n-    size_t length = NSH_BASE_HDR_LEN + encap->mdlen;\n+    size_t length = nsh_hdr_len(nsh_hdr_src);\n     uint8_t next_proto;\n \n     switch (ntohl(packet->packet_type)) {\n@@ -427,23 +427,8 @@ encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)\n     }\n \n     nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length);\n-    nsh->ver_flags_len = htons(encap->flags << NSH_FLAGS_SHIFT | length >> 2);\n+    memcpy(nsh, nsh_hdr_src, length);\n     nsh->next_proto = next_proto;\n-    put_16aligned_be32(&nsh->path_hdr, encap->path_hdr);\n-    nsh->md_type = encap->mdtype;\n-    switch (nsh->md_type) {\n-        case NSH_M_TYPE1:\n-            nsh->md1 = *ALIGNED_CAST(struct nsh_md1_ctx *, encap->metadata);\n-            break;\n-        case NSH_M_TYPE2: {\n-            /* The MD2 metadata in encap is already padded to 4 bytes. */\n-            size_t len = ROUND_UP(encap->mdlen, 4);\n-            memcpy(&nsh->md2, encap->metadata, len);\n-            break;\n-        }\n-        default:\n-            OVS_NOT_REACHED();\n-    }\n \n     packet->packet_type = htonl(PT_NSH);\n     dp_packet_reset_offsets(packet);\n@@ -451,7 +436,7 @@ encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap)\n }\n \n bool\n-decap_nsh(struct dp_packet *packet)\n+pop_nsh(struct dp_packet *packet)\n {\n     struct nsh_hdr *nsh = (struct nsh_hdr *) dp_packet_l3(packet);\n     size_t length;\ndiff --git a/lib/packets.h b/lib/packets.h\nindex 705d0b2..e7832ba 100644\n--- a/lib/packets.h\n+++ b/lib/packets.h\n@@ -434,9 +434,8 @@ void push_eth(struct dp_packet *packet, const struct eth_addr *dst,\n               const struct eth_addr *src);\n void pop_eth(struct dp_packet *packet);\n \n-void encap_nsh(struct dp_packet *packet,\n-               const struct ovs_action_encap_nsh *encap_nsh);\n-bool decap_nsh(struct dp_packet *packet);\n+void push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src);\n+bool pop_nsh(struct dp_packet *packet);\n \n #define LLC_DSAP_SNAP 0xaa\n #define LLC_SSAP_SNAP 0xaa\ndiff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c\nindex 472c272..16976c4 100644\n--- a/ofproto/ofproto-dpif-ipfix.c\n+++ b/ofproto/ofproto-dpif-ipfix.c\n@@ -2823,8 +2823,8 @@ dpif_ipfix_read_actions(const struct flow *flow,\n         case OVS_ACTION_ATTR_POP_MPLS:\n         case OVS_ACTION_ATTR_PUSH_ETH:\n         case OVS_ACTION_ATTR_POP_ETH:\n-        case OVS_ACTION_ATTR_ENCAP_NSH:\n-        case OVS_ACTION_ATTR_DECAP_NSH:\n+        case OVS_ACTION_ATTR_PUSH_NSH:\n+        case OVS_ACTION_ATTR_POP_NSH:\n         case OVS_ACTION_ATTR_UNSPEC:\n         case __OVS_ACTION_ATTR_MAX:\n         default:\ndiff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c\nindex 65a2003..1af1569 100644\n--- a/ofproto/ofproto-dpif-sflow.c\n+++ b/ofproto/ofproto-dpif-sflow.c\n@@ -1199,8 +1199,8 @@ dpif_sflow_read_actions(const struct flow *flow,\n \t    break;\n \tcase OVS_ACTION_ATTR_SAMPLE:\n \tcase OVS_ACTION_ATTR_CLONE:\n-        case OVS_ACTION_ATTR_ENCAP_NSH:\n-        case OVS_ACTION_ATTR_DECAP_NSH:\n+        case OVS_ACTION_ATTR_PUSH_NSH:\n+        case OVS_ACTION_ATTR_POP_NSH:\n \tcase OVS_ACTION_ATTR_UNSPEC:\n \tcase __OVS_ACTION_ATTR_MAX:\n \tdefault:\ndiff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c\nindex 9e1f837..4e4d6a6 100644\n--- a/ofproto/ofproto-dpif-xlate.c\n+++ b/ofproto/ofproto-dpif-xlate.c\n@@ -4392,8 +4392,8 @@ xlate_fixup_actions(struct ofpbuf *b, const struct nlattr *actions,\n         case OVS_ACTION_ATTR_CT:\n         case OVS_ACTION_ATTR_PUSH_ETH:\n         case OVS_ACTION_ATTR_POP_ETH:\n-        case OVS_ACTION_ATTR_ENCAP_NSH:\n-        case OVS_ACTION_ATTR_DECAP_NSH:\n+        case OVS_ACTION_ATTR_PUSH_NSH:\n+        case OVS_ACTION_ATTR_POP_NSH:\n         case OVS_ACTION_ATTR_METER:\n             ofpbuf_put(b, a, nl_attr_len_pad(a, left));\n             break;\n@@ -5797,17 +5797,17 @@ rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,\n \n /* For an MD2 NSH header returns a pointer to an ofpbuf with the encoded\n  * MD2 TLVs provided as encap properties to the encap operation. This\n- * will be stored as encap_data in the ctx and copied into the encap_nsh\n+ * will be stored as encap_data in the ctx and copied into the push_nsh\n  * action at the next commit. */\n static struct ofpbuf *\n-rewrite_flow_encap_nsh(struct xlate_ctx *ctx,\n-                       const struct ofpact_encap *encap,\n-                       struct flow *flow,\n-                       struct flow_wildcards *wc)\n+rewrite_flow_push_nsh(struct xlate_ctx *ctx,\n+                      const struct ofpact_encap *encap,\n+                      struct flow *flow,\n+                      struct flow_wildcards *wc)\n {\n     ovs_be32 packet_type = flow->packet_type;\n     const char *ptr = (char *) encap->props;\n-    struct ofpbuf *buf = ofpbuf_new(OVS_ENCAP_NSH_MAX_MD_LEN);\n+    struct ofpbuf *buf = ofpbuf_new(NSH_CTX_HDRS_MAX_LEN);\n     uint8_t md_type = NSH_M_TYPE1;\n     uint8_t np = 0;\n     int i;\n@@ -5847,7 +5847,7 @@ rewrite_flow_encap_nsh(struct xlate_ctx *ctx,\n         }\n         ptr += ROUND_UP(prop_ptr->len, 8);\n     }\n-    if (buf->size == 0 || buf->size > OVS_ENCAP_NSH_MAX_MD_LEN) {\n+    if (buf->size == 0 || buf->size > NSH_CTX_HDRS_MAX_LEN) {\n         ofpbuf_delete(buf);\n         buf = NULL;\n     }\n@@ -5892,7 +5892,7 @@ rewrite_flow_encap_nsh(struct xlate_ctx *ctx,\n \n     if (md_type == NSH_M_TYPE1) {\n         flow->nsh.mdtype = NSH_M_TYPE1;\n-        memset(flow->nsh.c, 0, sizeof flow->nsh.c);\n+        memset(flow->nsh.context, 0, sizeof flow->nsh.context);\n         if (buf) {\n             /* Drop any MD2 context TLVs. */\n             ofpbuf_delete(buf);\n@@ -5923,7 +5923,7 @@ xlate_generic_encap_action(struct xlate_ctx *ctx,\n             rewrite_flow_encap_ethernet(ctx, flow, wc);\n             break;\n         case PT_NSH:\n-            encap_data = rewrite_flow_encap_nsh(ctx, encap, flow, wc);\n+            encap_data = rewrite_flow_push_nsh(ctx, encap, flow, wc);\n             break;\n         default:\n             /* New packet type was checked during decoding. */\n@@ -5966,7 +5966,7 @@ xlate_generic_decap_action(struct xlate_ctx *ctx,\n             }\n             return false;\n         case PT_NSH:\n-            /* The decap_nsh action is generated at the commit executed as\n+            /* The pop_nsh action is generated at the commit executed as\n              * part of freezing the ctx for recirculation. Here we just set\n              * the new packet type based on the NSH next protocol field. */\n             switch (flow->nsh.np) {\ndiff --git a/tests/nsh.at b/tests/nsh.at\nindex aa80a2a..562f3da 100644\n--- a/tests/nsh.at\n+++ b/tests/nsh.at\n@@ -105,7 +105,7 @@ bridge(\"br0\")\n \n Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_si=255,nsh_c1=0x11223344,nsh_c2=0x0,nsh_c3=0x0,nsh_c4=0x0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0\n Megaflow: recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no\n-Datapath actions: encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)\n+Datapath actions: push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)\n ])\n \n AT_CHECK([\n@@ -121,7 +121,7 @@ bridge(\"br0\")\n \n Final flow: unchanged\n Megaflow: recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=1,nsh_np=3,nsh_spi=0x1234,nsh_c1=0x11223344\n-Datapath actions: pop_eth,decap_nsh(),recirc(0x2)\n+Datapath actions: pop_eth,pop_nsh(),recirc(0x2)\n ])\n \n # Now send two real ICMP echo request packets in on port p1\n@@ -139,7 +139,7 @@ ovs-appctl time/warp 1000\n AT_CHECK([\n     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort\n ], [0], [flow-dump from non-dpdk interfaces:\n-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:encap_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)\n+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_nsh(flags=0,mdtype=1,np=3,spi=0x1234,si=255,c1=0x11223344,c2=0x0,c3=0x0,c4=0x0),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)\n recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:2\n ])\n \n@@ -170,7 +170,7 @@ ovs-appctl time/warp 1000\n AT_CHECK([\n     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort\n ], [0], [flow-dump from non-dpdk interfaces:\n-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_vlan(vid=100,pcp=0),encap_nsh(flags=0,mdtype=1,np=3,spi=0x0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),decap_nsh(),recirc(0x4)\n+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_vlan(vid=100,pcp=0),push_nsh(flags=0,mdtype=1,np=3,spi=0x0,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),pop_nsh(),recirc(0x4)\n recirc_id(0x4),in_port(1),packet_type(ns=0,id=0),eth_type(0x8100),vlan(vid=100,pcp=0),encap(eth_type(0x0800),ipv4(frag=no)), packets:1, bytes:102, used:0.0s, actions:2\n ])\n \n@@ -195,7 +195,7 @@ ovs-vsctl set bridge br0 datapath_type=dummy \\\n         add-port br0 v4 -- set Interface v4 type=patch options:peer=v3 ofport_request=4])\n \n AT_DATA([flows.txt], [dnl\n-    table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3\n+    table=0,in_port=1,ip,actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9876543210))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->dl_dst,3\n     table=0,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234,actions=decap(),decap(),2\n ])\n \n@@ -205,7 +205,7 @@ AT_CHECK([\n     ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep actions\n ], [0], [dnl\n  in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_spi=0x1234 actions=decap(),decap(),output:2\n- ip,in_port=1 actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3\n+ ip,in_port=1 actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9876543210))),set_field:0x1234->nsh_spi,encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,output:3\n ])\n \n AT_CHECK([\n@@ -216,7 +216,7 @@ Flow: icmp,in_port=1,vlan_tci=0x0000,dl_src=00:11:22:33:44:55,dl_dst=66:77:88:99\n bridge(\"br0\")\n -------------\n  0. ip,in_port=1, priority 32768\n-    encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))\n+    encap(nsh(md_type=2,tlv(0x1000,10,0x12345678),tlv(0x2000,20,0xfedcba9876543210)))\n     set_field:0x1234->nsh_spi\n     encap(ethernet)\n     set_field:11:22:33:44:55:66->eth_dst\n@@ -230,7 +230,7 @@ bridge(\"br0\")\n \n Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=11:22:33:44:55:66,dl_type=0x894f,nsh_flags=0,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234,nsh_si=255,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0\n Megaflow: recirc_id=0,eth,ip,in_port=1,dl_dst=66:77:88:99:aa:bb,nw_frag=no\n-Datapath actions: encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a0412345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)\n+Datapath actions: push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a041234567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x1)\n ])\n \n AT_CHECK([\n@@ -246,7 +246,7 @@ bridge(\"br0\")\n \n Final flow: unchanged\n Megaflow: recirc_id=0,eth,in_port=4,dl_type=0x894f,nsh_mdtype=2,nsh_np=3,nsh_spi=0x1234\n-Datapath actions: pop_eth,decap_nsh(),recirc(0x2)\n+Datapath actions: pop_eth,pop_nsh(),recirc(0x2)\n ])\n \n # Now send two real ICMP echo request packets in on port p1\n@@ -264,7 +264,7 @@ ovs-appctl time/warp 1000\n AT_CHECK([\n     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort\n ], [0], [flow-dump from non-dpdk interfaces:\n-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:encap_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a0412345678),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,decap_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)\n+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(dst=1e:2c:e9:2a:66:9e),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:push_nsh(flags=0,mdtype=2,np=3,spi=0x1234,si=255,md2=0x10000a041234567820001408fedcba9876543210),push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),pop_eth,pop_nsh(),set(eth(dst=11:22:33:44:55:66)),recirc(0x3)\n recirc_id(0x3),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:1, bytes:98, used:0.0s, actions:2\n ])\n \n@@ -577,8 +577,8 @@ ovs-appctl time/warp 1000\n AT_CHECK([\n     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort\n ], [0], [flow-dump from non-dpdk interfaces:\n-recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))\n-tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s, actions:decap_nsh(),recirc(0x1)\n+recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.30,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3000,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=30.0.0.1,dst=30.0.0.3)),tnl_pop(4789))\n+tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3000,si=255), packets:1, bytes:108, used:0.0s, actions:pop_nsh(),recirc(0x1)\n tunnel(tun_id=0x0,src=30.0.0.1,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0x1),in_port(4789),packet_type(ns=1,id=0x800),ipv4(frag=no), packets:1, bytes:84, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6\n ])\n \n@@ -631,9 +631,9 @@ ovs-appctl time/warp 1000\n AT_CHECK([\n     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort\n ], [0], [flow-dump from non-dpdk interfaces:\n-recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,encap_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))\n+recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,push_nsh(flags=0,mdtype=1,np=1,spi=0x3020,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))\n tunnel(tun_id=0x0,src=20.0.0.1,dst=20.0.0.2,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(spi=0x3020,si=255), packets:1, bytes:108, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),set(nsh(spi=0x3020,si=254)),pop_eth,clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:02,dl_type=0x0800),ipv4(src=20.0.0.2,dst=20.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(2)),set(ipv4(src=30.0.0.2,dst=30.0.0.3)),tnl_pop(4789))\n-tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s, actions:decap_nsh(),recirc(0x2)\n+tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s, actions:pop_nsh(),recirc(0x2)\n tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0x2),in_port(4789),packet_type(ns=1,id=0x800),ipv4(frag=no), packets:1, bytes:84, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6\n ])\n \n",
    "prefixes": [
        "ovs-dev",
        "v3",
        "1/2"
    ]
}