diff --git a/build-aux/extract-odp-netlink-h b/build-aux/extract-odp-netlink-h
index a509adb88..bc1cc35a7 100755
--- a/build-aux/extract-odp-netlink-h
+++ b/build-aux/extract-odp-netlink-h
@@ -45,7 +45,11 @@ s,#.*experimenter
field
whose most significant byte is zero and whose remaining bytes are an
Organizationally Unique Identifier (OUI) assigned by the IEEE [IEEE OUI],
- as shown below. OpenFlow says that support for experimenter fields is
- optional. Open vSwitch 2.4 and later does support them, primarily so that
- it can support the ONFOXM_ET_
* code points defined by official
- Open Networking Foundation extensions to OpenFlow 1.3 in e.g. [TCP Flags
- Match Field Extension].
+ as shown below.
+ OpenFlow says that support for experimenter fields is optional. Open + vSwitch 2.4 and later does support them, so that it can support the + following experimenter classes: +
+ +ONFOXM_ET
)NXOXM_NSH
)+ OpenFlow says that support for experimenter fields is optional. Open + vSwitch 2.4 and later does support them, so that it can support the + following experimenter classes: +
+ +ONFOXM_ET
)NXOXM_NSH
)
Taken as a unit,
The fields in this group relate to tunnels, which Open vSwitch
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 571755a75..b782e8c59 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1025,7 +1025,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
int match_len;
int i;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
struct nxm_put_ctx ctx = { .output = b, .implied_ethernet = false };
@@ -1154,6 +1154,21 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
flow->tunnel.gbp_flags, match->wc.masks.tunnel.gbp_flags);
tun_metadata_to_nx_match(b, oxm, match);
+ /* Network Service Header */
+ nxm_put_8m(&ctx, MFF_NSH_FLAGS, oxm, flow->nsh.flags,
+ match->wc.masks.nsh.flags);
+ nxm_put_8m(&ctx, MFF_NSH_MDTYPE, oxm, flow->nsh.mdtype,
+ match->wc.masks.nsh.mdtype);
+ nxm_put_8m(&ctx, MFF_NSH_NP, oxm, flow->nsh.np,
+ match->wc.masks.nsh.np);
+ nxm_put_32m(&ctx, MFF_NSH_SPI, oxm, flow->nsh.spi,
+ match->wc.masks.nsh.spi);
+ nxm_put_8m(&ctx, MFF_NSH_SI, oxm, flow->nsh.si, match->wc.masks.nsh.si);
+ for (int i = 0; i < 4; i++) {
+ nxm_put_32m(&ctx, MFF_NSH_C1 + i, oxm, flow->nsh.c[i],
+ match->wc.masks.nsh.c[i]);
+ }
+
/* Registers. */
if (oxm < OFP15_VERSION) {
for (i = 0; i < FLOW_N_REGS; i++) {
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 03120bf06..e631c6836 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -270,6 +270,58 @@ odp_set_nd(struct dp_packet *packet, const struct ovs_key_nd *key,
}
}
+/* Set the NSH header. Assumes the NSH header is present and matches the
+ * MD format of the key. The slow path must take case of that. */
+static void
+odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key,
+ const struct ovs_key_nsh *mask)
+{
+ struct nsh_hdr *nsh = dp_packet_l3(packet);
+
+ if (!mask) {
+ nsh->ver_flags_len = htons(key->flags << NSH_FLAGS_SHIFT) |
+ (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
+ put_16aligned_be32(&nsh->path_hdr, key->path_hdr);
+ switch (nsh->md_type) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ put_16aligned_be32(&nsh->md1.c[i], key->c[i]);
+ }
+ break;
+ case NSH_M_TYPE2:
+ /* TODO */
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+ } else {
+ uint8_t flags = (ntohs(nsh->ver_flags_len) & NSH_FLAGS_MASK) >>
+ NSH_FLAGS_SHIFT;
+ flags = key->flags | (flags & ~mask->flags);
+ nsh->ver_flags_len = htons(flags << NSH_FLAGS_SHIFT) |
+ (nsh->ver_flags_len & ~htons(NSH_FLAGS_MASK));
+
+ ovs_be32 path_hdr = get_16aligned_be32(&nsh->path_hdr);
+ path_hdr = key->path_hdr | (path_hdr & ~mask->path_hdr);
+ put_16aligned_be32(&nsh->path_hdr, path_hdr);
+ switch (nsh->md_type) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ ovs_be32 p = get_16aligned_be32(&nsh->md1.c[i]);
+ ovs_be32 k = key->c[i];
+ ovs_be32 m = mask->c[i];
+ put_16aligned_be32(&nsh->md1.c[i], k | (p & ~m));
+ }
+ break;
+ case NSH_M_TYPE2:
+ /* TODO */
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+ }
+}
+
static void
odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
{
@@ -295,6 +347,10 @@ odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
odp_eth_set_addrs(packet, nl_attr_get(a), NULL);
break;
+ case OVS_KEY_ATTR_NSH:
+ odp_set_nsh(packet, nl_attr_get(a), NULL);
+ break;
+
case OVS_KEY_ATTR_IPV4:
ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));
packet_set_ipv4(packet, ipv4_key->ipv4_src,
@@ -419,6 +475,11 @@ odp_execute_masked_set_action(struct dp_packet *packet,
get_mask(a, struct ovs_key_ethernet));
break;
+ case OVS_KEY_ATTR_NSH:
+ odp_set_nsh(packet, nl_attr_get(a),
+ get_mask(a, struct ovs_key_nsh));
+ break;
+
case OVS_KEY_ATTR_IPV4:
odp_set_ipv4(packet, nl_attr_get(a),
get_mask(a, struct ovs_key_ipv4));
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 728e325ce..fa4871c4b 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -175,6 +175,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char *namebuf, size_t bufsize)
case OVS_KEY_ATTR_DP_HASH: return "dp_hash";
case OVS_KEY_ATTR_RECIRC_ID: return "recirc_id";
case OVS_KEY_ATTR_PACKET_TYPE: return "packet_type";
+ case OVS_KEY_ATTR_NSH: return "nsh";
case __OVS_KEY_ATTR_MAX:
default:
@@ -247,6 +248,98 @@ format_odp_clone_action(struct ds *ds, const struct nlattr *attr,
ds_put_format(ds, ")");
}
+static void
+format_nsh_key(struct ds *ds, const struct ovs_key_nsh *key)
+{
+ ds_put_format(ds, "flags=%d", key->flags);
+ ds_put_format(ds, ",mdtype=%d", key->mdtype);
+ ds_put_format(ds, ",np=%d", key->np);
+ ds_put_format(ds, ",spi=0x%x",
+ (ntohl(key->path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT);
+ ds_put_format(ds, ",si=%d",
+ (ntohl(key->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT);
+
+ switch (key->mdtype) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ ds_put_format(ds, ",c%d=0x%x", i + 1, ntohl(key->c[i]));
+ }
+ break;
+ case NSH_M_TYPE2:
+ /* TODO */
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static void
+format_uint8_masked(struct ds *s, bool *first, const char *name,
+ uint8_t value, uint8_t mask)
+{
+ if (mask != 0) {
+ if (!*first) {
+ ds_put_char(s, ',');
+ }
+ ds_put_format(s, "%s=", name);
+ if (mask == UINT8_MAX) {
+ ds_put_format(s, "%"PRIu8, value);
+ } else {
+ ds_put_format(s, "0x%02"PRIx8"/0x%02"PRIx8, value, mask);
+ }
+ *first = false;
+ }
+}
+
+static void
+format_be32_masked(struct ds *s, bool *first, const char *name,
+ ovs_be32 value, ovs_be32 mask)
+{
+ if (mask != htonl(0)) {
+ if (!*first) {
+ ds_put_char(s, ',');
+ }
+ ds_put_format(s, "%s=", name);
+ if (mask == OVS_BE32_MAX) {
+ ds_put_format(s, "0x%"PRIx32, ntohl(value));
+ } else {
+ ds_put_format(s, "0x%"PRIx32"/0x%08"PRIx32,
+ ntohl(value), ntohl(mask));
+ }
+ *first = false;
+ }
+}
+
+static void
+format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key,
+ const struct ovs_key_nsh *mask)
+{
+ if (!mask) {
+ format_nsh_key(ds, key);
+ } else {
+ bool first = true;
+ uint32_t spi = (ntohl(key->path_hdr) & NSH_SPI_MASK) >> NSH_SPI_SHIFT;
+ uint32_t spi_mask = (ntohl(mask->path_hdr) & NSH_SPI_MASK) >>
+ NSH_SPI_SHIFT;
+ if (spi_mask == 0x00ffffff) {
+ spi_mask = UINT32_MAX;
+ }
+ uint8_t si = (ntohl(key->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
+ uint8_t si_mask = (ntohl(mask->path_hdr) & NSH_SI_MASK) >>
+ NSH_SI_SHIFT;
+
+ format_uint8_masked(ds, &first, "flags", key->flags, mask->flags);
+ format_uint8_masked(ds, &first, "mdtype", key->mdtype, mask->mdtype);
+ format_uint8_masked(ds, &first, "np", key->np, mask->np);
+ format_be32_masked(ds, &first, "spi", htonl(spi), htonl(spi_mask));
+ format_uint8_masked(ds, &first, "si", si, si_mask);
+ format_be32_masked(ds, &first, "c1", key->c[0], mask->c[0]);
+ format_be32_masked(ds, &first, "c2", key->c[1], mask->c[1]);
+ format_be32_masked(ds, &first, "c3", key->c[2], mask->c[2]);
+ format_be32_masked(ds, &first, "c4", key->c[3], mask->c[3]);
+ }
+}
+
static const char *
slow_path_reason_to_string(uint32_t reason)
{
@@ -1970,6 +2063,7 @@ static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
[OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4] = { .len = sizeof(struct ovs_key_ct_tuple_ipv4) },
[OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { .len = sizeof(struct ovs_key_ct_tuple_ipv6) },
[OVS_KEY_ATTR_PACKET_TYPE] = { .len = 4 },
+ [OVS_KEY_ATTR_NSH] = { .len = sizeof(struct ovs_key_nsh) },
};
/* Returns the correct length of the payload for a flow key attribute of the
@@ -2222,6 +2316,7 @@ odp_mask_is_constant__(enum ovs_key_attr attr, const void *mask, size_t size,
case OVS_KEY_ATTR_CT_MARK:
case OVS_KEY_ATTR_CT_LABELS:
case OVS_KEY_ATTR_PACKET_TYPE:
+ case OVS_KEY_ATTR_NSH:
return is_all_byte(mask, size, u8);
case OVS_KEY_ATTR_TCP_FLAGS:
@@ -3185,6 +3280,12 @@ format_odp_key_attr__(const struct nlattr *a, const struct nlattr *ma,
ds_chomp(ds, ',');
break;
}
+ case OVS_KEY_ATTR_NSH: {
+ const struct ovs_key_nsh *mask = ma ? nl_attr_get(ma) : NULL;
+ const struct ovs_key_nsh *key = nl_attr_get(a);
+ format_nsh_key_mask(ds, key, mask);
+ break;
+ }
case OVS_KEY_ATTR_UNSPEC:
case __OVS_KEY_ATTR_MAX:
default:
@@ -3602,6 +3703,29 @@ scan_be16(const char *s, ovs_be16 *key, ovs_be16 *mask)
return 0;
}
+static int
+scan_be32(const char *s, ovs_be32 *key, ovs_be32 *mask)
+{
+ uint32_t key_, mask_;
+ int n;
+
+ if (ovs_scan(s, "%"SCNi32"%n", &key_, &n)) {
+ int len = n;
+
+ *key = htonl(key_);
+ if (mask) {
+ if (ovs_scan(s + len, "/%"SCNi32"%n", &mask_, &n)) {
+ len += n;
+ *mask = htonl(mask_);
+ } else {
+ *mask = OVS_BE32_MAX;
+ }
+ }
+ return len;
+ }
+ return 0;
+}
+
static int
scan_be64(const char *s, ovs_be64 *key, ovs_be64 *mask)
{
@@ -4403,6 +4527,17 @@ parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
SCAN_FIELD("id=", be16, id);
} SCAN_END(OVS_KEY_ATTR_PACKET_TYPE);
+ SCAN_BEGIN("nsh(", struct ovs_key_nsh) {
+ SCAN_FIELD("flags=", u8, flags);
+ SCAN_FIELD("mdtype=", u8, mdtype);
+ SCAN_FIELD("np=", u8, np);
+ SCAN_FIELD("path_hdr=", be32, path_hdr);
+ SCAN_FIELD("c1=", be32, c[0]);
+ SCAN_FIELD("c2=", be32, c[1]);
+ SCAN_FIELD("c3=", be32, c[2]);
+ SCAN_FIELD("c4=", be32, c[2]);
+ } SCAN_END(OVS_KEY_ATTR_NSH);
+
/* Encap open-coded. */
if (!strncmp(s, "encap(", 6)) {
const char *start = s;
@@ -4511,6 +4646,10 @@ static void get_arp_key(const struct flow *, struct ovs_key_arp *);
static void put_arp_key(const struct ovs_key_arp *, struct flow *);
static void get_nd_key(const struct flow *, struct ovs_key_nd *);
static void put_nd_key(const struct ovs_key_nd *, struct flow *);
+static void get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh,
+ bool is_mask);
+static void put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow,
+ bool is_mask);
/* These share the same layout. */
union ovs_key_tp {
@@ -4687,6 +4826,12 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
for (i = 0; i < n; i++) {
mpls_key[i].mpls_lse = data->mpls_lse[i];
}
+ } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
+ struct ovs_key_nsh *nsh_key;
+
+ nsh_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_NSH,
+ sizeof *nsh_key);
+ get_nsh_key(data, nsh_key, export_mask);
}
if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
@@ -4930,6 +5075,7 @@ odp_key_to_dp_packet(const struct nlattr *key, size_t key_len,
case OVS_KEY_ATTR_TCP_FLAGS:
case OVS_KEY_ATTR_MPLS:
case OVS_KEY_ATTR_PACKET_TYPE:
+ case OVS_KEY_ATTR_NSH:
case __OVS_KEY_ATTR_MAX:
default:
break;
@@ -5240,6 +5386,21 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
expected_bit = OVS_KEY_ATTR_ARP;
}
}
+ } else if (src_flow->dl_type == htons(ETH_TYPE_NSH)) {
+ if (!is_mask) {
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_NSH;
+ }
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_NSH)) {
+ const struct ovs_key_nsh *nsh_key;
+
+ nsh_key = nl_attr_get(attrs[OVS_KEY_ATTR_NSH]);
+ put_nsh_key(nsh_key, flow, false);
+ if (is_mask) {
+ check_start = nsh_key;
+ check_len = sizeof *nsh_key;
+ expected_bit = OVS_KEY_ATTR_NSH;
+ }
+ }
} else {
goto done;
}
@@ -6282,6 +6443,87 @@ commit_set_nw_action(const struct flow *flow, struct flow *base,
return 0;
}
+static void
+get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh, bool is_mask)
+{
+ nsh->flags = flow->nsh.flags;
+ nsh->mdtype = flow->nsh.mdtype;
+ nsh->np = flow->nsh.np;
+ nsh->path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) |
+ flow->nsh.si);
+ if (is_mask) {
+ for (int i = 0; i < 4; i++) {
+ nsh->c[i] = flow->nsh.c[i];
+ }
+ } else {
+ switch (nsh->mdtype) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ nsh->c[i] = flow->nsh.c[i];
+ }
+ break;
+ case NSH_M_TYPE2:
+ /* TODO: MD type 2 */
+ break;
+ }
+ }
+}
+
+static void
+put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow,
+ bool is_mask OVS_UNUSED)
+{
+ flow->nsh.flags = nsh->flags;
+ flow->nsh.mdtype = nsh->mdtype;
+ flow->nsh.np = nsh->np;
+ flow->nsh.spi = htonl((ntohl(nsh->path_hdr) & NSH_SPI_MASK) >>
+ NSH_SPI_SHIFT);
+ flow->nsh.si = (ntohl(nsh->path_hdr) & NSH_SI_MASK) >> NSH_SI_SHIFT;
+ switch (nsh->mdtype) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ flow->nsh.c[i] = nsh->c[i];
+ }
+ break;
+ case NSH_M_TYPE2:
+ /* TODO: MD type 2 */
+ memset(flow->nsh.c, 0, sizeof flow->nsh.c);
+ break;
+ }
+}
+
+static void
+commit_set_nsh_action(const struct flow *flow, struct flow *base_flow,
+ struct ofpbuf *odp_actions,
+ struct flow_wildcards *wc,
+ bool use_masked)
+{
+ struct ovs_key_nsh key, mask, base;
+
+ if (flow->dl_type != htons(ETH_TYPE_NSH) ||
+ !memcmp(&base_flow->nsh, &flow->nsh, sizeof base_flow->nsh)) {
+ return;
+ }
+
+ /* Check that mdtype and np remain unchanged. */
+ ovs_assert(flow->nsh.mdtype == base_flow->nsh.mdtype &&
+ flow->nsh.np == base_flow->nsh.np);
+
+ get_nsh_key(flow, &key, false);
+ get_nsh_key(base_flow, &base, false);
+ get_nsh_key(&wc->masks, &mask, true);
+ mask.mdtype = 0; /* Not writable. */
+ mask.np = 0; /* Not writable. */
+
+ if (commit(OVS_KEY_ATTR_NSH, use_masked, &key, &base, &mask, sizeof key,
+ odp_actions)) {
+ put_nsh_key(&base, base_flow, false);
+ if (mask.mdtype != 0) { /* Mask was changed by commit(). */
+ put_nsh_key(&mask, &wc->masks, true);
+ }
+ }
+}
+
/* TCP, UDP, and SCTP keys have the same layout. */
BUILD_ASSERT_DECL(sizeof(struct ovs_key_tcp) == sizeof(struct ovs_key_udp) &&
sizeof(struct ovs_key_tcp) == sizeof(struct ovs_key_sctp));
@@ -6445,6 +6687,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
commit_mpls_action(flow, base, odp_actions);
mpls_done = true;
}
+ commit_set_nsh_action(flow, base, odp_actions, wc, use_masked);
slow1 = commit_set_nw_action(flow, base, odp_actions, wc, use_masked);
commit_set_port_action(flow, base, odp_actions, wc, use_masked);
slow2 = commit_set_icmp_action(flow, base, odp_actions, wc);
diff --git a/lib/odp-util.h b/lib/odp-util.h
index bdb23545b..111cd9cff 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -147,7 +147,7 @@ void odp_portno_name_format(const struct hmap *portno_names,
* add another field and forget to adjust this value.
*/
#define ODPUTIL_FLOW_KEY_BYTES 640
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
/* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
* key. An array of "struct nlattr" might not, in theory, be sufficiently
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 354a6ce0e..86dd5cb61 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -102,7 +102,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
void
ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
/* Initialize most of wc. */
flow_wildcards_init_catchall(wc);
diff --git a/lib/packets.h b/lib/packets.h
index 297a8a94a..5083e6a83 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -25,6 +25,7 @@
#include "openvswitch/geneve.h"
#include "openvswitch/packets.h"
#include "openvswitch/types.h"
+#include "openvswitch/nsh.h"
#include "odp-netlink.h"
#include "random.h"
#include "hash.h"
@@ -397,6 +398,7 @@ ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
#define ETH_TYPE_RARP 0x8035
#define ETH_TYPE_MPLS 0x8847
#define ETH_TYPE_MPLS_MCAST 0x8848
+#define ETH_TYPE_NSH 0x894f
static inline bool eth_type_mpls(ovs_be16 eth_type)
{
@@ -1298,6 +1300,7 @@ enum packet_type {
PT_IPV6 = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_IPV6),
PT_MPLS = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS),
PT_MPLS_MC = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS_MCAST),
+ PT_NSH = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_NSH),
PT_UNKNOWN = PACKET_TYPE(0xffff, 0xffff), /* Unknown packet type. */
};
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index 45b23367e..19fc27c7c 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -99,7 +99,7 @@ struct rule;
/* Metadata for restoring pipeline context after recirculation. Helpers
* are inlined below to keep them together with the definition for easier
* updates. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
struct frozen_metadata {
/* Metadata in struct flow. */
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index fc665a636..e4cca657d 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1050,6 +1050,7 @@ sflow_read_set_action(const struct nlattr *attr,
case OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6:
case OVS_KEY_ATTR_UNSPEC:
case OVS_KEY_ATTR_PACKET_TYPE:
+ case OVS_KEY_ATTR_NSH:
case __OVS_KEY_ATTR_MAX:
default:
break;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1c2644c4f..79decdb9e 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3728,7 +3728,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
/* If 'struct flow' gets additional metadata, we'll need to zero it out
* before traversing a patch port. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 39);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
memset(&flow_tnl, 0, sizeof flow_tnl);
if (!check_output_prerequisites(ctx, xport, flow, check_stp)) {
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 9c10253bb..31cb5208f 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2386,7 +2386,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_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
+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
matching:
dp_hash: arbitrary mask
recirc_id: exact match or wildcard
@@ -2548,6 +2548,15 @@ 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
+ nsh_flags: arbitrary mask
+ nsh_mdtype: exact match or wildcard
+ nsh_np: exact match or wildcard
+ nsh_spi: exact match or wildcard
+ nsh_si: exact match or wildcard
+ nsh_c1: arbitrary mask
+ nsh_c2: arbitrary mask
+ nsh_c3: arbitrary mask
+ nsh_c4: arbitrary mask
' $1
}
class
(or vendor
),
field
, and experimenter
(when present) uniquely
@@ -1292,6 +1328,27 @@ tcp,tp_src=0x07c0/0xfff0
+