@@ -62,11 +62,17 @@ Post-v2.7.0
* The "learn" action now supports a "limit" option (see ovs-ofctl(8)).
* The port status bit OFPPS_LIVE now reflects link aliveness.
* OpenFlow 1.5 packet-out is now supported.
+ * Support for OpenFlow 1.5 field packet_type and packet-type-aware
+ pipeline (PTAP).
+ * Added generic encap and decap actions (EXT-382).
+ First supported use case is encap/decap for Ethernet.
- Fedora Packaging:
* OVN services are no longer restarted automatically after upgrade.
- Add --cleanup option to command 'ovs-appctl exit' (see ovs-vswitchd(8)).
- L3 tunneling:
* Use new tunnel port option "packet_type" to configure L2 vs. L3.
+ * In conjunction with PTAP tunnel ports can handle a mix of L2 and L3
+ payload.
* New vxlan tunnel extension "gpe" to support VXLAN-GPE tunnels.
* New support for non-Ethernet (L3) payloads in GRE and VXLAN-GPE.
- The BFD detection multiplier is now user-configurable.
@@ -466,6 +466,7 @@ enum ofp_header_type_namespaces {
OFPHTN_IP_PROTO = 2, /* ns_type is a IP protocol number. */
OFPHTN_UDP_TCP_PORT = 3, /* ns_type is a TCP or UDP port. */
OFPHTN_IPV4_OPTION = 4, /* ns_type is an IPv4 option number. */
+ OFPHTN_N_TYPES
};
#endif /* openflow/openflow-common.h */
@@ -12,6 +12,7 @@ openvswitchinclude_HEADERS = \
include/openvswitch/meta-flow.h \
include/openvswitch/ofpbuf.h \
include/openvswitch/ofp-actions.h \
+ include/openvswitch/ofp-ed-props.h \
include/openvswitch/ofp-errors.h \
include/openvswitch/ofp-msgs.h \
include/openvswitch/ofp-parse.h \
@@ -25,6 +25,7 @@
#include "openvswitch/ofp-util.h"
#include "openvswitch/ofp-errors.h"
#include "openvswitch/types.h"
+#include "openvswitch/ofp-ed-props.h"
#ifdef __cplusplus
extern "C" {
@@ -93,6 +94,10 @@ struct vl_mff_map;
OFPACT(PUSH_MPLS, ofpact_push_mpls, ofpact, "push_mpls") \
OFPACT(POP_MPLS, ofpact_pop_mpls, ofpact, "pop_mpls") \
\
+ /* Generic encap & decap */ \
+ OFPACT(ENCAP, ofpact_encap, props, "encap") \
+ OFPACT(DECAP, ofpact_decap, ofpact, "decap") \
+ \
/* Metadata. */ \
OFPACT(SET_TUNNEL, ofpact_tunnel, ofpact, "set_tunnel") \
OFPACT(SET_QUEUE, ofpact_queue, ofpact, "set_queue") \
@@ -974,6 +979,33 @@ struct ofpact_unroll_xlate {
ovs_be64 rule_cookie; /* OVS_BE64_MAX if none. */
};
+/* OFPACT_ENCAP.
+ *
+ * Used for NXAST_ENCAP. */
+
+struct ofpact_encap {
+ struct ofpact ofpact;
+ ovs_be32 new_pkt_type; /* Packet type of the header to add. */
+ uint16_t hdr_size; /* New header size in bytes. */
+ uint16_t n_props; /* Number of encap properties. */
+ struct ofpact_ed_prop props[]; /* Properties in internal format. */
+};
+
+/* OFPACT_DECAP.
+ *
+ * Used for NXAST_DECAP. */
+struct ofpact_decap {
+ struct ofpact ofpact;
+
+ /* New packet type.
+ *
+ * The special value (0,0xFFFE) "Use next proto" is used to request OVS to
+ * automatically set the new packet type based on the decap'ed header's
+ * next protocol.
+ */
+ ovs_be32 new_pkt_type;
+};
+
/* Converting OpenFlow to ofpacts. */
enum ofperr ofpacts_pull_openflow_actions(struct ofpbuf *openflow,
unsigned int actions_len,
new file mode 100644
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2017 Intel, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OPENVSWITCH_OFP_ED_PROPS_H
+#define OPENVSWITCH_OFP_ED_PROPS_H 1
+
+#include "openvswitch/ofp-errors.h"
+#include "openvswitch/types.h"
+#include "openvswitch/ofpbuf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum ofp_ed_prop_class {
+ OFPPPC_BASIC = 0, /* ONF Basic class. */
+ OFPPPC_MPLS = 1, /* MPLS property class. */
+ OFPPPC_GRE = 2, /* GRE property class. */
+ OFPPPC_GTP = 3, /* GTP property class. */
+
+ /* Experimenter property class.
+ *
+ * First 32 bits of property data
+ * is exp id after that is the experimenter property data.
+ */
+ OFPPPC_EXPERIMENTER=0xffff
+};
+
+/*
+ * External representation of encap/decap properties.
+ * These must be padded to a multiple of 8 bytes.
+ */
+struct ofp_ed_prop_header {
+ ovs_be16 prop_class;
+ uint8_t type;
+ uint8_t len;
+};
+
+/*
+ * Internal representation of encap/decap properties
+ */
+struct ofpact_ed_prop {
+ uint16_t prop_class;
+ uint8_t type;
+ uint8_t len;
+};
+
+enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
+ struct ofpbuf *out, size_t *remaining);
+enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop,
+ struct ofpbuf *out);
+bool parse_ed_prop_class(const char *str, uint16_t *prop_class);
+bool parse_ed_prop_type(uint16_t prop_class, const char *str, uint8_t *type);
+char *parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type,
+ const char *str, struct ofpbuf *out);
+char *format_ed_prop_class(const struct ofpact_ed_prop *prop);
+char *format_ed_prop_type(const struct ofpact_ed_prop *prop);
+void format_ed_prop_value(struct ds *s, const struct ofpact_ed_prop *prop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ofp-ed-props.h */
@@ -276,6 +276,15 @@ enum ofperr {
* 64. */
OFPERR_NXBAC_BAD_CONJUNCTION,
+ /* NX1.3+(39). Unsupported packet type in encap or decap. */
+ OFPERR_NXBAC_BAD_HEADER_TYPE,
+
+ /* NX1.3+(40). Unrecognized encap or decap property. */
+ OFPERR_NXBAC_UNKNOWN_ED_PROP,
+
+ /* NX1.3+(41). Error in encap or decap property. */
+ OFPERR_NXBAC_BAD_ED_PROP,
+
/* ## --------------------- ## */
/* ## OFPET_BAD_INSTRUCTION ## */
/* ## --------------------- ## */
@@ -149,6 +149,7 @@ lib_libopenvswitch_la_SOURCES = \
lib/odp-util.c \
lib/odp-util.h \
lib/ofp-actions.c \
+ lib/ofp-ed-props.c \
lib/ofp-errors.c \
lib/ofp-msgs.c \
lib/ofp-parse.c \
@@ -5922,13 +5922,17 @@ put_ethernet_key(const struct ovs_key_ethernet *eth, struct flow *flow)
}
static void
-commit_set_ether_addr_action(const struct flow *flow, struct flow *base_flow,
- struct ofpbuf *odp_actions,
- struct flow_wildcards *wc,
- bool use_masked)
+commit_set_ether_action(const struct flow *flow, struct flow *base_flow,
+ struct ofpbuf *odp_actions,
+ struct flow_wildcards *wc,
+ bool use_masked)
{
struct ovs_key_ethernet key, base, mask;
+ if (flow->packet_type != htonl(PT_ETH)) {
+ return;
+ }
+
get_ethernet_key(flow, &key);
get_ethernet_key(base_flow, &base);
get_ethernet_key(&wc->masks, &mask);
@@ -5941,29 +5945,6 @@ commit_set_ether_addr_action(const struct flow *flow, struct flow *base_flow,
}
static void
-commit_ether_action(const struct flow *flow, struct flow *base_flow,
- struct ofpbuf *odp_actions, struct flow_wildcards *wc,
- bool use_masked)
-{
- if (flow->packet_type == htonl(PT_ETH)) {
- if (base_flow->packet_type != htonl(PT_ETH)) {
- odp_put_push_eth_action(odp_actions, &flow->dl_src, &flow->dl_dst);
- base_flow->packet_type = flow->packet_type;
- base_flow->dl_src = flow->dl_src;
- base_flow->dl_dst = flow->dl_dst;
- } else {
- commit_set_ether_addr_action(flow, base_flow, odp_actions, wc,
- use_masked);
- }
- } else {
- if (base_flow->packet_type == htonl(PT_ETH)) {
- odp_put_pop_eth_action(odp_actions);
- base_flow->packet_type = flow->packet_type;
- }
- }
-}
-
-static void
commit_vlan_action(const struct flow* flow, struct flow *base,
struct ofpbuf *odp_actions, struct flow_wildcards *wc)
{
@@ -6400,6 +6381,50 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,
}
}
+static void
+commit_packet_type_change(const struct flow *flow,
+ struct flow *base_flow,
+ struct ofpbuf *odp_actions,
+ struct flow_wildcards *wc,
+ bool pending_encap)
+{
+ if (flow->packet_type == base_flow->packet_type) {
+ return;
+ }
+
+ if (pending_encap) {
+ switch (ntohl(flow->packet_type)) {
+ case PT_ETH: {
+ /* push_eth */
+ odp_put_push_eth_action(odp_actions, &flow->dl_src,
+ &flow->dl_dst);
+ base_flow->packet_type = flow->packet_type;
+ base_flow->dl_src = flow->dl_src;
+ base_flow->dl_dst = flow->dl_dst;
+ break;
+ }
+ default:
+ /* Only the above protocols are supported for encap. The check
+ * is done at action decoding. */
+ OVS_NOT_REACHED();
+ }
+ } else {
+ if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE &&
+ base_flow->packet_type == htonl(PT_ETH)) {
+ /* pop_eth */
+ odp_put_pop_eth_action(odp_actions);
+ base_flow->packet_type = flow->packet_type;
+ base_flow->dl_src = eth_addr_zero;
+ base_flow->dl_dst = eth_addr_zero;
+ } else {
+ /* All other cases are handled through recirculation. */
+ OVS_NOT_REACHED();
+ }
+ }
+
+ wc->masks.packet_type = OVS_BE32_MAX;
+}
+
/* If any of the flow key data that ODP actions can modify are different in
* 'base' and 'flow', appends ODP actions to 'odp_actions' that change the flow
* key from 'base' into 'flow', and then changes 'base' the same way. Does not
@@ -6412,12 +6437,13 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow,
enum slow_path_reason
commit_odp_actions(const struct flow *flow, struct flow *base,
struct ofpbuf *odp_actions, struct flow_wildcards *wc,
- bool use_masked)
+ bool use_masked, bool pending_encap)
{
enum slow_path_reason slow1, slow2;
bool mpls_done = false;
- commit_ether_action(flow, base, odp_actions, wc, use_masked);
+ commit_packet_type_change(flow, base, odp_actions, wc, pending_encap);
+ commit_set_ether_action(flow, base, odp_actions, wc, use_masked);
/* Make packet a non-MPLS packet before committing L3/4 actions,
* which would otherwise do nothing. */
if (eth_type_mpls(base->dl_type) && !eth_type_mpls(flow->dl_type)) {
@@ -276,7 +276,8 @@ enum slow_path_reason commit_odp_actions(const struct flow *,
struct flow *base,
struct ofpbuf *odp_actions,
struct flow_wildcards *wc,
- bool use_masked);
+ bool use_masked,
+ bool pending_encap);
/* ofproto-dpif interface.
*
@@ -342,6 +342,12 @@ enum ofp_raw_action_type {
/* NX1.0+(43): void. */
NXAST_RAW_CT_CLEAR,
+ /* NX1.3+(46): struct nx_action_encap, ... */
+ NXAST_RAW_ENCAP,
+
+ /* NX1.3+(47): struct nx_action_decap, ... */
+ NXAST_RAW_DECAP,
+
/* ## ------------------ ## */
/* ## Debugging actions. ## */
/* ## ------------------ ## */
@@ -472,6 +478,8 @@ ofpact_next_flattened(const struct ofpact *ofpact)
case OFPACT_WRITE_METADATA:
case OFPACT_GOTO_TABLE:
case OFPACT_NAT:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
return ofpact_next(ofpact);
case OFPACT_CLONE:
@@ -4019,6 +4027,320 @@ format_FIN_TIMEOUT(const struct ofpact_fin_timeout *a,
ds_chomp(s, ',');
ds_put_format(s, "%s)%s", colors.paren, colors.end);
}
+
+/* Action structure for NXAST_ENCAP */
+struct nx_action_encap {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Total size including any property TLVs. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_ENCAP. */
+ ovs_be16 hdr_size; /* Header size in bytes, 0 = 'not specified'.*/
+ ovs_be32 new_pkt_type; /* Header type to add and PACKET_TYPE of result. */
+ ovs_be16 n_props; /* Number of the following endecap properties. */
+ uint8_t pad[6]; /* Align to 8 bytes boundary */
+ struct ofp_ed_prop_header props[]; /* Encap TLV properties. */
+};
+OFP_ASSERT(sizeof(struct nx_action_encap) == 24);
+
+static enum ofperr
+decode_NXAST_RAW_ENCAP(const struct nx_action_encap *nae,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_encap *encap;
+ const struct ofp_ed_prop_header *ofp_prop;
+ size_t props_len;
+ uint16_t n_props = 0;
+ int err;
+
+ encap = ofpact_put_ENCAP(out);
+ encap->ofpact.raw = NXAST_RAW_ENCAP;
+ switch (ntohl(nae->new_pkt_type)) {
+ case PT_ETH:
+ /* Add supported encap header types here. */
+ break;
+ default:
+ return OFPERR_NXBAC_BAD_HEADER_TYPE;
+ }
+ encap->new_pkt_type = nae->new_pkt_type;
+ encap->hdr_size = ntohs(nae->hdr_size);
+ encap->n_props = ntohs(nae->n_props);
+
+ ofp_prop = nae->props;
+ props_len = ntohs(nae->len) - offsetof(struct nx_action_encap, props);
+ n_props = 0;
+ while (props_len > 0) {
+ err = decode_ed_prop(&ofp_prop, out, &props_len);
+ if (err) {
+ return err;
+ }
+ n_props++;
+ }
+ if (n_props != encap->n_props) {
+ return OFPERR_NXBAC_BAD_ED_PROP;
+ }
+ out->header = &encap->ofpact;
+ ofpact_finish_ENCAP(out, &encap);
+
+ return 0;
+}
+
+static void
+encode_ENCAP(const struct ofpact_encap *encap,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ size_t start_ofs = out->size;
+ struct nx_action_encap *nae = put_NXAST_ENCAP(out);
+ int i;
+
+ nae->new_pkt_type = encap->new_pkt_type;
+ nae->hdr_size = htons(encap->hdr_size);
+ nae->n_props = htons(encap->n_props);
+ const struct ofpact_ed_prop *prop = encap->props;
+ for (i = 0; i < encap->n_props; i++) {
+ encode_ed_prop(&prop, out);
+ }
+ pad_ofpat(out, start_ofs);
+}
+
+static bool
+parse_encap_header(const char *hdr, ovs_be32 *packet_type)
+{
+ if (strcmp(hdr, "ethernet") == 0) {
+ *packet_type = htonl(PT_ETH);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static char *
+parse_ed_props(char **arg, int *n_props, struct ofpbuf *out)
+{
+ char *key, *value, *err, *prop, *str;
+ uint16_t prop_class;
+ uint8_t prop_type;
+
+ str = *arg;
+ while (ofputil_parse_key_value(arg, &key, &value)) {
+ if (strcmp(key, "prop") != 0) {
+ return xasprintf("Invalid encap argument: %s", key);
+ }
+ /* Parse property class. */
+ str = prop = value;
+ if (!ofputil_parse_key_value(&prop, &key, &value)
+ || strcmp(key, "class") != 0 || value == NULL) {
+ return xasprintf("Missing prop class: %s", str);
+ }
+ if (!parse_ed_prop_class(value, &prop_class)) {
+ return xasprintf("Invalid encap prop class: %s", value);
+ }
+ /* Parse property type. */
+ str = prop;
+ if (!ofputil_parse_key_value(&prop, &key, &value)
+ || strcmp(key, "type") != 0 || value == NULL) {
+ return xasprintf("Missing encap prop type: %s", str);
+ }
+ if (!parse_ed_prop_type(prop_class, value, &prop_type)) {
+ return xasprintf("Invalid property type: %s", value);
+ }
+ /* Parse the prop value dependent on class and type. */
+ str = prop;
+ if (!ofputil_parse_key_value(&prop, &key, &value)
+ || strcmp(key, "val") != 0 || value == NULL) {
+ return xasprintf("Missing encap prop value: %s", str);
+ }
+ err = parse_ed_prop_value(prop_class, prop_type, value, out);
+ if (err != NULL) {
+ return err;
+ }
+ (*n_props)++;
+ }
+ return NULL;
+}
+
+/* The string representation of the encap action is
+ * encap(hdr=<pkt_type>,prop(class=<class>,type=<type>,val=<val>),prop(...),...)
+ */
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_ENCAP(char *arg,
+ const struct ofputil_port_map *port_map OVS_UNUSED,
+ struct ofpbuf *out,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_encap *encap;
+ char *key, *value, *str;
+ char *error = NULL;
+ int n_props = 0;
+
+ encap = ofpact_put_ENCAP(out);
+ encap->hdr_size = 0;
+ /* Parse encap header type. */
+ str = arg;
+ if (!ofputil_parse_key_value(&arg, &key, &value)
+ || strcmp(key, "hdr") != 0 || value == NULL) {
+ return xasprintf("Missing encap hdr: %s", str);
+ }
+ if (!parse_encap_header(value, &encap->new_pkt_type)) {
+ return xasprintf("Encap hdr not supported: %s", value);
+ }
+ /* Parse encap properties. */
+ error = parse_ed_props(&arg, &n_props, out);
+ if (error != NULL) {
+ return error;
+ }
+ /* ofbuf out may have been re-allocated. */
+ encap = out->header;
+ encap->n_props = n_props;
+ ofpact_finish_ENCAP(out, &encap);
+ return NULL;
+}
+
+static char *
+format_encap_pkt_type(const ovs_be32 pkt_type)
+{
+ switch (ntohl(pkt_type)) {
+ case PT_ETH:
+ return "ethernet";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void
+format_ed_props(struct ds *s, uint16_t n_props,
+ const struct ofpact_ed_prop *prop)
+{
+ const uint8_t *p = (uint8_t *) prop;
+ int i;
+
+ if (n_props == 0) {
+ return;
+ }
+ for (i = 0; i < n_props; i++) {
+ ds_put_format(s, ",prop(class=%s,", format_ed_prop_class(prop));
+ ds_put_format(s, "type=%s,", format_ed_prop_type(prop));
+ format_ed_prop_value(s, prop);
+ ds_put_cstr(s, ")");
+ p += ROUND_UP(prop->len, 8);
+ prop = ALIGNED_CAST(const struct ofpact_ed_prop *, p);
+ }
+}
+
+static void
+format_ENCAP(const struct ofpact_encap *a,
+ const struct ofputil_port_map *port_map OVS_UNUSED,
+ struct ds *s)
+{
+ ds_put_format(s, "%sencap(%s", colors.paren, colors.end);
+ ds_put_format(s, "hdr=%s", format_encap_pkt_type(a->new_pkt_type));
+ format_ed_props(s, a->n_props, a->props);
+ ds_put_format(s, "%s)%s", colors.paren, colors.end);
+}
+
+/* Action structure for NXAST_DECAP */
+struct nx_action_decap {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Total size including any property TLVs. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_DECAP. */
+ uint8_t pad[2]; /* 2 bytes padding */
+
+ /* Packet type or result.
+ *
+ * The special value (0,0xFFFE) "Use next proto"
+ * is used to request OvS to automatically set the new packet type based
+ * on the decap'ed header's next protocol.
+ */
+ ovs_be32 new_pkt_type;
+};
+OFP_ASSERT(sizeof(struct nx_action_decap) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_DECAP(const struct nx_action_decap *nad,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct ofpact_decap * decap;
+
+ decap = ofpact_put_DECAP(ofpacts);
+ decap->ofpact.raw = NXAST_RAW_DECAP;
+ decap->new_pkt_type = nad->new_pkt_type;
+ return 0;
+}
+
+static void
+encode_DECAP(const struct ofpact_decap *decap,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ struct nx_action_decap *nad = put_NXAST_DECAP(out);
+
+ nad->len = htons(sizeof(struct nx_action_decap));
+ nad->new_pkt_type = decap->new_pkt_type;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_DECAP(char *arg,
+ const struct ofputil_port_map *port_map OVS_UNUSED,
+ struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_decap *decap;
+ char *key, *value, *pos;
+ char *error = NULL;
+ uint16_t ns, type;
+
+ decap = ofpact_put_DECAP(ofpacts);
+ /* Default next packet_type is PT_USE_NEXT_PROTO. */
+ decap->new_pkt_type = htonl(PT_USE_NEXT_PROTO);
+
+ /* Parse decap packet_type if given. */
+ if (ofputil_parse_key_value(&arg, &key, &value)) {
+ if (strcmp(key, "packet_type") == 0) {
+ pos = value;
+ if (!ofputil_parse_key_value(&pos, &key, &value)
+ || strcmp(key, "ns") != 0) {
+ return xstrdup("Missing packet_type attribute ns");
+ }
+ error = str_to_u16(value, "ns", &ns);
+ if (error) {
+ return error;
+ }
+ if (ns >= OFPHTN_N_TYPES) {
+ return xasprintf("Unsupported ns value: %"PRIu16, ns);
+ }
+ if (!ofputil_parse_key_value(&pos, &key, &value)
+ || strcmp(key, "type") != 0) {
+ return xstrdup("Missing packet_type attribute type");
+ }
+ error = str_to_u16(value, "type", &type);
+ if (error) {
+ return error;
+ }
+ decap->new_pkt_type = htonl(PACKET_TYPE(ns, type));
+ } else {
+ return xasprintf("Invalid decap argument: %s", key);
+ }
+ }
+ return NULL;
+}
+
+static void
+format_DECAP(const struct ofpact_decap *a,
+ const struct ofputil_port_map *port_map OVS_UNUSED,
+ struct ds *s)
+{
+ ds_put_format(s, "%sdecap(%s", colors.paren, colors.end);
+ if (a->new_pkt_type != htonl(PT_USE_NEXT_PROTO)) {
+ ds_put_format(s, "packet_type(ns=%"PRIu16",id=%#"PRIx16")",
+ pt_ns(a->new_pkt_type),
+ pt_ns_type(a->new_pkt_type));
+ }
+ ds_put_format(s, "%s)%s", colors.paren, colors.end);
+}
+
/* Action structures for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE, and
* NXAST_RESUBMIT_TABLE_CT.
@@ -6802,6 +7124,8 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
case OFPACT_SET_TUNNEL:
case OFPACT_SET_VLAN_PCP:
case OFPACT_SET_VLAN_VID:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
return true;
case OFPACT_BUNDLE:
case OFPACT_CLEAR_ACTIONS:
@@ -6877,6 +7201,8 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
case OFPACT_SET_VLAN_PCP:
case OFPACT_SET_VLAN_VID:
case OFPACT_STRIP_VLAN:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
return true;
/* In general these actions are excluded because they are not part of
@@ -6984,6 +7310,8 @@ ofpacts_execute_action_set(struct ofpbuf *action_list,
/* The OpenFlow spec "Action Set" section specifies this order. */
ofpacts_copy_last(action_list, action_set, OFPACT_STRIP_VLAN);
ofpacts_copy_last(action_list, action_set, OFPACT_POP_MPLS);
+ ofpacts_copy_last(action_list, action_set, OFPACT_DECAP);
+ ofpacts_copy_last(action_list, action_set, OFPACT_ENCAP);
ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_MPLS);
ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_VLAN);
ofpacts_copy_last(action_list, action_set, OFPACT_DEC_TTL);
@@ -7127,6 +7455,8 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
case OFPACT_CT:
case OFPACT_CT_CLEAR:
case OFPACT_NAT:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
default:
return OVSINST_OFPIT11_APPLY_ACTIONS;
}
@@ -7488,8 +7818,8 @@ inconsistent_match(enum ofputil_protocol *usable_protocols)
*usable_protocols &= OFPUTIL_P_OF10_ANY;
}
-/* May modify flow->dl_type, flow->nw_proto and flow->vlan_tci,
- * caller must restore them.
+/* May modify flow->packet_type, flow->dl_type, flow->nw_proto and
+ * flow->vlan_tci, caller must restore them.
*
* Modifies some actions, filling in fields that could not be properly set
* without context. */
@@ -7501,6 +7831,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
struct flow *flow = &match->flow;
const struct ofpact_enqueue *enqueue;
const struct mf_field *mf;
+ ovs_be16 dl_type = get_dl_type(flow);
switch (a->type) {
case OFPACT_OUTPUT:
@@ -7578,7 +7909,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
case OFPACT_SET_IPV4_SRC:
case OFPACT_SET_IPV4_DST:
- if (flow->dl_type != htons(ETH_TYPE_IP)) {
+ if (dl_type != htons(ETH_TYPE_IP)) {
inconsistent_match(usable_protocols);
}
return 0;
@@ -7643,7 +7974,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
case OFPACT_SET_MPLS_TC:
case OFPACT_SET_MPLS_TTL:
case OFPACT_DEC_MPLS_TTL:
- if (!eth_type_mpls(flow->dl_type)) {
+ if (!eth_type_mpls(dl_type)) {
inconsistent_match(usable_protocols);
}
return 0;
@@ -7681,6 +8012,9 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
return 0;
case OFPACT_PUSH_MPLS:
+ if (flow->packet_type != htonl(PT_ETH)) {
+ inconsistent_match(usable_protocols);
+ }
flow->dl_type = ofpact_get_PUSH_MPLS(a)->ethertype;
/* The packet is now MPLS and the MPLS payload is opaque.
* Thus nothing can be assumed about the network protocol.
@@ -7689,7 +8023,8 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
return 0;
case OFPACT_POP_MPLS:
- if (!eth_type_mpls(flow->dl_type)) {
+ if (flow->packet_type != htonl(PT_ETH)
+ || !eth_type_mpls(dl_type)) {
inconsistent_match(usable_protocols);
}
flow->dl_type = ofpact_get_POP_MPLS(a)->ethertype;
@@ -7708,7 +8043,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
case OFPACT_CT: {
struct ofpact_conntrack *oc = ofpact_get_CT(a);
- if (!dl_type_is_ip_any(flow->dl_type)
+ if (!dl_type_is_ip_any(dl_type)
|| (flow->ct_state & CS_INVALID && oc->flags & NX_CT_F_COMMIT)
|| (oc->alg == IPPORT_FTP && flow->nw_proto != IPPROTO_TCP)
|| (oc->alg == IPPORT_TFTP && flow->nw_proto != IPPROTO_UDP)) {
@@ -7733,10 +8068,10 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
case OFPACT_NAT: {
struct ofpact_nat *on = ofpact_get_NAT(a);
- if (!dl_type_is_ip_any(flow->dl_type) ||
- (on->range_af == AF_INET && flow->dl_type != htons(ETH_TYPE_IP)) ||
+ if (!dl_type_is_ip_any(dl_type) ||
+ (on->range_af == AF_INET && dl_type != htons(ETH_TYPE_IP)) ||
(on->range_af == AF_INET6
- && flow->dl_type != htons(ETH_TYPE_IPV6))) {
+ && dl_type != htons(ETH_TYPE_IPV6))) {
return OFPERR_OFPBAC_MATCH_INCONSISTENT;
}
return 0;
@@ -7785,6 +8120,29 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
case OFPACT_DEBUG_RECIRC:
return 0;
+ case OFPACT_ENCAP:
+ flow->packet_type = ofpact_get_ENCAP(a)->new_pkt_type;
+ if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE) {
+ flow->dl_type = htons(pt_ns_type(flow->packet_type));
+ }
+ if (!is_ip_any(flow)) {
+ flow->nw_proto = 0;
+ }
+ return 0;
+
+ case OFPACT_DECAP:
+ if (flow->packet_type == htonl(PT_ETH)) {
+ /* Adjust the packet_type to allow subsequent actions. */
+ flow->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE,
+ ntohs(flow->dl_type));
+ } else {
+ /* The actual packet_type is only known after decapsulation.
+ * Do not allow subsequent actions that depend on packet headers. */
+ flow->packet_type = htonl(PT_UNKNOWN);
+ flow->dl_type = OVS_BE16_MAX;
+ }
+ return 0;
+
default:
OVS_NOT_REACHED();
}
@@ -7810,6 +8168,7 @@ ofpacts_check(struct ofpact ofpacts[], size_t ofpacts_len,
enum ofputil_protocol *usable_protocols)
{
struct ofpact *a;
+ ovs_be32 packet_type = match->flow.packet_type;
ovs_be16 dl_type = match->flow.dl_type;
uint8_t nw_proto = match->flow.nw_proto;
enum ofperr error = 0;
@@ -7825,6 +8184,7 @@ ofpacts_check(struct ofpact ofpacts[], size_t ofpacts_len,
}
}
/* Restore fields that may have been modified. */
+ match->flow.packet_type = packet_type;
match->flow.dl_type = dl_type;
memcpy(&match->flow.vlans, &vlans, sizeof(vlans));
match->flow.nw_proto = nw_proto;
@@ -8276,6 +8636,8 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
case OFPACT_CT:
case OFPACT_CT_CLEAR:
case OFPACT_NAT:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
default:
return false;
}
new file mode 100644
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2017 Intel, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include "openvswitch/ofp-ed-props.h"
+#include "openvswitch/ofp-util.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/ofp-parse.h"
+#include "util.h"
+#include "lib/packets.h"
+
+
+enum ofperr
+decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop,
+ struct ofpbuf *out OVS_UNUSED,
+ size_t *remaining)
+{
+ uint16_t prop_class = ntohs((*ofp_prop)->prop_class);
+ size_t len = (*ofp_prop)->len;
+ size_t pad_len = ROUND_UP(len, 8);
+
+ switch (prop_class) {
+ default:
+ return OFPERR_NXBAC_UNKNOWN_ED_PROP;
+ }
+
+ *remaining -= pad_len;
+ *ofp_prop = ALIGNED_CAST(const struct ofp_ed_prop_header *,
+ ((char *)(*ofp_prop) + pad_len));
+ return 0;
+}
+
+enum ofperr
+encode_ed_prop(const struct ofpact_ed_prop **prop,
+ struct ofpbuf *out OVS_UNUSED)
+{
+ size_t prop_len;
+
+ switch ((*prop)->prop_class) {
+ default:
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ *prop = ALIGNED_CAST(const struct ofpact_ed_prop *,
+ ((char *)(*prop) + prop_len));
+ return 0;
+}
+
+bool
+parse_ed_prop_class(const char *str OVS_UNUSED,
+ uint16_t *prop_class)
+{
+ if (!strcmp(str,"basic")) {
+ *prop_class = OFPPPC_BASIC;
+ } else if (!strcmp(str,"mpls")) {
+ *prop_class = OFPPPC_MPLS;
+ } else if (!strcmp(str,"gre")) {
+ *prop_class = OFPPPC_GRE;
+ } else if (!strcmp(str,"gtp")) {
+ *prop_class = OFPPPC_GTP;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool
+parse_ed_prop_type(uint16_t prop_class,
+ const char *str OVS_UNUSED,
+ uint8_t *type OVS_UNUSED)
+{
+ switch (prop_class) {
+ default:
+ return false;
+ }
+}
+
+/* Parse the value of an encap/decap property based on property class
+ * and type and append the parsed property in internal format to the
+ * ofpbuf out.
+ * Returns a malloced string in the event of a parse error. The caller
+ * must free the string.
+ */
+
+char *
+parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED,
+ const char *value, struct ofpbuf *out OVS_UNUSED)
+{
+
+ if (value == NULL || *value == '\0') {
+ return xstrdup("Value missing for encap property");
+ }
+
+ switch (prop_class) {
+ default:
+ /* Unsupported property classes rejected before. */
+ OVS_NOT_REACHED();
+ }
+
+ return NULL;
+}
+
+char *
+format_ed_prop_class(const struct ofpact_ed_prop *prop)
+{
+ switch (prop->prop_class) {
+ case OFPPPC_BASIC:
+ return "basic";
+ case OFPPPC_MPLS:
+ return "mpls";
+ case OFPPPC_GRE:
+ return "gre";
+ case OFPPPC_GTP:
+ return "gtp";
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+char *
+format_ed_prop_type(const struct ofpact_ed_prop *prop)
+{
+ switch (prop->prop_class) {
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+void
+format_ed_prop_value(struct ds *s OVS_UNUSED,
+ const struct ofpact_ed_prop *prop)
+{
+ switch (prop->prop_class) {
+ default:
+ OVS_NOT_REACHED();
+ }
+}
@@ -1265,7 +1265,8 @@ pt_ns_type(ovs_be32 packet_type)
/* Well-known packet_type field values. */
enum packet_type {
- PT_ETH = PACKET_TYPE(OFPHTN_ONF, 0x0000), /* Default: Ethernet */
+ PT_ETH = PACKET_TYPE(OFPHTN_ONF, 0x0000), /* Default PT: Ethernet */
+ PT_USE_NEXT_PROTO = PACKET_TYPE(OFPHTN_ONF, 0xfffe), /* Pseudo PT for decap. */
PT_IPV4 = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_IP),
PT_IPV6 = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_IPV6),
PT_MPLS = PACKET_TYPE(OFPHTN_ETHERTYPE, ETH_TYPE_MPLS),
@@ -234,6 +234,8 @@ struct xlate_ctx {
bool in_action_set; /* Currently translating action_set, if true. */
bool in_packet_out; /* Currently translating a packet_out msg, if
* true. */
+ bool pending_encap; /* Waiting to commit a pending encap
+ * action, if true. */
uint8_t table_id; /* OpenFlow table ID where flow was found. */
ovs_be64 rule_cookie; /* Cookie of the rule being translated. */
@@ -3465,7 +3467,8 @@ xlate_commit_actions(struct xlate_ctx *ctx)
ctx->xout->slow |= commit_odp_actions(&ctx->xin->flow, &ctx->base_flow,
ctx->odp_actions, ctx->wc,
- use_masked);
+ use_masked, ctx->pending_encap);
+ ctx->pending_encap = false;
}
static void
@@ -5503,6 +5506,8 @@ freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end,
case OFPACT_METER:
case OFPACT_SAMPLE:
case OFPACT_CLONE:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
case OFPACT_DEBUG_RECIRC:
case OFPACT_CT:
case OFPACT_CT_CLEAR:
@@ -5692,6 +5697,93 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
}
static void
+rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,
+ struct flow *flow,
+ struct flow_wildcards *wc)
+{
+ wc->masks.packet_type = OVS_BE32_MAX;
+ if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE) {
+ /* Only adjust the packet_type and zero the dummy Ethernet addresses. */
+ ovs_be16 ethertype = pt_ns_type_be(flow->packet_type);
+ flow->packet_type = htonl(PT_ETH);
+ flow->dl_src = eth_addr_zero;
+ flow->dl_dst = eth_addr_zero;
+ flow->dl_type = ethertype;
+ } else {
+ xlate_report_debug(ctx, OFT_ACTION,
+ "encap(ethernet) unsupported for packet type "
+ "ethernet");
+ /* TODO: Error handling: drop packet. */
+ ctx->error = 1;
+ }
+}
+
+static void
+xlate_generic_encap_action(struct xlate_ctx *ctx,
+ const struct ofpact_encap *encap)
+{
+ struct flow *flow = &ctx->xin->flow;
+ struct flow_wildcards *wc = ctx->wc;
+
+ /* Ensure that any pending actions on the inner packet are applied before
+ * rewriting the flow */
+ xlate_commit_actions(ctx);
+
+ /* Rewrite the flow to reflect the effect of pushing the new encap header. */
+ switch (ntohl(encap->new_pkt_type)) {
+ case PT_ETH:
+ rewrite_flow_encap_ethernet(ctx, flow, wc);
+ break;
+ default:
+ /* TODO: Error handling: Should not happen if the PT is checked
+ * at decoding */
+ break;
+ }
+
+ if (!ctx->error) {
+ /* The actual encap datapath action will be generated at next commit. */
+ ctx->pending_encap = true;
+ }
+}
+
+/* Returns true if packet must be recirculated after decapsulation. */
+static bool
+xlate_generic_decap_action(struct xlate_ctx *ctx,
+ const struct ofpact_decap *decap OVS_UNUSED)
+{
+ struct flow *flow = &ctx->xin->flow;
+
+ /* Ensure that any pending actions on the current packet are applied
+ * before generating the decap action. */
+ xlate_commit_actions(ctx);
+
+ /* We assume for now that the new_pkt_type is PT_USE_NEXT_PROTO. */
+ switch (ntohl(flow->packet_type)) {
+ case PT_ETH:
+ if (flow->vlans[0].tci & htons(VLAN_CFI)) {
+ /* Error handling: drop packet. */
+ xlate_report_debug(ctx, OFT_ACTION, "Dropping packet, cannot "
+ "decap Ethernet if VLAN is present.");
+ ctx->error = 1;
+ } else {
+ /* Just change the packet_type.
+ * Delay generating pop_eth to the next commit. */
+ flow->packet_type = htonl(PACKET_TYPE(OFPHTN_ETHERTYPE,
+ ntohs(flow->dl_type)));
+ ctx->wc->masks.dl_type = OVS_BE16_MAX;
+ }
+ return false;
+ default:
+ xlate_report_debug(ctx, OFT_ACTION,
+ "decap() for unsupported packet type %x",
+ ntohl(flow->packet_type));
+ /* TODO: Error handling: drop packet. */
+ ctx->error = 1;
+ return false;
+ }
+}
+
+static void
recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
{
/* No need to recirculate if already exiting. */
@@ -5765,6 +5857,8 @@ recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
case OFPACT_EXIT:
case OFPACT_SAMPLE:
case OFPACT_CLONE:
+ case OFPACT_ENCAP:
+ case OFPACT_DECAP:
case OFPACT_UNROLL_XLATE:
case OFPACT_CT:
case OFPACT_CT_CLEAR:
@@ -6181,6 +6275,22 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
xlate_clone(ctx, ofpact_get_CLONE(a));
break;
+ case OFPACT_ENCAP:
+ xlate_generic_encap_action(ctx, ofpact_get_ENCAP(a));
+ break;
+
+ case OFPACT_DECAP: {
+ bool recirc_needed =
+ xlate_generic_decap_action(ctx, ofpact_get_DECAP(a));
+ if (!ctx->error && recirc_needed) {
+ /* Recirculate for parsing of inner packet. */
+ ctx_trigger_freeze(ctx);
+ /* Then continue with next action. */
+ a = ofpact_next(a);
+ }
+ break;
+ }
+
case OFPACT_CT:
compose_conntrack_action(ctx, ofpact_get_CT(a));
break;
@@ -6423,7 +6533,7 @@ xlate_wc_finish(struct xlate_ctx *ctx)
* use non-header fields as part of the cache. */
flow_wildcards_clear_non_packet_fields(ctx->wc);
- /* Wildcard ethernet addresses if the original packet type was not
+ /* Wildcard ethernet fields if the original packet type was not
* Ethernet. */
if (ctx->xin->upcall_flow->packet_type != htonl(PT_ETH)) {
ctx->wc->masks.dl_dst = eth_addr_zero;
@@ -6512,6 +6622,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
.in_group = false,
.in_action_set = false,
.in_packet_out = xin->in_packet_out,
+ .pending_encap = false,
.table_id = 0,
.rule_cookie = OVS_BE64_MAX,
@@ -6672,6 +6783,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
flow->packet_type = htonl(PT_ETH);
flow->dl_src = eth_addr_zero;
flow->dl_dst = eth_addr_zero;
+ ctx.pending_encap = true;
}
if (!xin->ofpacts && !ctx.rule) {
@@ -1561,17 +1561,23 @@ the action set, the one written later replaces the earlier action:
\fBpop_mpls\fR
.
.IP 2.
-\fBpush_mpls\fR
+\fBdecap\fR
.
.IP 3.
-\fBpush_vlan\fR
+\fBencap\fR
.
.IP 4.
+\fBpush_mpls\fR
+.
+.IP 5.
+\fBpush_vlan\fR
+.
+.IP 6.
\fBdec_ttl\fR
.IQ
\fBdec_mpls_ttl\fR
.
-.IP 5.
+.IP 7.
\fBload\fR
.IQ
\fBmove\fR
@@ -1611,10 +1617,10 @@ the later modification takes effect, and when they modify
different parts of a field (or different fields), then both
modifications are applied.
.
-.IP 6.
+.IP 8.
\fBset_queue\fR
.
-.IP 7.
+.IP 9.
\fBgroup\fR
.IQ
\fBoutput\fR
@@ -1630,6 +1636,7 @@ not visible.)
.RE
.IP
Only the actions listed above may be written to the action set.
+\fBencap\fR and \fBdecap\fR actions are nonstandard.
.
.IP \fBwrite_metadata\fB:\fIvalue\fR[/\fImask\fR]
Updates the metadata field for the flow. If \fImask\fR is omitted, the
@@ -1730,6 +1737,62 @@ that is saved and restored includes all flow data and metadata
\fBpush\fR and \fBpop\fR actions, and the OpenFlow action set.
.IP
This action was added in Open vSwitch 2.6.90.
+.
+.IP "\fBencap(hdr=\fR\fIheader\fR[\fB,prop(class=\fR\fIclass\fR\fB,type=\fR\fItype\fR\fB,val=\fR\fIvalue\fR\fB)\fR...]\fB)\fR"
+Encapsulates the packet with a new packet header, e.g., ethernet
+or nsh.
+.
+.RS
+.IP "\fBhdr=\fR\fIheader\fR"
+Used to specify encapsulation header type.
+.
+.IP "\fBprop(class=\fR\fIclass\fR\fB,type=\fR\fItype\fR\fB,val=\fR\fIvalue\fR\fB)\fR"
+Used to specify the initial value for the field in the encapsulation header.
+.RE
+.IP
+For example, \fBencap(hdr=ethernet)\fR will encapsulate the L3 packet with
+Ethernet header.
+.IP
+\fBencap(hdr=nsh,prop(class=nsh,type=md_type,val=1))\fR will encapsulate
+the packet with nsh header and nsh metadata type 1.
+.IP
+\fBencap(hdr=nsh,prop(class=nsh,type=md_type,val=2)\fR
+\fB,prop(class=nsh,type=tlv,val(0x1000,10,0x12345678)))\fR will encapsulate
+the packet with nsh header and nsh metadata type 2, and the nsh TLV with
+class 0x1000 and type 10 is set to 0x12345678.
+.IP
+\fBprop(...)\fR is just used to set some
+necessary fields for encapsulation header initialization. Other fields
+in the encapsulation header must be set by \fBset_field\fR action. New
+encapsulation header implementation must add new match fields and
+corresponding \fBset\fR action in order that \fBset_field\fR action can
+change the fields in the encapsulation header on demand.
+.IP
+\fBencap(hdr=nsh,prop(class=nsh,type=md_type,val=1)),\fR
+\fBset_field:0x1234->nsh_spi,set_field:0x11223344->nsh_c1\fR
+is an example to encapsulate nsh header and set nsh spi and c1.
+.IP
+This action was added in Open vSwitch 2.8.
+.
+.IP "\fBdecap(\fR[\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR]\fB)\fR"
+Decapsulates the outer packet header.
+.
+.RS
+.IP "\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR"
+It is optional and used to specify the outer header type of the
+decapsulated packet. \fInamespace\fR is 0 for Ethernet packet,
+1 for L3 packet, \fItype\fR\ is L3 protocol type, e.g.,
+0x894f for nsh, 0x0 for Ethernet.
+.RE
+.IP
+By default, \fBdecap()\fR will decapsulate the outer packet header
+according to the packet header type, if
+\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR
+is given, it will decapsulate the given packet header, it will fail
+if the actual outer packet header type is not of
+\fBpacket_type(ns=\fR\fInamespace\fR\fB,type=\fR\fItype\fR\fB)\fR.
+.IP
+This action was added in Open vSwitch 2.8.
.RE
.
.PP