+ struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
+ move->ofpact.raw = ONFACT_RAW13_COPY_FIELD;
+ move->src.ofs = ntohs(src_offset);
+ move->src.n_bits = ntohs(n_bits);
+ move->dst.ofs = ntohs(dst_offset);
+ move->dst.n_bits = ntohs(n_bits);
+
+ struct ofpbuf b = ofpbuf_const_initializer(action, ntohs(action_len));
+ ofpbuf_pull(&b, oxm_offset);
+
+ enum ofperr error = nx_pull_header(&b, &move->src.field, NULL);
+ if (error) {
+ return error;
+ }
+ error = nx_pull_header(&b, &move->dst.field, NULL);
+ if (error) {
+ return error;
+ }
+
+ if (!is_all_zeros(b.data, b.size)) {
+ return OFPERR_NXBRC_MUST_BE_ZERO;
+ }
+
+ return nxm_reg_move_check(move, NULL);
+}
+
+static enum ofperr
+decode_OFPAT_RAW15_COPY_FIELD(const struct ofp15_action_copy_field *oacf,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ return decode_copy_field__(oacf->src_offset, oacf->dst_offset,
+ oacf->n_bits, oacf, oacf->len,
+ OBJECT_OFFSETOF(oacf, pad2), ofpacts);
+}
+
+static enum ofperr
+decode_ONFACT_RAW13_COPY_FIELD(const struct onf_action_copy_field *oacf,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ return decode_copy_field__(oacf->src_offset, oacf->dst_offset,
+ oacf->n_bits, oacf, oacf->len,
+ OBJECT_OFFSETOF(oacf, pad3), ofpacts);
+}
+
+static enum ofperr
+decode_NXAST_RAW_REG_MOVE(const struct nx_action_reg_move *narm,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
+ move->ofpact.raw = NXAST_RAW_REG_MOVE;
+ move->src.ofs = ntohs(narm->src_ofs);
+ move->src.n_bits = ntohs(narm->n_bits);
+ move->dst.ofs = ntohs(narm->dst_ofs);
+ move->dst.n_bits = ntohs(narm->n_bits);
+
+ struct ofpbuf b = ofpbuf_const_initializer(narm, ntohs(narm->len));
+ ofpbuf_pull(&b, sizeof *narm);
+
+ enum ofperr error = nx_pull_header(&b, &move->src.field, NULL);
+ if (error) {
+ return error;
+ }
+ error = nx_pull_header(&b, &move->dst.field, NULL);
+ if (error) {
+ return error;
+ }
+ if (!is_all_zeros(b.data, b.size)) {
+ return OFPERR_NXBRC_MUST_BE_ZERO;
+ }
+
+ return nxm_reg_move_check(move, NULL);
+}
+
+static void
+encode_REG_MOVE(const struct ofpact_reg_move *move,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ /* For OpenFlow 1.3, the choice of ONFACT_RAW13_COPY_FIELD versus
+ * NXAST_RAW_REG_MOVE is somewhat difficult. Neither one is guaranteed to
+ * be supported by every OpenFlow 1.3 implementation. It would be ideal to
+ * probe for support. Until we have that ability, we currently prefer
+ * NXAST_RAW_REG_MOVE for backward compatibility with older Open vSwitch
+ * versions. */
+ size_t start_ofs = out->size;
+ if (ofp_version >= OFP15_VERSION) {
+ struct ofp15_action_copy_field *copy = put_OFPAT15_COPY_FIELD(out);
+ copy->n_bits = htons(move->dst.n_bits);
+ copy->src_offset = htons(move->src.ofs);
+ copy->dst_offset = htons(move->dst.ofs);
+ out->size = out->size - sizeof copy->pad2;
+ nx_put_header(out, move->src.field->id, ofp_version, false);
+ nx_put_header(out, move->dst.field->id, ofp_version, false);
+ } else if (ofp_version == OFP13_VERSION
+ && move->ofpact.raw == ONFACT_RAW13_COPY_FIELD) {
+ struct onf_action_copy_field *copy = put_ONFACT13_COPY_FIELD(out);
+ copy->n_bits = htons(move->dst.n_bits);
+ copy->src_offset = htons(move->src.ofs);
+ copy->dst_offset = htons(move->dst.ofs);
+ out->size = out->size - sizeof copy->pad3;
+ nx_put_header(out, move->src.field->id, ofp_version, false);
+ nx_put_header(out, move->dst.field->id, ofp_version, false);
+ } else {
+ struct nx_action_reg_move *narm = put_NXAST_REG_MOVE(out);
+ narm->n_bits = htons(move->dst.n_bits);
+ narm->src_ofs = htons(move->src.ofs);
+ narm->dst_ofs = htons(move->dst.ofs);
+ nx_put_header(out, move->src.field->id, 0, false);
+ nx_put_header(out, move->dst.field->id, 0, false);
+ }
+ pad_ofpat(out, start_ofs);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_REG_MOVE(const char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
+ const char *full_arg = arg;
+ char *error;
+
+ error = mf_parse_subfield__(&move->src, &arg);
+ if (error) {
+ return error;
+ }
+ if (strncmp(arg, "->", 2)) {
+ return xasprintf("%s: missing `->' following source", full_arg);
+ }
+ arg += 2;
+ error = mf_parse_subfield(&move->dst, arg);
+ if (error) {
+ return error;
+ }
+
+ if (move->src.n_bits != move->dst.n_bits) {
+ return xasprintf("%s: source field is %d bits wide but destination is "
+ "%d bits wide", full_arg,
+ move->src.n_bits, move->dst.n_bits);
+ }
+ return NULL;
+}
+
+static void
+format_REG_MOVE(const struct ofpact_reg_move *a, struct ds *s)
+{
+ nxm_format_reg_move(a, s);
+}
+\f
+/* Action structure for OFPAT12_SET_FIELD. */
+struct ofp12_action_set_field {
+ ovs_be16 type; /* OFPAT12_SET_FIELD. */
+ ovs_be16 len; /* Length is padded to 64 bits. */
+
+ /* Followed by:
+ * - An OXM header, value, and (in OpenFlow 1.5+) optionally a mask.
+ * - Enough 0-bytes to pad out to a multiple of 64 bits.
+ *
+ * The "pad" member is the beginning of the above. */
+ uint8_t pad[4];
+};
+OFP_ASSERT(sizeof(struct ofp12_action_set_field) == 8);
+
+/* Action structure for NXAST_REG_LOAD.
+ *
+ * Copies value[0:n_bits] to dst[ofs:ofs+n_bits], where a[b:c] denotes the bits
+ * within 'a' numbered 'b' through 'c' (not including bit 'c'). Bit numbering
+ * starts at 0 for the least-significant bit, 1 for the next most significant
+ * bit, and so on.
+ *
+ * 'dst' is an nxm_header with nxm_hasmask=0. See the documentation for
+ * NXAST_REG_MOVE, above, for the permitted fields and for the side effects of
+ * loading them.
+ *
+ * The 'ofs' and 'n_bits' fields are combined into a single 'ofs_nbits' field
+ * to avoid enlarging the structure by another 8 bytes. To allow 'n_bits' to
+ * take a value between 1 and 64 (inclusive) while taking up only 6 bits, it is
+ * also stored as one less than its true value:
+ *
+ * 15 6 5 0
+ * +------------------------------+------------------+
+ * | ofs | n_bits - 1 |
+ * +------------------------------+------------------+
+ *
+ * The switch will reject actions for which ofs+n_bits is greater than the
+ * width of 'dst', or in which any bits in 'value' with value 2**n_bits or
+ * greater are set to 1, with error type OFPET_BAD_ACTION, code
+ * OFPBAC_BAD_ARGUMENT.
+ */
+struct nx_action_reg_load {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length is 24. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_REG_LOAD. */
+ ovs_be16 ofs_nbits; /* (ofs << 6) | (n_bits - 1). */
+ ovs_be32 dst; /* Destination register. */
+ ovs_be64 value; /* Immediate value. */
+};
+OFP_ASSERT(sizeof(struct nx_action_reg_load) == 24);
+
+/* Action structure for NXAST_REG_LOAD2.
+ *
+ * Compared to OFPAT_SET_FIELD, we can use this to set whole or partial fields
+ * in any OpenFlow version. Compared to NXAST_REG_LOAD, we can use this to set
+ * OXM experimenter fields. */
+struct nx_action_reg_load2 {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* At least 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_SET_FIELD. */
+
+ /* Followed by:
+ * - An NXM/OXM header, value, and optionally a mask.
+ * - Enough 0-bytes to pad out to a multiple of 64 bits.
+ *
+ * The "pad" member is the beginning of the above. */
+ uint8_t pad[6];
+};
+OFP_ASSERT(sizeof(struct nx_action_reg_load2) == 16);
+
+static enum ofperr
+decode_ofpat_set_field(const struct ofp12_action_set_field *oasf,
+ bool may_mask, struct ofpbuf *ofpacts)
+{
+ struct ofpbuf b = ofpbuf_const_initializer(oasf, ntohs(oasf->len));
+ ofpbuf_pull(&b, OBJECT_OFFSETOF(oasf, pad));
+
+ struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
+ enum ofperr error = nx_pull_entry(&b, &sf->field, &sf->value,
+ may_mask ? &sf->mask : NULL);
+ if (error) {
+ return (error == OFPERR_OFPBMC_BAD_MASK
+ ? OFPERR_OFPBAC_BAD_SET_MASK
+ : error);
+ }
+ if (!may_mask) {
+ memset(&sf->mask, 0xff, sf->field->n_bytes);
+ }
+
+ if (!is_all_zeros(b.data, b.size)) {
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+
+ /* OpenFlow says specifically that one may not set OXM_OF_IN_PORT via
+ * Set-Field. */
+ if (sf->field->id == MFF_IN_PORT_OXM) {
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+
+ /* oxm_length is now validated to be compatible with mf_value. */
+ if (!sf->field->writable) {
+ VLOG_WARN_RL(&rl, "destination field %s is not writable",
+ sf->field->name);
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+
+ /* The value must be valid for match. OpenFlow 1.5 also says,
+ * "In an OXM_OF_VLAN_VID set-field action, the OFPVID_PRESENT bit must be
+ * a 1-bit in oxm_value and in oxm_mask." */
+ if (!mf_is_value_valid(sf->field, &sf->value)
+ || (sf->field->id == MFF_VLAN_VID
+ && (!(sf->mask.be16 & htons(OFPVID12_PRESENT))
+ || !(sf->value.be16 & htons(OFPVID12_PRESENT))))) {
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ mf_format(sf->field, &sf->value, NULL, &ds);
+ VLOG_WARN_RL(&rl, "Invalid value for set field %s: %s",
+ sf->field->name, ds_cstr(&ds));
+ ds_destroy(&ds);
+
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+ return 0;
+}
+
+static enum ofperr
+decode_OFPAT_RAW12_SET_FIELD(const struct ofp12_action_set_field *oasf,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ return decode_ofpat_set_field(oasf, false, ofpacts);
+}
+
+static enum ofperr
+decode_OFPAT_RAW15_SET_FIELD(const struct ofp12_action_set_field *oasf,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ return decode_ofpat_set_field(oasf, true, ofpacts);
+}
+
+static enum ofperr
+decode_NXAST_RAW_REG_LOAD(const struct nx_action_reg_load *narl,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_set_field *sf = ofpact_put_reg_load(out);
+ struct mf_subfield dst;
+ enum ofperr error;
+
+ sf->ofpact.raw = NXAST_RAW_REG_LOAD;
+
+ dst.field = mf_from_nxm_header(ntohl(narl->dst));
+ dst.ofs = nxm_decode_ofs(narl->ofs_nbits);
+ dst.n_bits = nxm_decode_n_bits(narl->ofs_nbits);
+ error = mf_check_dst(&dst, NULL);
+ if (error) {
+ return error;
+ }
+
+ /* Reject 'narl' if a bit numbered 'n_bits' or higher is set to 1 in
+ * narl->value. */
+ if (dst.n_bits < 64 && ntohll(narl->value) >> dst.n_bits) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ sf->field = dst.field;
+ bitwise_put(ntohll(narl->value),
+ &sf->value, dst.field->n_bytes, dst.ofs,
+ dst.n_bits);
+ bitwise_put(UINT64_MAX,
+ &sf->mask, dst.field->n_bytes, dst.ofs,
+ dst.n_bits);
+
+ return 0;
+}
+
+static enum ofperr
+decode_NXAST_RAW_REG_LOAD2(const struct nx_action_reg_load2 *narl,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_set_field *sf = ofpact_put_SET_FIELD(out);
+ sf->ofpact.raw = NXAST_RAW_REG_LOAD2;
+
+ struct ofpbuf b = ofpbuf_const_initializer(narl, ntohs(narl->len));
+ ofpbuf_pull(&b, OBJECT_OFFSETOF(narl, pad));
+
+ enum ofperr error = nx_pull_entry(&b, &sf->field, &sf->value, &sf->mask);
+ if (error) {
+ return error;
+ }
+ if (!is_all_zeros(b.data, b.size)) {
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+
+ if (!sf->field->writable) {
+ VLOG_WARN_RL(&rl, "destination field %s is not writable",
+ sf->field->name);
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+ return 0;
+}
+
+static void
+ofpact_put_set_field(struct ofpbuf *openflow, enum ofp_version ofp_version,
+ enum mf_field_id field, uint64_t value_)
+{
+ struct ofp12_action_set_field *oasf OVS_UNUSED;
+ int n_bytes = mf_from_id(field)->n_bytes;
+ size_t start_ofs = openflow->size;
+ union mf_value value;
+
+ value.be64 = htonll(value_ << (8 * (8 - n_bytes)));
+
+ oasf = put_OFPAT12_SET_FIELD(openflow);
+ openflow->size = openflow->size - sizeof oasf->pad;
+ nx_put_entry(openflow, field, ofp_version, &value, NULL);
+ pad_ofpat(openflow, start_ofs);
+}
+
+static bool
+next_load_segment(const struct ofpact_set_field *sf,
+ struct mf_subfield *dst, uint64_t *value)
+{
+ int n_bits = sf->field->n_bits;
+ int n_bytes = sf->field->n_bytes;
+ int start = dst->ofs + dst->n_bits;
+
+ if (start < n_bits) {
+ dst->field = sf->field;
+ dst->ofs = bitwise_scan(&sf->mask, n_bytes, 1, start, n_bits);
+ if (dst->ofs < n_bits) {
+ dst->n_bits = bitwise_scan(&sf->mask, n_bytes, 0, dst->ofs + 1,
+ MIN(dst->ofs + 64, n_bits)) - dst->ofs;
+ *value = bitwise_get(&sf->value, n_bytes, dst->ofs, dst->n_bits);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Convert 'sf' to a series of REG_LOADs. */
+static void
+set_field_to_nxast(const struct ofpact_set_field *sf, struct ofpbuf *openflow)
+{
+ /* If 'sf' cannot be encoded as NXAST_REG_LOAD because it requires an
+ * experimenter OXM or is variable length (or if it came in as
+ * NXAST_REG_LOAD2), encode as NXAST_REG_LOAD2. Otherwise use
+ * NXAST_REG_LOAD, which is backward compatible. */
+ if (sf->ofpact.raw == NXAST_RAW_REG_LOAD2
+ || !mf_nxm_header(sf->field->id) || sf->field->variable_len) {
+ struct nx_action_reg_load2 *narl OVS_UNUSED;
+ size_t start_ofs = openflow->size;
+
+ narl = put_NXAST_REG_LOAD2(openflow);
+ openflow->size = openflow->size - sizeof narl->pad;
+ nx_put_entry(openflow, sf->field->id, 0, &sf->value, &sf->mask);
+ pad_ofpat(openflow, start_ofs);
+ } else {
+ struct mf_subfield dst;
+ uint64_t value;
+
+ dst.ofs = dst.n_bits = 0;
+ while (next_load_segment(sf, &dst, &value)) {
+ struct nx_action_reg_load *narl = put_NXAST_REG_LOAD(openflow);
+ narl->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs, dst.n_bits);
+ narl->dst = htonl(mf_nxm_header(dst.field->id));
+ narl->value = htonll(value);
+ }
+ }
+}
+
+/* Convert 'sf', which must set an entire field, to standard OpenFlow 1.0/1.1
+ * actions, if we can, falling back to Nicira extensions if we must.
+ *
+ * We check only meta-flow types that can appear within set field actions and
+ * that have a mapping to compatible action types. These struct mf_field
+ * definitions have a defined OXM or NXM header value and specify the field as
+ * writable. */
+static void
+set_field_to_legacy_openflow(const struct ofpact_set_field *sf,
+ enum ofp_version ofp_version,
+ struct ofpbuf *out)
+{
+ switch ((int) sf->field->id) {
+ case MFF_VLAN_TCI: {
+ ovs_be16 tci = sf->value.be16;
+ bool cfi = (tci & htons(VLAN_CFI)) != 0;
+ uint16_t vid = vlan_tci_to_vid(tci);
+ uint8_t pcp = vlan_tci_to_pcp(tci);
+
+ if (ofp_version < OFP11_VERSION) {
+ /* NXM_OF_VLAN_TCI to OpenFlow 1.0 mapping:
+ *
+ * If CFI=1, Add or modify VLAN VID & PCP.
+ * If CFI=0, strip VLAN header, if any.
+ */
+ if (cfi) {
+ put_OFPAT10_SET_VLAN_VID(out, vid);
+ put_OFPAT10_SET_VLAN_PCP(out, pcp);
+ } else {
+ put_OFPAT10_STRIP_VLAN(out);
+ }
+ } else {
+ /* NXM_OF_VLAN_TCI to OpenFlow 1.1 mapping:
+ *
+ * If CFI=1, Add or modify VLAN VID & PCP.
+ * OpenFlow 1.1 set actions only apply if the packet
+ * already has VLAN tags. To be sure that is the case
+ * we have to push a VLAN header. As we do not support
+ * multiple layers of VLANs, this is a no-op, if a VLAN
+ * header already exists. This may backfire, however,
+ * when we start supporting multiple layers of VLANs.
+ * If CFI=0, strip VLAN header, if any.
+ */
+ if (cfi) {
+ /* Push a VLAN tag, if one was not seen at action validation
+ * time. */
+ if (!sf->flow_has_vlan) {
+ put_OFPAT11_PUSH_VLAN(out, htons(ETH_TYPE_VLAN_8021Q));
+ }
+ put_OFPAT11_SET_VLAN_VID(out, vid);
+ put_OFPAT11_SET_VLAN_PCP(out, pcp);
+ } else {
+ /* If the flow did not match on vlan, we have no way of
+ * knowing if the vlan tag exists, so we must POP just to be
+ * sure. */
+ put_OFPAT11_POP_VLAN(out);
+ }
+ }
+ break;
+ }
+
+ case MFF_VLAN_VID: {
+ uint16_t vid = ntohs(sf->value.be16) & VLAN_VID_MASK;
+ if (ofp_version == OFP10_VERSION) {
+ put_OFPAT10_SET_VLAN_VID(out, vid);
+ } else {
+ put_OFPAT11_SET_VLAN_VID(out, vid);
+ }
+ break;
+ }
+
+ case MFF_VLAN_PCP:
+ if (ofp_version == OFP10_VERSION) {
+ put_OFPAT10_SET_VLAN_PCP(out, sf->value.u8);
+ } else {
+ put_OFPAT11_SET_VLAN_PCP(out, sf->value.u8);
+ }
+ break;
+
+ case MFF_ETH_SRC:
+ put_OFPAT_SET_DL_SRC(out, ofp_version)->dl_addr = sf->value.mac;
+ break;
+
+ case MFF_ETH_DST:
+ put_OFPAT_SET_DL_DST(out, ofp_version)->dl_addr = sf->value.mac;
+ break;
+
+ case MFF_IPV4_SRC:
+ put_OFPAT_SET_NW_SRC(out, ofp_version, sf->value.be32);
+ break;
+
+ case MFF_IPV4_DST:
+ put_OFPAT_SET_NW_DST(out, ofp_version, sf->value.be32);
+ break;
+
+ case MFF_IP_DSCP:
+ put_OFPAT_SET_NW_TOS(out, ofp_version, sf->value.u8);
+ break;
+
+ case MFF_IP_DSCP_SHIFTED:
+ put_OFPAT_SET_NW_TOS(out, ofp_version, sf->value.u8 << 2);
+ break;
+
+ case MFF_TCP_SRC:
+ case MFF_UDP_SRC:
+ put_OFPAT_SET_TP_SRC(out, sf->value.be16);
+ break;
+
+ case MFF_TCP_DST:
+ case MFF_UDP_DST:
+ put_OFPAT_SET_TP_DST(out, sf->value.be16);
+ break;
+
+ default:
+ set_field_to_nxast(sf, out);
+ break;
+ }
+}
+
+static void
+set_field_to_set_field(const struct ofpact_set_field *sf,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ struct ofp12_action_set_field *oasf OVS_UNUSED;
+ size_t start_ofs = out->size;
+
+ oasf = put_OFPAT12_SET_FIELD(out);
+ out->size = out->size - sizeof oasf->pad;
+ nx_put_entry(out, sf->field->id, ofp_version, &sf->value, &sf->mask);
+ pad_ofpat(out, start_ofs);
+}
+
+static void
+encode_SET_FIELD(const struct ofpact_set_field *sf,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (ofp_version >= OFP15_VERSION) {
+ /* OF1.5+ only has Set-Field (reg_load is redundant so we drop it
+ * entirely). */
+ set_field_to_set_field(sf, ofp_version, out);
+ } else if (sf->ofpact.raw == NXAST_RAW_REG_LOAD ||
+ sf->ofpact.raw == NXAST_RAW_REG_LOAD2) {
+ /* It came in as reg_load, send it out the same way. */
+ set_field_to_nxast(sf, out);
+ } else if (ofp_version < OFP12_VERSION) {
+ /* OpenFlow 1.0 and 1.1 don't have Set-Field. */
+ set_field_to_legacy_openflow(sf, ofp_version, out);
+ } else if (is_all_ones((const uint8_t *) &sf->mask, sf->field->n_bytes)) {
+ /* We're encoding to OpenFlow 1.2, 1.3, or 1.4. The action sets an
+ * entire field, so encode it as OFPAT_SET_FIELD. */
+ set_field_to_set_field(sf, ofp_version, out);
+ } else {
+ /* We're encoding to OpenFlow 1.2, 1.3, or 1.4. The action cannot be
+ * encoded as OFPAT_SET_FIELD because it does not set an entire field,
+ * so encode it as reg_load. */
+ set_field_to_nxast(sf, out);
+ }
+}
+
+/* Parses the input argument 'arg' into the key, value, and delimiter
+ * components that are common across the reg_load and set_field action format.
+ *
+ * With an argument like "1->metadata", sets the following pointers to
+ * point within 'arg':
+ * key: "metadata"
+ * value: "1"
+ * delim: "->"
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+set_field_split_str(char *arg, char **key, char **value, char **delim)
+{
+ char *value_end;
+
+ *value = arg;
+ value_end = strstr(arg, "->");
+ *key = value_end + strlen("->");
+ if (delim) {
+ *delim = value_end;
+ }
+
+ if (!value_end) {
+ return xasprintf("%s: missing `->'", arg);
+ }
+ if (strlen(value_end) <= strlen("->")) {
+ return xasprintf("%s: missing field name following `->'", arg);
+ }
+
+ return NULL;
+}
+
+/* Parses a "set_field" action with argument 'arg', appending the parsed
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+set_field_parse__(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols)
+{
+ struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
+ char *value;
+ char *delim;
+ char *key;
+ const struct mf_field *mf;
+ char *error;
+
+ error = set_field_split_str(arg, &key, &value, &delim);
+ if (error) {
+ return error;
+ }
+
+ mf = mf_from_name(key);
+ if (!mf) {
+ return xasprintf("%s is not a valid OXM field name", key);
+ }
+ if (!mf->writable) {
+ return xasprintf("%s is read-only", key);
+ }
+ sf->field = mf;
+ delim[0] = '\0';
+ error = mf_parse(mf, value, &sf->value, &sf->mask);
+ if (error) {
+ return error;
+ }
+
+ if (!mf_is_value_valid(mf, &sf->value)) {
+ return xasprintf("%s is not a valid value for field %s", value, key);
+ }
+
+ *usable_protocols &= mf->usable_protocols_exact;
+ return NULL;
+}
+
+/* Parses 'arg' as the argument to a "set_field" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_SET_FIELD(const char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols)
+{
+ char *copy = xstrdup(arg);
+ char *error = set_field_parse__(copy, ofpacts, usable_protocols);
+ free(copy);
+ return error;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_reg_load(char *arg, struct ofpbuf *ofpacts)
+{
+ struct ofpact_set_field *sf = ofpact_put_reg_load(ofpacts);
+ struct mf_subfield dst;
+ char *key, *value_str;
+ union mf_value value;
+ char *error;
+
+ error = set_field_split_str(arg, &key, &value_str, NULL);
+ if (error) {
+ return error;
+ }
+
+ error = mf_parse_subfield(&dst, key);
+ if (error) {
+ return error;
+ }
+
+ if (parse_int_string(value_str, (uint8_t *)&value, dst.field->n_bytes,
+ &key)) {
+ return xasprintf("%s: cannot parse integer value", arg);
+ }
+
+ if (!bitwise_is_all_zeros(&value, dst.field->n_bytes, dst.n_bits,
+ dst.field->n_bytes * 8 - dst.n_bits)) {
+ struct ds ds;
+
+ ds_init(&ds);
+ mf_format(dst.field, &value, NULL, &ds);
+ error = xasprintf("%s: value %s does not fit into %d bits",
+ arg, ds_cstr(&ds), dst.n_bits);
+ ds_destroy(&ds);
+ return error;
+ }
+
+ sf->field = dst.field;
+ memset(&sf->value, 0, sizeof sf->value);
+ bitwise_copy(&value, dst.field->n_bytes, 0, &sf->value,
+ dst.field->n_bytes, dst.ofs, dst.n_bits);
+ bitwise_one(&sf->mask, dst.field->n_bytes, dst.ofs, dst.n_bits);
+
+ return NULL;
+}
+
+static void
+format_SET_FIELD(const struct ofpact_set_field *a, struct ds *s)
+{
+ if (a->ofpact.raw == NXAST_RAW_REG_LOAD) {
+ struct mf_subfield dst;
+ uint64_t value;
+
+ dst.ofs = dst.n_bits = 0;
+ while (next_load_segment(a, &dst, &value)) {
+ ds_put_format(s, "load:%#"PRIx64"->", value);
+ mf_format_subfield(&dst, s);
+ ds_put_char(s, ',');
+ }
+ ds_chomp(s, ',');
+ } else {
+ ds_put_cstr(s, "set_field:");
+ mf_format(a->field, &a->value, &a->mask, s);
+ ds_put_format(s, "->%s", a->field->name);
+ }
+}
+
+/* Appends an OFPACT_SET_FIELD ofpact to 'ofpacts' and returns it. The ofpact
+ * is marked such that, if possible, it will be translated to OpenFlow as
+ * NXAST_REG_LOAD extension actions rather than OFPAT_SET_FIELD, either because
+ * that was the way that the action was expressed when it came into OVS or for
+ * backward compatibility. */
+struct ofpact_set_field *
+ofpact_put_reg_load(struct ofpbuf *ofpacts)
+{
+ struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
+ sf->ofpact.raw = NXAST_RAW_REG_LOAD;
+ return sf;
+}
+\f
+/* Action structure for NXAST_STACK_PUSH and NXAST_STACK_POP.
+ *
+ * Pushes (or pops) field[offset: offset + n_bits] to (or from)
+ * top of the stack.
+ */
+struct nx_action_stack {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length is 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_STACK_PUSH or NXAST_STACK_POP. */
+ ovs_be16 offset; /* Bit offset into the field. */
+ /* Followed by:
+ * - OXM/NXM header for field to push or pop (4 or 8 bytes).
+ * - ovs_be16 'n_bits', the number of bits to extract from the field.
+ * - Enough 0-bytes to pad out the action to 24 bytes. */
+ uint8_t pad[12]; /* See above. */
+};
+OFP_ASSERT(sizeof(struct nx_action_stack) == 24);
+
+static enum ofperr
+decode_stack_action(const struct nx_action_stack *nasp,
+ struct ofpact_stack *stack_action)
+{
+ stack_action->subfield.ofs = ntohs(nasp->offset);
+
+ struct ofpbuf b = ofpbuf_const_initializer(nasp, sizeof *nasp);
+ ofpbuf_pull(&b, OBJECT_OFFSETOF(nasp, pad));
+ enum ofperr error = nx_pull_header(&b, &stack_action->subfield.field,
+ NULL);
+ if (error) {
+ return error;
+ }
+ stack_action->subfield.n_bits = ntohs(*(const ovs_be16 *) b.data);
+ ofpbuf_pull(&b, 2);
+ if (!is_all_zeros(b.data, b.size)) {
+ return OFPERR_NXBRC_MUST_BE_ZERO;
+ }
+
+ return 0;
+}
+
+static enum ofperr
+decode_NXAST_RAW_STACK_PUSH(const struct nx_action_stack *nasp,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct ofpact_stack *push = ofpact_put_STACK_PUSH(ofpacts);
+ enum ofperr error = decode_stack_action(nasp, push);
+ return error ? error : nxm_stack_push_check(push, NULL);
+}
+
+static enum ofperr
+decode_NXAST_RAW_STACK_POP(const struct nx_action_stack *nasp,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct ofpact_stack *pop = ofpact_put_STACK_POP(ofpacts);
+ enum ofperr error = decode_stack_action(nasp, pop);
+ return error ? error : nxm_stack_pop_check(pop, NULL);
+}
+
+static void
+encode_STACK_op(const struct ofpact_stack *stack_action,
+ struct nx_action_stack *nasp)
+{
+ struct ofpbuf b;
+ ovs_be16 n_bits;
+
+ nasp->offset = htons(stack_action->subfield.ofs);
+
+ ofpbuf_use_stack(&b, nasp, ntohs(nasp->len));
+ ofpbuf_put_uninit(&b, OBJECT_OFFSETOF(nasp, pad));
+ nx_put_header(&b, stack_action->subfield.field->id, 0, false);
+ n_bits = htons(stack_action->subfield.n_bits);
+ ofpbuf_put(&b, &n_bits, sizeof n_bits);
+}
+
+static void
+encode_STACK_PUSH(const struct ofpact_stack *stack,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ encode_STACK_op(stack, put_NXAST_STACK_PUSH(out));
+}
+
+static void
+encode_STACK_POP(const struct ofpact_stack *stack,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ encode_STACK_op(stack, put_NXAST_STACK_POP(out));
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_STACK_PUSH(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ return nxm_parse_stack_action(ofpact_put_STACK_PUSH(ofpacts), arg);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_STACK_POP(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ return nxm_parse_stack_action(ofpact_put_STACK_POP(ofpacts), arg);
+}
+
+static void
+format_STACK_PUSH(const struct ofpact_stack *a, struct ds *s)
+{
+ nxm_format_stack_push(a, s);
+}
+
+static void
+format_STACK_POP(const struct ofpact_stack *a, struct ds *s)
+{
+ nxm_format_stack_pop(a, s);
+}
+\f
+/* Action structure for NXAST_DEC_TTL_CNT_IDS.
+ *
+ * If the packet is not IPv4 or IPv6, does nothing. For IPv4 or IPv6, if the
+ * TTL or hop limit is at least 2, decrements it by 1. Otherwise, if TTL or
+ * hop limit is 0 or 1, sends a packet-in to the controllers with each of the
+ * 'n_controllers' controller IDs specified in 'cnt_ids'.
+ *
+ * (This differs from NXAST_DEC_TTL in that for NXAST_DEC_TTL the packet-in is
+ * sent only to controllers with id 0.)
+ */
+struct nx_action_cnt_ids {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length including slaves. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_DEC_TTL_CNT_IDS. */
+
+ ovs_be16 n_controllers; /* Number of controllers. */
+ uint8_t zeros[4]; /* Must be zero. */
+
+ /* Followed by 1 or more controller ids.
+ *
+ * uint16_t cnt_ids[]; // Controller ids.
+ * uint8_t pad[]; // Must be 0 to 8-byte align cnt_ids[].
+ */
+};
+OFP_ASSERT(sizeof(struct nx_action_cnt_ids) == 16);
+
+static enum ofperr
+decode_OFPAT_RAW_DEC_NW_TTL(struct ofpbuf *out)
+{
+ uint16_t id = 0;
+ struct ofpact_cnt_ids *ids;
+ enum ofperr error = 0;
+
+ ids = ofpact_put_DEC_TTL(out);
+ ids->n_controllers = 1;
+ ofpbuf_put(out, &id, sizeof id);
+ ids = out->header;
+ ofpact_finish(out, &ids->ofpact);
+ return error;
+}
+
+static enum ofperr
+decode_NXAST_RAW_DEC_TTL_CNT_IDS(const struct nx_action_cnt_ids *nac_ids,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_cnt_ids *ids;
+ size_t ids_size;
+ int i;
+
+ ids = ofpact_put_DEC_TTL(out);
+ ids->ofpact.raw = NXAST_RAW_DEC_TTL_CNT_IDS;
+ ids->n_controllers = ntohs(nac_ids->n_controllers);
+ ids_size = ntohs(nac_ids->len) - sizeof *nac_ids;
+
+ if (!is_all_zeros(nac_ids->zeros, sizeof nac_ids->zeros)) {
+ return OFPERR_NXBRC_MUST_BE_ZERO;
+ }
+
+ if (ids_size < ids->n_controllers * sizeof(ovs_be16)) {
+ VLOG_WARN_RL(&rl, "Nicira action dec_ttl_cnt_ids only has %"PRIuSIZE" "
+ "bytes allocated for controller ids. %"PRIuSIZE" bytes "
+ "are required for %"PRIu16" controllers.",
+ ids_size, ids->n_controllers * sizeof(ovs_be16),
+ ids->n_controllers);
+ return OFPERR_OFPBAC_BAD_LEN;
+ }
+
+ for (i = 0; i < ids->n_controllers; i++) {
+ uint16_t id = ntohs(((ovs_be16 *)(nac_ids + 1))[i]);
+ ofpbuf_put(out, &id, sizeof id);
+ ids = out->header;
+ }
+
+ ofpact_finish(out, &ids->ofpact);
+
+ return 0;
+}
+
+static void
+encode_DEC_TTL(const struct ofpact_cnt_ids *dec_ttl,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (dec_ttl->ofpact.raw == NXAST_RAW_DEC_TTL_CNT_IDS
+ || dec_ttl->n_controllers != 1
+ || dec_ttl->cnt_ids[0] != 0) {
+ struct nx_action_cnt_ids *nac_ids = put_NXAST_DEC_TTL_CNT_IDS(out);
+ int ids_len = ROUND_UP(2 * dec_ttl->n_controllers, OFP_ACTION_ALIGN);
+ ovs_be16 *ids;
+ size_t i;
+
+ nac_ids->len = htons(ntohs(nac_ids->len) + ids_len);
+ nac_ids->n_controllers = htons(dec_ttl->n_controllers);
+
+ ids = ofpbuf_put_zeros(out, ids_len);
+ for (i = 0; i < dec_ttl->n_controllers; i++) {
+ ids[i] = htons(dec_ttl->cnt_ids[i]);
+ }
+ } else {
+ put_OFPAT_DEC_NW_TTL(out, ofp_version);
+ }
+}
+
+static void
+parse_noargs_dec_ttl(struct ofpbuf *ofpacts)
+{
+ struct ofpact_cnt_ids *ids;
+ uint16_t id = 0;
+
+ ofpact_put_DEC_TTL(ofpacts);
+ ofpbuf_put(ofpacts, &id, sizeof id);
+ ids = ofpacts->header;
+ ids->n_controllers++;
+ ofpact_finish(ofpacts, &ids->ofpact);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_DEC_TTL(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ if (*arg == '\0') {
+ parse_noargs_dec_ttl(ofpacts);
+ } else {
+ struct ofpact_cnt_ids *ids;
+ char *cntr;
+
+ ids = ofpact_put_DEC_TTL(ofpacts);
+ ids->ofpact.raw = NXAST_RAW_DEC_TTL_CNT_IDS;
+ for (cntr = strtok_r(arg, ", ", &arg); cntr != NULL;
+ cntr = strtok_r(NULL, ", ", &arg)) {
+ uint16_t id = atoi(cntr);
+
+ ofpbuf_put(ofpacts, &id, sizeof id);
+ ids = ofpacts->header;
+ ids->n_controllers++;
+ }
+ if (!ids->n_controllers) {
+ return xstrdup("dec_ttl_cnt_ids: expected at least one controller "
+ "id.");
+ }
+ ofpact_finish(ofpacts, &ids->ofpact);
+ }
+ return NULL;
+}
+
+static void
+format_DEC_TTL(const struct ofpact_cnt_ids *a, struct ds *s)
+{
+ size_t i;
+
+ ds_put_cstr(s, "dec_ttl");
+ if (a->ofpact.raw == NXAST_RAW_DEC_TTL_CNT_IDS) {
+ ds_put_cstr(s, "(");
+ for (i = 0; i < a->n_controllers; i++) {
+ if (i) {
+ ds_put_cstr(s, ",");
+ }
+ ds_put_format(s, "%"PRIu16, a->cnt_ids[i]);
+ }
+ ds_put_cstr(s, ")");
+ }
+}
+\f
+/* Set MPLS label actions. */
+
+static enum ofperr
+decode_OFPAT_RAW_SET_MPLS_LABEL(ovs_be32 label,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ ofpact_put_SET_MPLS_LABEL(out)->label = label;
+ return 0;
+}
+
+static void
+encode_SET_MPLS_LABEL(const struct ofpact_mpls_label *label,
+ enum ofp_version ofp_version,
+ struct ofpbuf *out)
+{
+ if (ofp_version < OFP12_VERSION) {
+ put_OFPAT_SET_MPLS_LABEL(out, ofp_version, label->label);
+ } else {
+ ofpact_put_set_field(out, ofp_version, MFF_MPLS_LABEL,
+ ntohl(label->label));
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_SET_MPLS_LABEL(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_mpls_label *mpls_label = ofpact_put_SET_MPLS_LABEL(ofpacts);
+ if (*arg == '\0') {
+ return xstrdup("set_mpls_label: expected label.");
+ }
+
+ mpls_label->label = htonl(atoi(arg));
+ return NULL;
+}
+
+static void
+format_SET_MPLS_LABEL(const struct ofpact_mpls_label *a, struct ds *s)
+{
+ ds_put_format(s, "set_mpls_label(%"PRIu32")", ntohl(a->label));
+}
+\f
+/* Set MPLS TC actions. */
+
+static enum ofperr
+decode_OFPAT_RAW_SET_MPLS_TC(uint8_t tc,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ ofpact_put_SET_MPLS_TC(out)->tc = tc;
+ return 0;
+}
+
+static void
+encode_SET_MPLS_TC(const struct ofpact_mpls_tc *tc,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (ofp_version < OFP12_VERSION) {
+ put_OFPAT_SET_MPLS_TC(out, ofp_version, tc->tc);
+ } else {
+ ofpact_put_set_field(out, ofp_version, MFF_MPLS_TC, tc->tc);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_SET_MPLS_TC(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_mpls_tc *mpls_tc = ofpact_put_SET_MPLS_TC(ofpacts);
+
+ if (*arg == '\0') {
+ return xstrdup("set_mpls_tc: expected tc.");
+ }
+
+ mpls_tc->tc = atoi(arg);
+ return NULL;
+}
+
+static void
+format_SET_MPLS_TC(const struct ofpact_mpls_tc *a, struct ds *s)
+{
+ ds_put_format(s, "set_mpls_ttl(%"PRIu8")", a->tc);
+}
+\f
+/* Set MPLS TTL actions. */
+
+static enum ofperr
+decode_OFPAT_RAW_SET_MPLS_TTL(uint8_t ttl,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ ofpact_put_SET_MPLS_TTL(out)->ttl = ttl;
+ return 0;
+}
+
+static void
+encode_SET_MPLS_TTL(const struct ofpact_mpls_ttl *ttl,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ put_OFPAT_SET_MPLS_TTL(out, ofp_version, ttl->ttl);
+}
+
+/* Parses 'arg' as the argument to a "set_mpls_ttl" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_SET_MPLS_TTL(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_mpls_ttl *mpls_ttl = ofpact_put_SET_MPLS_TTL(ofpacts);
+
+ if (*arg == '\0') {
+ return xstrdup("set_mpls_ttl: expected ttl.");
+ }
+
+ mpls_ttl->ttl = atoi(arg);
+ return NULL;
+}
+
+static void
+format_SET_MPLS_TTL(const struct ofpact_mpls_ttl *a, struct ds *s)
+{
+ ds_put_format(s, "set_mpls_ttl(%"PRIu8")", a->ttl);
+}
+\f
+/* Decrement MPLS TTL actions. */
+
+static enum ofperr
+decode_OFPAT_RAW_DEC_MPLS_TTL(struct ofpbuf *out)
+{
+ ofpact_put_DEC_MPLS_TTL(out);
+ return 0;
+}
+
+static void
+encode_DEC_MPLS_TTL(const struct ofpact_null *null OVS_UNUSED,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ put_OFPAT_DEC_MPLS_TTL(out, ofp_version);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_DEC_MPLS_TTL(char *arg OVS_UNUSED, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ ofpact_put_DEC_MPLS_TTL(ofpacts);
+ return NULL;
+}
+
+static void
+format_DEC_MPLS_TTL(const struct ofpact_null *a OVS_UNUSED, struct ds *s)
+{
+ ds_put_cstr(s, "dec_mpls_ttl");
+}
+\f
+/* Push MPLS label action. */
+
+static enum ofperr
+decode_OFPAT_RAW_PUSH_MPLS(ovs_be16 ethertype,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_push_mpls *oam;
+
+ if (!eth_type_mpls(ethertype)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ oam = ofpact_put_PUSH_MPLS(out);
+ oam->ethertype = ethertype;
+
+ return 0;
+}
+
+static void
+encode_PUSH_MPLS(const struct ofpact_push_mpls *push_mpls,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ put_OFPAT_PUSH_MPLS(out, ofp_version, push_mpls->ethertype);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_PUSH_MPLS(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ uint16_t ethertype;
+ char *error;
+
+ error = str_to_u16(arg, "push_mpls", ðertype);
+ if (!error) {
+ ofpact_put_PUSH_MPLS(ofpacts)->ethertype = htons(ethertype);
+ }
+ return error;
+}
+
+static void
+format_PUSH_MPLS(const struct ofpact_push_mpls *a, struct ds *s)
+{
+ ds_put_format(s, "push_mpls:0x%04"PRIx16, ntohs(a->ethertype));
+}
+\f
+/* Pop MPLS label action. */
+
+static enum ofperr
+decode_OFPAT_RAW_POP_MPLS(ovs_be16 ethertype,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ ofpact_put_POP_MPLS(out)->ethertype = ethertype;
+ return 0;
+}
+
+static void
+encode_POP_MPLS(const struct ofpact_pop_mpls *pop_mpls,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ put_OFPAT_POP_MPLS(out, ofp_version, pop_mpls->ethertype);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_POP_MPLS(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ uint16_t ethertype;
+ char *error;
+
+ error = str_to_u16(arg, "pop_mpls", ðertype);
+ if (!error) {
+ ofpact_put_POP_MPLS(ofpacts)->ethertype = htons(ethertype);
+ }
+ return error;
+}
+
+static void
+format_POP_MPLS(const struct ofpact_pop_mpls *a, struct ds *s)
+{
+ ds_put_format(s, "pop_mpls:0x%04"PRIx16, ntohs(a->ethertype));
+}
+\f
+/* Set tunnel ID actions. */
+
+static enum ofperr
+decode_NXAST_RAW_SET_TUNNEL(uint32_t tun_id,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_tunnel *tunnel = ofpact_put_SET_TUNNEL(out);
+ tunnel->ofpact.raw = NXAST_RAW_SET_TUNNEL;
+ tunnel->tun_id = tun_id;
+ return 0;
+}
+
+static enum ofperr
+decode_NXAST_RAW_SET_TUNNEL64(uint64_t tun_id,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_tunnel *tunnel = ofpact_put_SET_TUNNEL(out);
+ tunnel->ofpact.raw = NXAST_RAW_SET_TUNNEL64;
+ tunnel->tun_id = tun_id;
+ return 0;
+}
+
+static void
+encode_SET_TUNNEL(const struct ofpact_tunnel *tunnel,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ uint64_t tun_id = tunnel->tun_id;
+
+ if (ofp_version < OFP12_VERSION) {
+ if (tun_id <= UINT32_MAX
+ && tunnel->ofpact.raw != NXAST_RAW_SET_TUNNEL64) {
+ put_NXAST_SET_TUNNEL(out, tun_id);
+ } else {
+ put_NXAST_SET_TUNNEL64(out, tun_id);
+ }
+ } else {
+ ofpact_put_set_field(out, ofp_version, MFF_TUN_ID, tun_id);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_set_tunnel(char *arg, struct ofpbuf *ofpacts,
+ enum ofp_raw_action_type raw)
+{
+ struct ofpact_tunnel *tunnel;
+
+ tunnel = ofpact_put_SET_TUNNEL(ofpacts);
+ tunnel->ofpact.raw = raw;
+ return str_to_u64(arg, &tunnel->tun_id);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_SET_TUNNEL(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ return parse_set_tunnel(arg, ofpacts, NXAST_RAW_SET_TUNNEL);
+}
+
+static void
+format_SET_TUNNEL(const struct ofpact_tunnel *a, struct ds *s)
+{
+ ds_put_format(s, "set_tunnel%s:%#"PRIx64,
+ (a->tun_id > UINT32_MAX
+ || a->ofpact.raw == NXAST_RAW_SET_TUNNEL64 ? "64" : ""),
+ a->tun_id);
+}
+\f
+/* Set queue action. */
+
+static enum ofperr
+decode_OFPAT_RAW_SET_QUEUE(uint32_t queue_id,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ ofpact_put_SET_QUEUE(out)->queue_id = queue_id;
+ return 0;
+}
+
+static void
+encode_SET_QUEUE(const struct ofpact_queue *queue,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ put_OFPAT_SET_QUEUE(out, ofp_version, queue->queue_id);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_SET_QUEUE(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ return str_to_u32(arg, &ofpact_put_SET_QUEUE(ofpacts)->queue_id);
+}
+
+static void
+format_SET_QUEUE(const struct ofpact_queue *a, struct ds *s)
+{
+ ds_put_format(s, "set_queue:%"PRIu32, a->queue_id);
+}
+\f
+/* Pop queue action. */
+
+static enum ofperr
+decode_NXAST_RAW_POP_QUEUE(struct ofpbuf *out)
+{
+ ofpact_put_POP_QUEUE(out);
+ return 0;
+}
+
+static void
+encode_POP_QUEUE(const struct ofpact_null *null OVS_UNUSED,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ put_NXAST_POP_QUEUE(out);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_POP_QUEUE(const char *arg OVS_UNUSED, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ ofpact_put_POP_QUEUE(ofpacts);
+ return NULL;
+}
+
+static void
+format_POP_QUEUE(const struct ofpact_null *a OVS_UNUSED, struct ds *s)
+{
+ ds_put_cstr(s, "pop_queue");
+}
+\f
+/* Action structure for NXAST_FIN_TIMEOUT.
+ *
+ * This action changes the idle timeout or hard timeout, or both, of this
+ * OpenFlow rule when the rule matches a TCP packet with the FIN or RST flag.
+ * When such a packet is observed, the action reduces the rule's idle timeout
+ * to 'fin_idle_timeout' and its hard timeout to 'fin_hard_timeout'. This
+ * action has no effect on an existing timeout that is already shorter than the
+ * one that the action specifies. A 'fin_idle_timeout' or 'fin_hard_timeout'
+ * of zero has no effect on the respective timeout.
+ *
+ * 'fin_idle_timeout' and 'fin_hard_timeout' are measured in seconds.
+ * 'fin_hard_timeout' specifies time since the flow's creation, not since the
+ * receipt of the FIN or RST.
+ *
+ * This is useful for quickly discarding learned TCP flows that otherwise will
+ * take a long time to expire.
+ *
+ * This action is intended for use with an OpenFlow rule that matches only a
+ * single TCP flow. If the rule matches multiple TCP flows (e.g. it wildcards
+ * all TCP traffic, or all TCP traffic to a particular port), then any FIN or
+ * RST in any of those flows will cause the entire OpenFlow rule to expire
+ * early, which is not normally desirable.
+ */
+struct nx_action_fin_timeout {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_FIN_TIMEOUT. */
+ ovs_be16 fin_idle_timeout; /* New idle timeout, if nonzero. */
+ ovs_be16 fin_hard_timeout; /* New hard timeout, if nonzero. */
+ ovs_be16 pad; /* Must be zero. */
+};
+OFP_ASSERT(sizeof(struct nx_action_fin_timeout) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_FIN_TIMEOUT(const struct nx_action_fin_timeout *naft,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_fin_timeout *oft;
+
+ oft = ofpact_put_FIN_TIMEOUT(out);
+ oft->fin_idle_timeout = ntohs(naft->fin_idle_timeout);
+ oft->fin_hard_timeout = ntohs(naft->fin_hard_timeout);
+ return 0;
+}
+
+static void
+encode_FIN_TIMEOUT(const struct ofpact_fin_timeout *fin_timeout,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct nx_action_fin_timeout *naft = put_NXAST_FIN_TIMEOUT(out);
+ naft->fin_idle_timeout = htons(fin_timeout->fin_idle_timeout);
+ naft->fin_hard_timeout = htons(fin_timeout->fin_hard_timeout);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_FIN_TIMEOUT(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_fin_timeout *oft = ofpact_put_FIN_TIMEOUT(ofpacts);
+ char *key, *value;
+
+ while (ofputil_parse_key_value(&arg, &key, &value)) {
+ char *error;
+
+ if (!strcmp(key, "idle_timeout")) {
+ error = str_to_u16(value, key, &oft->fin_idle_timeout);
+ } else if (!strcmp(key, "hard_timeout")) {
+ error = str_to_u16(value, key, &oft->fin_hard_timeout);
+ } else {
+ error = xasprintf("invalid key '%s' in 'fin_timeout' argument",
+ key);
+ }
+
+ if (error) {
+ return error;
+ }
+ }
+ return NULL;
+}
+
+static void
+format_FIN_TIMEOUT(const struct ofpact_fin_timeout *a, struct ds *s)
+{
+ ds_put_cstr(s, "fin_timeout(");
+ if (a->fin_idle_timeout) {
+ ds_put_format(s, "idle_timeout=%"PRIu16",", a->fin_idle_timeout);
+ }
+ if (a->fin_hard_timeout) {
+ ds_put_format(s, "hard_timeout=%"PRIu16",", a->fin_hard_timeout);
+ }
+ ds_chomp(s, ',');
+ ds_put_char(s, ')');
+}
+\f
+/* Action structures for NXAST_RESUBMIT and NXAST_RESUBMIT_TABLE.
+ *
+ * These actions search one of the switch's flow tables:
+ *
+ * - For NXAST_RESUBMIT_TABLE only, if the 'table' member is not 255, then
+ * it specifies the table to search.
+ *
+ * - Otherwise (for NXAST_RESUBMIT_TABLE with a 'table' of 255, or for
+ * NXAST_RESUBMIT regardless of 'table'), it searches the current flow
+ * table, that is, the OpenFlow flow table that contains the flow from
+ * which this action was obtained. If this action did not come from a
+ * flow table (e.g. it came from an OFPT_PACKET_OUT message), then table 0
+ * is the current table.
+ *
+ * The flow table lookup uses a flow that may be slightly modified from the
+ * original lookup:
+ *
+ * - For NXAST_RESUBMIT, the 'in_port' member of struct nx_action_resubmit
+ * is used as the flow's in_port.
+ *
+ * - For NXAST_RESUBMIT_TABLE, if the 'in_port' member is not OFPP_IN_PORT,
+ * then its value is used as the flow's in_port. Otherwise, the original
+ * in_port is used.
+ *
+ * - If actions that modify the flow (e.g. OFPAT_SET_VLAN_VID) precede the
+ * resubmit action, then the flow is updated with the new values.
+ *
+ * Following the lookup, the original in_port is restored.
+ *
+ * If the modified flow matched in the flow table, then the corresponding
+ * actions are executed. Afterward, actions following the resubmit in the
+ * original set of actions, if any, are executed; any changes made to the
+ * packet (e.g. changes to VLAN) by secondary actions persist when those
+ * actions are executed, although the original in_port is restored.
+ *
+ * Resubmit actions may be used any number of times within a set of actions.
+ *
+ * Resubmit actions may nest to an implementation-defined depth. Beyond this
+ * implementation-defined depth, further resubmit actions are simply ignored.
+ *
+ * NXAST_RESUBMIT ignores 'table' and 'pad'. NXAST_RESUBMIT_TABLE requires
+ * 'pad' to be all-bits-zero.
+ *
+ * Open vSwitch 1.0.1 and earlier did not support recursion. Open vSwitch
+ * before 1.2.90 did not support NXAST_RESUBMIT_TABLE.
+ */
+struct nx_action_resubmit {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length is 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_RESUBMIT. */
+ ovs_be16 in_port; /* New in_port for checking flow table. */
+ uint8_t table; /* NXAST_RESUBMIT_TABLE: table to use. */
+ uint8_t pad[3];
+};
+OFP_ASSERT(sizeof(struct nx_action_resubmit) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_RESUBMIT(uint16_t port,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_resubmit *resubmit;
+
+ resubmit = ofpact_put_RESUBMIT(out);
+ resubmit->ofpact.raw = NXAST_RAW_RESUBMIT;
+ resubmit->in_port = u16_to_ofp(port);
+ resubmit->table_id = 0xff;
+ return 0;
+}
+
+static enum ofperr
+decode_NXAST_RAW_RESUBMIT_TABLE(const struct nx_action_resubmit *nar,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_resubmit *resubmit;
+
+ if (nar->pad[0] || nar->pad[1] || nar->pad[2]) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ resubmit = ofpact_put_RESUBMIT(out);
+ resubmit->ofpact.raw = NXAST_RAW_RESUBMIT_TABLE;
+ resubmit->in_port = u16_to_ofp(ntohs(nar->in_port));
+ resubmit->table_id = nar->table;
+ return 0;
+}
+
+static void
+encode_RESUBMIT(const struct ofpact_resubmit *resubmit,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ uint16_t in_port = ofp_to_u16(resubmit->in_port);
+
+ if (resubmit->table_id == 0xff
+ && resubmit->ofpact.raw != NXAST_RAW_RESUBMIT_TABLE) {
+ put_NXAST_RESUBMIT(out, in_port);
+ } else {
+ struct nx_action_resubmit *nar = put_NXAST_RESUBMIT_TABLE(out);
+ nar->table = resubmit->table_id;
+ nar->in_port = htons(in_port);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_RESUBMIT(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_resubmit *resubmit;
+ char *in_port_s, *table_s;
+
+ resubmit = ofpact_put_RESUBMIT(ofpacts);
+
+ in_port_s = strsep(&arg, ",");
+ if (in_port_s && in_port_s[0]) {
+ if (!ofputil_port_from_string(in_port_s, &resubmit->in_port)) {
+ return xasprintf("%s: resubmit to unknown port", in_port_s);
+ }
+ } else {
+ resubmit->in_port = OFPP_IN_PORT;
+ }
+
+ table_s = strsep(&arg, ",");
+ if (table_s && table_s[0]) {
+ uint32_t table_id = 0;
+ char *error;
+
+ error = str_to_u32(table_s, &table_id);
+ if (error) {
+ return error;
+ }
+ resubmit->table_id = table_id;
+ } else {
+ resubmit->table_id = 255;
+ }
+
+ if (resubmit->in_port == OFPP_IN_PORT && resubmit->table_id == 255) {
+ return xstrdup("at least one \"in_port\" or \"table\" must be "
+ "specified on resubmit");
+ }
+ return NULL;
+}
+
+static void
+format_RESUBMIT(const struct ofpact_resubmit *a, struct ds *s)
+{
+ if (a->in_port != OFPP_IN_PORT && a->table_id == 255) {
+ ds_put_cstr(s, "resubmit:");
+ ofputil_format_port(a->in_port, s);
+ } else {
+ ds_put_format(s, "resubmit(");
+ if (a->in_port != OFPP_IN_PORT) {
+ ofputil_format_port(a->in_port, s);
+ }
+ ds_put_char(s, ',');
+ if (a->table_id != 255) {
+ ds_put_format(s, "%"PRIu8, a->table_id);
+ }
+ ds_put_char(s, ')');
+ }
+}
+\f
+/* Action structure for NXAST_LEARN.
+ *
+ * This action adds or modifies a flow in an OpenFlow table, similar to
+ * OFPT_FLOW_MOD with OFPFC_MODIFY_STRICT as 'command'. The new flow has the
+ * specified idle timeout, hard timeout, priority, cookie, and flags. The new
+ * flow's match criteria and actions are built by applying each of the series
+ * of flow_mod_spec elements included as part of the action.
+ *
+ * A flow_mod_spec starts with a 16-bit header. A header that is all-bits-0 is
+ * a no-op used for padding the action as a whole to a multiple of 8 bytes in
+ * length. Otherwise, the flow_mod_spec can be thought of as copying 'n_bits'
+ * bits from a source to a destination. In this case, the header contains
+ * multiple fields:
+ *
+ * 15 14 13 12 11 10 0
+ * +------+---+------+---------------------------------+
+ * | 0 |src| dst | n_bits |
+ * +------+---+------+---------------------------------+
+ *
+ * The meaning and format of a flow_mod_spec depends on 'src' and 'dst'. The
+ * following table summarizes the meaning of each possible combination.
+ * Details follow the table:
+ *
+ * src dst meaning
+ * --- --- ----------------------------------------------------------
+ * 0 0 Add match criteria based on value in a field.
+ * 1 0 Add match criteria based on an immediate value.
+ * 0 1 Add NXAST_REG_LOAD action to copy field into a different field.
+ * 1 1 Add NXAST_REG_LOAD action to load immediate value into a field.
+ * 0 2 Add OFPAT_OUTPUT action to output to port from specified field.
+ * All other combinations are undefined and not allowed.
+ *
+ * The flow_mod_spec header is followed by a source specification and a
+ * destination specification. The format and meaning of the source
+ * specification depends on 'src':
+ *
+ * - If 'src' is 0, the source bits are taken from a field in the flow to
+ * which this action is attached. (This should be a wildcarded field. If
+ * its value is fully specified then the source bits being copied have
+ * constant values.)
+ *
+ * The source specification is an ovs_be32 'field' and an ovs_be16 'ofs'.
+ * 'field' is an nxm_header with nxm_hasmask=0, and 'ofs' the starting bit
+ * offset within that field. The source bits are field[ofs:ofs+n_bits-1].
+ * 'field' and 'ofs' are subject to the same restrictions as the source
+ * field in NXAST_REG_MOVE.
+ *
+ * - If 'src' is 1, the source bits are a constant value. The source
+ * specification is (n_bits+15)/16*2 bytes long. Taking those bytes as a
+ * number in network order, the source bits are the 'n_bits'
+ * least-significant bits. The switch will report an error if other bits
+ * in the constant are nonzero.
+ *
+ * The flow_mod_spec destination specification, for 'dst' of 0 or 1, is an
+ * ovs_be32 'field' and an ovs_be16 'ofs'. 'field' is an nxm_header with
+ * nxm_hasmask=0 and 'ofs' is a starting bit offset within that field. The
+ * meaning of the flow_mod_spec depends on 'dst':
+ *
+ * - If 'dst' is 0, the flow_mod_spec specifies match criteria for the new
+ * flow. The new flow matches only if bits field[ofs:ofs+n_bits-1] in a
+ * packet equal the source bits. 'field' may be any nxm_header with
+ * nxm_hasmask=0 that is allowed in NXT_FLOW_MOD.
+ *
+ * Order is significant. Earlier flow_mod_specs must satisfy any
+ * prerequisites for matching fields specified later, by copying constant
+ * values into prerequisite fields.
+ *
+ * The switch will reject flow_mod_specs that do not satisfy NXM masking
+ * restrictions.
+ *
+ * - If 'dst' is 1, the flow_mod_spec specifies an NXAST_REG_LOAD action for
+ * the new flow. The new flow copies the source bits into
+ * field[ofs:ofs+n_bits-1]. Actions are executed in the same order as the
+ * flow_mod_specs.
+ *
+ * A single NXAST_REG_LOAD action writes no more than 64 bits, so n_bits
+ * greater than 64 yields multiple NXAST_REG_LOAD actions.
+ *
+ * The flow_mod_spec destination spec for 'dst' of 2 (when 'src' is 0) is
+ * empty. It has the following meaning:
+ *
+ * - The flow_mod_spec specifies an OFPAT_OUTPUT action for the new flow.
+ * The new flow outputs to the OpenFlow port specified by the source field.
+ * Of the special output ports with value OFPP_MAX or larger, OFPP_IN_PORT,
+ * OFPP_FLOOD, OFPP_LOCAL, and OFPP_ALL are supported. Other special ports
+ * may not be used.
+ *
+ * Resource Management
+ * -------------------
+ *
+ * A switch has a finite amount of flow table space available for learning.
+ * When this space is exhausted, no new learning table entries will be learned
+ * until some existing flow table entries expire. The controller should be
+ * prepared to handle this by flooding (which can be implemented as a
+ * low-priority flow).
+ *
+ * If a learned flow matches a single TCP stream with a relatively long
+ * timeout, one may make the best of resource constraints by setting
+ * 'fin_idle_timeout' or 'fin_hard_timeout' (both measured in seconds), or
+ * both, to shorter timeouts. When either of these is specified as a nonzero
+ * value, OVS adds a NXAST_FIN_TIMEOUT action, with the specified timeouts, to
+ * the learned flow.
+ *
+ * Examples
+ * --------
+ *
+ * The following examples give a prose description of the flow_mod_specs along
+ * with informal notation for how those would be represented and a hex dump of
+ * the bytes that would be required.
+ *
+ * These examples could work with various nx_action_learn parameters. Typical
+ * values would be idle_timeout=OFP_FLOW_PERMANENT, hard_timeout=60,
+ * priority=OFP_DEFAULT_PRIORITY, flags=0, table_id=10.
+ *
+ * 1. Learn input port based on the source MAC, with lookup into
+ * NXM_NX_REG1[16:31] by resubmit to in_port=99:
+ *
+ * Match on in_port=99:
+ * ovs_be16(src=1, dst=0, n_bits=16), 20 10
+ * ovs_be16(99), 00 63
+ * ovs_be32(NXM_OF_IN_PORT), ovs_be16(0) 00 00 00 02 00 00
+ *
+ * Match Ethernet destination on Ethernet source from packet:
+ * ovs_be16(src=0, dst=0, n_bits=48), 00 30
+ * ovs_be32(NXM_OF_ETH_SRC), ovs_be16(0) 00 00 04 06 00 00
+ * ovs_be32(NXM_OF_ETH_DST), ovs_be16(0) 00 00 02 06 00 00
+ *
+ * Set NXM_NX_REG1[16:31] to the packet's input port:
+ * ovs_be16(src=0, dst=1, n_bits=16), 08 10
+ * ovs_be32(NXM_OF_IN_PORT), ovs_be16(0) 00 00 00 02 00 00
+ * ovs_be32(NXM_NX_REG1), ovs_be16(16) 00 01 02 04 00 10
+ *
+ * Given a packet that arrived on port A with Ethernet source address B,
+ * this would set up the flow "in_port=99, dl_dst=B,
+ * actions=load:A->NXM_NX_REG1[16..31]".
+ *
+ * In syntax accepted by ovs-ofctl, this action is: learn(in_port=99,
+ * NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],
+ * load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
+ *
+ * 2. Output to input port based on the source MAC and VLAN VID, with lookup
+ * into NXM_NX_REG1[16:31]:
+ *
+ * Match on same VLAN ID as packet:
+ * ovs_be16(src=0, dst=0, n_bits=12), 00 0c
+ * ovs_be32(NXM_OF_VLAN_TCI), ovs_be16(0) 00 00 08 02 00 00
+ * ovs_be32(NXM_OF_VLAN_TCI), ovs_be16(0) 00 00 08 02 00 00
+ *
+ * Match Ethernet destination on Ethernet source from packet:
+ * ovs_be16(src=0, dst=0, n_bits=48), 00 30
+ * ovs_be32(NXM_OF_ETH_SRC), ovs_be16(0) 00 00 04 06 00 00
+ * ovs_be32(NXM_OF_ETH_DST), ovs_be16(0) 00 00 02 06 00 00
+ *
+ * Output to the packet's input port:
+ * ovs_be16(src=0, dst=2, n_bits=16), 10 10
+ * ovs_be32(NXM_OF_IN_PORT), ovs_be16(0) 00 00 00 02 00 00
+ *
+ * Given a packet that arrived on port A with Ethernet source address B in
+ * VLAN C, this would set up the flow "dl_dst=B, vlan_vid=C,
+ * actions=output:A".
+ *
+ * In syntax accepted by ovs-ofctl, this action is:
+ * learn(NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],
+ * output:NXM_OF_IN_PORT[])
+ *
+ * 3. Here's a recipe for a very simple-minded MAC learning switch. It uses a
+ * 10-second MAC expiration time to make it easier to see what's going on
+ *
+ * ovs-vsctl del-controller br0
+ * ovs-ofctl del-flows br0
+ * ovs-ofctl add-flow br0 "table=0 actions=learn(table=1, \
+ hard_timeout=10, NXM_OF_VLAN_TCI[0..11], \
+ NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], \
+ output:NXM_OF_IN_PORT[]), resubmit(,1)"
+ * ovs-ofctl add-flow br0 "table=1 priority=0 actions=flood"
+ *
+ * You can then dump the MAC learning table with:
+ *
+ * ovs-ofctl dump-flows br0 table=1
+ *
+ * Usage Advice
+ * ------------
+ *
+ * For best performance, segregate learned flows into a table that is not used
+ * for any other flows except possibly for a lowest-priority "catch-all" flow
+ * (a flow with no match criteria). If different learning actions specify
+ * different match criteria, use different tables for the learned flows.
+ *
+ * The meaning of 'hard_timeout' and 'idle_timeout' can be counterintuitive.
+ * These timeouts apply to the flow that is added, which means that a flow with
+ * an idle timeout will expire when no traffic has been sent *to* the learned
+ * address. This is not usually the intent in MAC learning; instead, we want
+ * the MAC learn entry to expire when no traffic has been sent *from* the
+ * learned address. Use a hard timeout for that.
+ *
+ *
+ * Visibility of Changes
+ * ---------------------
+ *
+ * Prior to Open vSwitch 2.4, any changes made by a "learn" action in a given
+ * flow translation are visible to flow table lookups made later in the flow
+ * translation. This means that, in the example above, a MAC learned by the
+ * learn action in table 0 would be found in table 1 (if the packet being
+ * processed had the same source and destination MAC address).
+ *
+ * In Open vSwitch 2.4 and later, changes to a flow table (whether to add or
+ * modify a flow) by a "learn" action are visible only for later flow
+ * translations, not for later lookups within the same flow translation. In
+ * the MAC learning example, a MAC learned by the learn action in table 0 would
+ * not be found in table 1 if the flow translation would resubmit to table 1
+ * after the processing of the learn action, meaning that if this MAC had not
+ * been learned before then the packet would be flooded. */
+struct nx_action_learn {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* At least 24. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_LEARN. */
+ ovs_be16 idle_timeout; /* Idle time before discarding (seconds). */
+ ovs_be16 hard_timeout; /* Max time before discarding (seconds). */
+ ovs_be16 priority; /* Priority level of flow entry. */
+ ovs_be64 cookie; /* Cookie for new flow. */
+ ovs_be16 flags; /* NX_LEARN_F_*. */
+ uint8_t table_id; /* Table to insert flow entry. */
+ uint8_t pad; /* Must be zero. */
+ ovs_be16 fin_idle_timeout; /* Idle timeout after FIN, if nonzero. */
+ ovs_be16 fin_hard_timeout; /* Hard timeout after FIN, if nonzero. */
+ /* Followed by a sequence of flow_mod_spec elements, as described above,
+ * until the end of the action is reached. */
+};
+OFP_ASSERT(sizeof(struct nx_action_learn) == 32);
+
+static ovs_be16
+get_be16(const void **pp)
+{
+ const ovs_be16 *p = *pp;
+ ovs_be16 value = *p;
+ *pp = p + 1;
+ return value;
+}
+
+static ovs_be32
+get_be32(const void **pp)
+{
+ const ovs_be32 *p = *pp;
+ ovs_be32 value = get_unaligned_be32(p);
+ *pp = p + 1;
+ return value;
+}
+
+static void
+get_subfield(int n_bits, const void **p, struct mf_subfield *sf)
+{
+ sf->field = mf_from_nxm_header(ntohl(get_be32(p)));
+ sf->ofs = ntohs(get_be16(p));
+ sf->n_bits = n_bits;
+}
+
+static unsigned int
+learn_min_len(uint16_t header)
+{
+ int n_bits = header & NX_LEARN_N_BITS_MASK;
+ int src_type = header & NX_LEARN_SRC_MASK;
+ int dst_type = header & NX_LEARN_DST_MASK;
+ unsigned int min_len;
+
+ min_len = 0;
+ if (src_type == NX_LEARN_SRC_FIELD) {
+ min_len += sizeof(ovs_be32); /* src_field */
+ min_len += sizeof(ovs_be16); /* src_ofs */
+ } else {
+ min_len += DIV_ROUND_UP(n_bits, 16);
+ }
+ if (dst_type == NX_LEARN_DST_MATCH ||
+ dst_type == NX_LEARN_DST_LOAD) {
+ min_len += sizeof(ovs_be32); /* dst_field */
+ min_len += sizeof(ovs_be16); /* dst_ofs */
+ }
+ return min_len;
+}
+
+/* Converts 'nal' into a "struct ofpact_learn" and appends that struct to
+ * 'ofpacts'. Returns 0 if successful, otherwise an OFPERR_*. */
+static enum ofperr
+decode_NXAST_RAW_LEARN(const struct nx_action_learn *nal,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct ofpact_learn *learn;
+ const void *p, *end;
+
+ if (nal->pad) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ learn = ofpact_put_LEARN(ofpacts);
+
+ learn->idle_timeout = ntohs(nal->idle_timeout);
+ learn->hard_timeout = ntohs(nal->hard_timeout);
+ learn->priority = ntohs(nal->priority);
+ learn->cookie = nal->cookie;
+ learn->table_id = nal->table_id;
+ learn->fin_idle_timeout = ntohs(nal->fin_idle_timeout);
+ learn->fin_hard_timeout = ntohs(nal->fin_hard_timeout);
+
+ learn->flags = ntohs(nal->flags);
+ if (learn->flags & ~(NX_LEARN_F_SEND_FLOW_REM |
+ NX_LEARN_F_DELETE_LEARNED)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ if (learn->table_id == 0xff) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ end = (char *) nal + ntohs(nal->len);
+ for (p = nal + 1; p != end; ) {
+ struct ofpact_learn_spec *spec;
+ uint16_t header = ntohs(get_be16(&p));
+
+ if (!header) {
+ break;
+ }
+
+ spec = ofpbuf_put_zeros(ofpacts, sizeof *spec);
+ learn = ofpacts->header;
+ learn->n_specs++;
+
+ spec->src_type = header & NX_LEARN_SRC_MASK;
+ spec->dst_type = header & NX_LEARN_DST_MASK;
+ spec->n_bits = header & NX_LEARN_N_BITS_MASK;
+
+ /* Check for valid src and dst type combination. */
+ if (spec->dst_type == NX_LEARN_DST_MATCH ||
+ spec->dst_type == NX_LEARN_DST_LOAD ||
+ (spec->dst_type == NX_LEARN_DST_OUTPUT &&
+ spec->src_type == NX_LEARN_SRC_FIELD)) {
+ /* OK. */
+ } else {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ /* Check that the arguments don't overrun the end of the action. */
+ if ((char *) end - (char *) p < learn_min_len(header)) {
+ return OFPERR_OFPBAC_BAD_LEN;
+ }
+
+ /* Get the source. */
+ if (spec->src_type == NX_LEARN_SRC_FIELD) {
+ get_subfield(spec->n_bits, &p, &spec->src);
+ } else {
+ int p_bytes = 2 * DIV_ROUND_UP(spec->n_bits, 16);
+
+ bitwise_copy(p, p_bytes, 0,
+ &spec->src_imm, sizeof spec->src_imm, 0,
+ spec->n_bits);
+ p = (const uint8_t *) p + p_bytes;
+ }
+
+ /* Get the destination. */
+ if (spec->dst_type == NX_LEARN_DST_MATCH ||
+ spec->dst_type == NX_LEARN_DST_LOAD) {
+ get_subfield(spec->n_bits, &p, &spec->dst);
+ }
+ }
+ ofpact_finish(ofpacts, &learn->ofpact);
+
+ if (!is_all_zeros(p, (char *) end - (char *) p)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ return 0;
+}
+
+static void
+put_be16(struct ofpbuf *b, ovs_be16 x)
+{
+ ofpbuf_put(b, &x, sizeof x);
+}
+
+static void
+put_be32(struct ofpbuf *b, ovs_be32 x)
+{
+ ofpbuf_put(b, &x, sizeof x);
+}
+
+static void
+put_u16(struct ofpbuf *b, uint16_t x)
+{
+ put_be16(b, htons(x));
+}
+
+static void
+put_u32(struct ofpbuf *b, uint32_t x)
+{
+ put_be32(b, htonl(x));
+}
+
+static void
+encode_LEARN(const struct ofpact_learn *learn,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ const struct ofpact_learn_spec *spec;
+ struct nx_action_learn *nal;
+ size_t start_ofs;
+
+ start_ofs = out->size;
+ nal = put_NXAST_LEARN(out);
+ nal->idle_timeout = htons(learn->idle_timeout);
+ nal->hard_timeout = htons(learn->hard_timeout);
+ nal->fin_idle_timeout = htons(learn->fin_idle_timeout);
+ nal->fin_hard_timeout = htons(learn->fin_hard_timeout);
+ nal->priority = htons(learn->priority);
+ nal->cookie = learn->cookie;
+ nal->flags = htons(learn->flags);
+ nal->table_id = learn->table_id;
+
+ for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+ put_u16(out, spec->n_bits | spec->dst_type | spec->src_type);
+
+ if (spec->src_type == NX_LEARN_SRC_FIELD) {
+ put_u32(out, mf_nxm_header(spec->src.field->id));
+ put_u16(out, spec->src.ofs);
+ } else {
+ size_t n_dst_bytes = 2 * DIV_ROUND_UP(spec->n_bits, 16);
+ uint8_t *bits = ofpbuf_put_zeros(out, n_dst_bytes);
+ bitwise_copy(&spec->src_imm, sizeof spec->src_imm, 0,
+ bits, n_dst_bytes, 0,
+ spec->n_bits);
+ }
+
+ if (spec->dst_type == NX_LEARN_DST_MATCH ||
+ spec->dst_type == NX_LEARN_DST_LOAD) {
+ put_u32(out, mf_nxm_header(spec->dst.field->id));
+ put_u16(out, spec->dst.ofs);
+ }
+ }
+
+ pad_ofpat(out, start_ofs);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_LEARN(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ return learn_parse(arg, ofpacts);
+}
+
+static void
+format_LEARN(const struct ofpact_learn *a, struct ds *s)
+{
+ learn_format(a, s);
+}
+\f
+/* Action structure for NXAST_CONJUNCTION. */
+struct nx_action_conjunction {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* At least 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* See enum ofp_raw_action_type. */
+ uint8_t clause;
+ uint8_t n_clauses;
+ ovs_be32 id;
+};
+OFP_ASSERT(sizeof(struct nx_action_conjunction) == 16);
+
+static void
+add_conjunction(struct ofpbuf *out,
+ uint32_t id, uint8_t clause, uint8_t n_clauses)
+{
+ struct ofpact_conjunction *oc;
+
+ oc = ofpact_put_CONJUNCTION(out);
+ oc->id = id;
+ oc->clause = clause;
+ oc->n_clauses = n_clauses;
+}
+
+static enum ofperr
+decode_NXAST_RAW_CONJUNCTION(const struct nx_action_conjunction *nac,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ if (nac->n_clauses < 2 || nac->n_clauses > 64
+ || nac->clause >= nac->n_clauses) {
+ return OFPERR_NXBAC_BAD_CONJUNCTION;
+ } else {
+ add_conjunction(out, ntohl(nac->id), nac->clause, nac->n_clauses);
+ return 0;
+ }
+}
+
+static void
+encode_CONJUNCTION(const struct ofpact_conjunction *oc,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ struct nx_action_conjunction *nac = put_NXAST_CONJUNCTION(out);
+ nac->clause = oc->clause;
+ nac->n_clauses = oc->n_clauses;
+ nac->id = htonl(oc->id);
+}
+
+static void
+format_CONJUNCTION(const struct ofpact_conjunction *oc, struct ds *s)
+{
+ ds_put_format(s, "conjunction(%"PRIu32",%"PRIu8"/%"PRIu8")",
+ oc->id, oc->clause + 1, oc->n_clauses);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_CONJUNCTION(const char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ uint8_t n_clauses;
+ uint8_t clause;
+ uint32_t id;
+ int n;
+
+ if (!ovs_scan(arg, "%"SCNi32" , %"SCNu8" / %"SCNu8" %n",
+ &id, &clause, &n_clauses, &n) || n != strlen(arg)) {
+ return xstrdup("\"conjunction\" syntax is \"conjunction(id,i/n)\"");
+ }
+
+ if (n_clauses < 2) {
+ return xstrdup("conjunction must have at least 2 clauses");
+ } else if (n_clauses > 64) {
+ return xstrdup("conjunction must have at most 64 clauses");
+ } else if (clause < 1) {
+ return xstrdup("clause index must be positive");
+ } else if (clause > n_clauses) {
+ return xstrdup("clause index must be less than or equal to "
+ "number of clauses");
+ }
+
+ add_conjunction(ofpacts, id, clause - 1, n_clauses);
+ return NULL;
+}
+\f
+/* Action structure for NXAST_MULTIPATH.
+ *
+ * This action performs the following steps in sequence:
+ *
+ * 1. Hashes the fields designated by 'fields', one of NX_HASH_FIELDS_*.
+ * Refer to the definition of "enum nx_mp_fields" for details.
+ *
+ * The 'basis' value is used as a universal hash parameter, that is,
+ * different values of 'basis' yield different hash functions. The
+ * particular universal hash function used is implementation-defined.
+ *
+ * The hashed fields' values are drawn from the current state of the
+ * flow, including all modifications that have been made by actions up to
+ * this point.
+ *
+ * 2. Applies the multipath link choice algorithm specified by 'algorithm',
+ * one of NX_MP_ALG_*. Refer to the definition of "enum nx_mp_algorithm"
+ * for details.
+ *
+ * The output of the algorithm is 'link', an unsigned integer less than
+ * or equal to 'max_link'.
+ *
+ * Some algorithms use 'arg' as an additional argument.
+ *
+ * 3. Stores 'link' in dst[ofs:ofs+n_bits]. The format and semantics of
+ * 'dst' and 'ofs_nbits' are similar to those for the NXAST_REG_LOAD
+ * action.
+ *
+ * The switch will reject actions that have an unknown 'fields', or an unknown
+ * 'algorithm', or in which ofs+n_bits is greater than the width of 'dst', or
+ * in which 'max_link' is greater than or equal to 2**n_bits, with error type
+ * OFPET_BAD_ACTION, code OFPBAC_BAD_ARGUMENT.
+ */
+struct nx_action_multipath {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length is 32. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_MULTIPATH. */
+
+ /* What fields to hash and how. */
+ ovs_be16 fields; /* One of NX_HASH_FIELDS_*. */
+ ovs_be16 basis; /* Universal hash parameter. */
+ ovs_be16 pad0;
+
+ /* Multipath link choice algorithm to apply to hash value. */
+ ovs_be16 algorithm; /* One of NX_MP_ALG_*. */
+ ovs_be16 max_link; /* Number of output links, minus 1. */
+ ovs_be32 arg; /* Algorithm-specific argument. */
+ ovs_be16 pad1;
+
+ /* Where to store the result. */
+ ovs_be16 ofs_nbits; /* (ofs << 6) | (n_bits - 1). */
+ ovs_be32 dst; /* Destination. */
+};
+OFP_ASSERT(sizeof(struct nx_action_multipath) == 32);
+
+static enum ofperr
+decode_NXAST_RAW_MULTIPATH(const struct nx_action_multipath *nam,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ uint32_t n_links = ntohs(nam->max_link) + 1;
+ size_t min_n_bits = log_2_ceil(n_links);
+ struct ofpact_multipath *mp;
+
+ mp = ofpact_put_MULTIPATH(out);
+ mp->fields = ntohs(nam->fields);
+ mp->basis = ntohs(nam->basis);
+ mp->algorithm = ntohs(nam->algorithm);
+ mp->max_link = ntohs(nam->max_link);
+ mp->arg = ntohl(nam->arg);
+ mp->dst.field = mf_from_nxm_header(ntohl(nam->dst));
+ mp->dst.ofs = nxm_decode_ofs(nam->ofs_nbits);
+ mp->dst.n_bits = nxm_decode_n_bits(nam->ofs_nbits);
+
+ if (!flow_hash_fields_valid(mp->fields)) {
+ VLOG_WARN_RL(&rl, "unsupported fields %d", (int) mp->fields);
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ } else if (mp->algorithm != NX_MP_ALG_MODULO_N
+ && mp->algorithm != NX_MP_ALG_HASH_THRESHOLD
+ && mp->algorithm != NX_MP_ALG_HRW
+ && mp->algorithm != NX_MP_ALG_ITER_HASH) {
+ VLOG_WARN_RL(&rl, "unsupported algorithm %d", (int) mp->algorithm);
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ } else if (mp->dst.n_bits < min_n_bits) {
+ VLOG_WARN_RL(&rl, "multipath action requires at least %"PRIuSIZE" bits for "
+ "%"PRIu32" links", min_n_bits, n_links);
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ return multipath_check(mp, NULL);
+}
+
+static void
+encode_MULTIPATH(const struct ofpact_multipath *mp,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ struct nx_action_multipath *nam = put_NXAST_MULTIPATH(out);
+
+ nam->fields = htons(mp->fields);
+ nam->basis = htons(mp->basis);
+ nam->algorithm = htons(mp->algorithm);
+ nam->max_link = htons(mp->max_link);
+ nam->arg = htonl(mp->arg);
+ nam->ofs_nbits = nxm_encode_ofs_nbits(mp->dst.ofs, mp->dst.n_bits);
+ nam->dst = htonl(mf_nxm_header(mp->dst.field->id));
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_MULTIPATH(const char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ return multipath_parse(ofpact_put_MULTIPATH(ofpacts), arg);
+}
+
+static void
+format_MULTIPATH(const struct ofpact_multipath *a, struct ds *s)
+{
+ multipath_format(a, s);
+}
+\f
+/* Action structure for NXAST_NOTE.
+ *
+ * This action has no effect. It is variable length. The switch does not
+ * attempt to interpret the user-defined 'note' data in any way. A controller
+ * can use this action to attach arbitrary metadata to a flow.
+ *
+ * This action might go away in the future.
+ */
+struct nx_action_note {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* A multiple of 8, but at least 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_NOTE. */
+ uint8_t note[6]; /* Start of user-defined data. */
+ /* Possibly followed by additional user-defined data. */
+};
+OFP_ASSERT(sizeof(struct nx_action_note) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_NOTE(const struct nx_action_note *nan,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_note *note;
+ unsigned int length;
+
+ length = ntohs(nan->len) - offsetof(struct nx_action_note, note);
+ note = ofpact_put_NOTE(out);
+ note->length = length;
+ ofpbuf_put(out, nan->note, length);
+ ofpact_finish(out, out->header);
+
+ return 0;
+}
+
+static void
+encode_NOTE(const struct ofpact_note *note,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ size_t start_ofs = out->size;
+ struct nx_action_note *nan;
+
+ put_NXAST_NOTE(out);
+ out->size = out->size - sizeof nan->note;
+
+ ofpbuf_put(out, note->data, note->length);
+ pad_ofpat(out, start_ofs);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_NOTE(const char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ size_t start_ofs = ofpacts->size;
+ ofpact_put_NOTE(ofpacts);
+ arg = ofpbuf_put_hex(ofpacts, arg, NULL);
+ if (arg[0]) {
+ return xstrdup("bad hex digit in `note' argument");
+ }
+ struct ofpact_note *note = ofpbuf_at_assert(ofpacts, start_ofs,
+ sizeof *note);
+ note->length = ofpacts->size - (start_ofs + sizeof *note);
+ ofpact_finish(ofpacts, ¬e->ofpact);
+ return NULL;
+}
+
+static void
+format_NOTE(const struct ofpact_note *a, struct ds *s)
+{
+ ds_put_cstr(s, "note:");
+ format_hex_arg(s, a->data, a->length);
+}
+\f
+/* Exit action. */
+
+static enum ofperr
+decode_NXAST_RAW_EXIT(struct ofpbuf *out)
+{
+ ofpact_put_EXIT(out);
+ return 0;
+}
+
+static void
+encode_EXIT(const struct ofpact_null *null OVS_UNUSED,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ put_NXAST_EXIT(out);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_EXIT(char *arg OVS_UNUSED, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ ofpact_put_EXIT(ofpacts);
+ return NULL;
+}
+
+static void
+format_EXIT(const struct ofpact_null *a OVS_UNUSED, struct ds *s)
+{
+ ds_put_cstr(s, "exit");
+}
+\f
+/* Unroll xlate action. */
+
+static void
+encode_UNROLL_XLATE(const struct ofpact_unroll_xlate *unroll OVS_UNUSED,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out OVS_UNUSED)
+{
+ OVS_NOT_REACHED();
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_UNROLL_XLATE(char *arg OVS_UNUSED, struct ofpbuf *ofpacts OVS_UNUSED,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ OVS_NOT_REACHED();
+ return NULL;
+}
+
+static void
+format_UNROLL_XLATE(const struct ofpact_unroll_xlate *a, struct ds *s)
+{
+ ds_put_format(s, "unroll_xlate(table=%"PRIu8", cookie=%"PRIu64")",
+ a->rule_table_id, ntohll(a->rule_cookie));
+}
+\f
+/* Action structure for NXAST_SAMPLE.
+ *
+ * Samples matching packets with the given probability and sends them
+ * each to the set of collectors identified with the given ID. The
+ * probability is expressed as a number of packets to be sampled out
+ * of USHRT_MAX packets, and must be >0.
+ *
+ * When sending packet samples to IPFIX collectors, the IPFIX flow
+ * record sent for each sampled packet is associated with the given
+ * observation domain ID and observation point ID. Each IPFIX flow
+ * record contain the sampled packet's headers when executing this
+ * rule. If a sampled packet's headers are modified by previous
+ * actions in the flow, those modified headers are sent. */
+struct nx_action_sample {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length is 24. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_SAMPLE. */
+ ovs_be16 probability; /* Fraction of packets to sample. */
+ ovs_be32 collector_set_id; /* ID of collector set in OVSDB. */
+ ovs_be32 obs_domain_id; /* ID of sampling observation domain. */
+ ovs_be32 obs_point_id; /* ID of sampling observation point. */
+};
+OFP_ASSERT(sizeof(struct nx_action_sample) == 24);
+
+static enum ofperr
+decode_NXAST_RAW_SAMPLE(const struct nx_action_sample *nas,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_sample *sample;
+
+ sample = ofpact_put_SAMPLE(out);
+ sample->probability = ntohs(nas->probability);
+ sample->collector_set_id = ntohl(nas->collector_set_id);
+ sample->obs_domain_id = ntohl(nas->obs_domain_id);
+ sample->obs_point_id = ntohl(nas->obs_point_id);
+
+ if (sample->probability == 0) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ return 0;
+}
+
+static void
+encode_SAMPLE(const struct ofpact_sample *sample,
+ enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+ struct nx_action_sample *nas;
+
+ nas = put_NXAST_SAMPLE(out);
+ nas->probability = htons(sample->probability);
+ nas->collector_set_id = htonl(sample->collector_set_id);
+ nas->obs_domain_id = htonl(sample->obs_domain_id);
+ nas->obs_point_id = htonl(sample->obs_point_id);
+}
+
+/* Parses 'arg' as the argument to a "sample" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_sample *os = ofpact_put_SAMPLE(ofpacts);
+ char *key, *value;
+
+ while (ofputil_parse_key_value(&arg, &key, &value)) {
+ char *error = NULL;
+
+ if (!strcmp(key, "probability")) {
+ error = str_to_u16(value, "probability", &os->probability);
+ if (!error && os->probability == 0) {
+ error = xasprintf("invalid probability value \"%s\"", value);
+ }
+ } else if (!strcmp(key, "collector_set_id")) {
+ error = str_to_u32(value, &os->collector_set_id);
+ } else if (!strcmp(key, "obs_domain_id")) {
+ error = str_to_u32(value, &os->obs_domain_id);
+ } else if (!strcmp(key, "obs_point_id")) {
+ error = str_to_u32(value, &os->obs_point_id);
+ } else {
+ error = xasprintf("invalid key \"%s\" in \"sample\" argument",
+ key);
+ }
+ if (error) {
+ return error;
+ }
+ }
+ if (os->probability == 0) {
+ return xstrdup("non-zero \"probability\" must be specified on sample");
+ }
+ return NULL;
+}
+
+static void
+format_SAMPLE(const struct ofpact_sample *a, struct ds *s)
+{
+ ds_put_format(s, "sample(probability=%"PRIu16",collector_set_id=%"PRIu32
+ ",obs_domain_id=%"PRIu32",obs_point_id=%"PRIu32")",
+ a->probability, a->collector_set_id,
+ a->obs_domain_id, a->obs_point_id);
+}
+\f
+/* debug_recirc instruction. */
+
+static bool enable_debug;
+
+void
+ofpact_dummy_enable(void)
+{
+ enable_debug = true;
+}
+
+static enum ofperr
+decode_NXAST_RAW_DEBUG_RECIRC(struct ofpbuf *out)
+{
+ if (!enable_debug) {
+ return OFPERR_OFPBAC_BAD_VENDOR_TYPE;
+ }
+
+ ofpact_put_DEBUG_RECIRC(out);
+ return 0;
+}
+
+static void
+encode_DEBUG_RECIRC(const struct ofpact_null *n OVS_UNUSED,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ put_NXAST_DEBUG_RECIRC(out);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_DEBUG_RECIRC(char *arg OVS_UNUSED, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ ofpact_put_DEBUG_RECIRC(ofpacts);
+ return NULL;
+}
+
+static void
+format_DEBUG_RECIRC(const struct ofpact_null *a OVS_UNUSED, struct ds *s)
+{
+ ds_put_cstr(s, "debug_recirc");
+}
+
+/* Action structure for NXAST_CT.
+ *
+ * Pass traffic to the connection tracker.
+ *
+ * There are two important concepts to understanding the connection tracking
+ * interface: Packet state and Connection state. Packets may be "Untracked" or
+ * "Tracked". Connections may be "Uncommitted" or "Committed".
+ *
+ * - Packet State:
+ *
+ * Untracked packets have not yet passed through the connection tracker,
+ * and the connection state for such packets is unknown. In most cases,
+ * packets entering the OpenFlow pipeline will initially be in the
+ * untracked state. Untracked packets may become tracked by executing
+ * NXAST_CT with a "recirc_table" specified. This makes various aspects
+ * about the connection available, in particular the connection state.
+ *
+ * Tracked packets have previously passed through the connection tracker.
+ * These packets will remain tracked through until the end of the OpenFlow
+ * pipeline. Tracked packets which have NXAST_CT executed with a
+ * "recirc_table" specified will return to the tracked state.
+ *
+ * The packet state is only significant for the duration of packet
+ * processing within the OpenFlow pipeline.
+ *
+ * - Connection State:
+ *
+ * Multiple packets may be associated with a single connection. Initially,
+ * all connections are uncommitted. The connection state corresponding to
+ * a packet is available in the NXM_NX_CT_STATE field for tracked packets.
+ *
+ * Uncommitted connections have no state stored about them. Uncommitted
+ * connections may transition into the committed state by executing
+ * NXAST_CT with the NX_CT_F_COMMIT flag.
+ *
+ * Once a connection becomes committed, information may be gathered about
+ * the connection by passing subsequent packets through the connection
+ * tracker, and the state of the connection will be stored beyond the
+ * lifetime of packet processing.
+ *
+ * Connections may transition back into the uncommitted state due to
+ * external timers, or due to the contents of packets that are sent to the
+ * connection tracker. This behaviour is outside of the scope of the
+ * OpenFlow interface.
+ *
+ * The "zone" specifies a context within which the tracking is done:
+ *
+ * The connection tracking zone is a 16-bit number. Each zone is an
+ * independent connection tracking context. The connection state for each
+ * connection is completely separate for each zone, so if a connection
+ * is committed to zone A, then it will remain uncommitted in zone B.
+ * If NXAST_CT is executed with the same zone multiple times, later
+ * executions have no effect.
+ *
+ * If 'zone_src' is nonzero, this specifies that the zone should be
+ * sourced from a field zone_src[ofs:ofs+nbits]. The format and semantics
+ * of 'zone_src' and 'zone_ofs_nbits' are similar to those for the
+ * NXAST_REG_LOAD action. The acceptable nxm_header values for 'zone_src'
+ * are the same as the acceptable nxm_header values for the 'src' field of
+ * NXAST_REG_MOVE.
+ *
+ * If 'zone_src' is zero, then the value of 'zone_imm' will be used as the
+ * connection tracking zone.
+ *
+ * The "recirc_table" allows NXM_NX_CT_* fields to become available:
+ *
+ * If "recirc_table" has a value other than NX_CT_RECIRC_NONE, then the
+ * packet will be logically cloned prior to executing this action. One
+ * copy will be sent to the connection tracker, then will be re-injected
+ * into the OpenFlow pipeline beginning at the OpenFlow table specified in
+ * this field. When the packet re-enters the pipeline, the NXM_NX_CT_*
+ * fields will be populated. The original instance of the packet will
+ * continue the current actions list. This can be thought of as similar to
+ * the effect of the "output" action: One copy is sent out (in this case,
+ * to the connection tracker), but the current copy continues processing.
+ *
+ * It is strongly recommended that this table is later than the current
+ * table, to prevent loops.
+ *
+ * The "alg" attaches protocol-specific behaviour to this action:
+ *
+ * The ALG is a 16-bit number which specifies that additional
+ * processing should be applied to this traffic.
+ *
+ * Protocol | Value | Meaning
+ * --------------------------------------------------------------------
+ * None | 0 | No protocol-specific behaviour.
+ * FTP | 21 | Parse FTP control connections and observe the
+ * | | negotiation of related data connections.
+ * Other | Other | Unsupported protocols.
+ *
+ * By way of example, if FTP control connections have this action applied
+ * with the ALG set to FTP (21), then the connection tracker will observe
+ * the negotiation of data connections. This allows the connection
+ * tracker to identify subsequent data connections as "related" to this
+ * existing connection. The "related" flag will be populated in the
+ * NXM_NX_CT_STATE field for such connections if the 'recirc_table' is
+ * specified.
+ *
+ * Zero or more actions may immediately follow this action. These actions will
+ * be executed within the context of the connection tracker, and they require
+ * the NX_CT_F_COMMIT flag to be set.
+ */
+struct nx_action_conntrack {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* At least 24. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_CT. */
+ ovs_be16 flags; /* Zero or more NX_CT_F_* flags.
+ * Unspecified flag bits must be zero. */
+ ovs_be32 zone_src; /* Connection tracking context. */
+ union {
+ ovs_be16 zone_ofs_nbits;/* Range to use from source field. */
+ ovs_be16 zone_imm; /* Immediate value for zone. */
+ };
+ uint8_t recirc_table; /* Recirculate to a specific table, or
+ NX_CT_RECIRC_NONE for no recirculation. */
+ uint8_t pad[3]; /* Zeroes */
+ ovs_be16 alg; /* Well-known port number for the protocol.
+ * 0 indicates no ALG is required. */
+ /* Followed by a sequence of zero or more OpenFlow actions. The length of
+ * these is included in 'len'. */
+};
+OFP_ASSERT(sizeof(struct nx_action_conntrack) == 24);
+
+static enum ofperr
+decode_ct_zone(const struct nx_action_conntrack *nac,
+ struct ofpact_conntrack *out)
+{
+ if (nac->zone_src) {
+ enum ofperr error;
+
+ out->zone_src.field = mf_from_nxm_header(ntohl(nac->zone_src));
+ out->zone_src.ofs = nxm_decode_ofs(nac->zone_ofs_nbits);
+ out->zone_src.n_bits = nxm_decode_n_bits(nac->zone_ofs_nbits);
+ error = mf_check_src(&out->zone_src, NULL);
+ if (error) {
+ return error;
+ }
+
+ if (out->zone_src.n_bits != 16) {
+ VLOG_WARN_RL(&rl, "zone n_bits %d not within valid range [16..16]",
+ out->zone_src.n_bits);
+ return OFPERR_OFPBAC_BAD_SET_LEN;
+ }
+ } else {
+ out->zone_src.field = NULL;
+ out->zone_imm = ntohs(nac->zone_imm);
+ }
+
+ return 0;
+}
+
+static enum ofperr
+decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ const size_t ct_offset = ofpacts_pull(out);
+ struct ofpact_conntrack *conntrack = ofpact_put_CT(out);
+ conntrack->flags = ntohs(nac->flags);
+
+ int error = decode_ct_zone(nac, conntrack);
+ if (error) {
+ goto out;
+ }
+ conntrack->recirc_table = nac->recirc_table;
+ conntrack->alg = ntohs(nac->alg);
+
+ ofpbuf_pull(out, sizeof(*conntrack));
+
+ struct ofpbuf openflow = ofpbuf_const_initializer(
+ nac + 1, ntohs(nac->len) - sizeof(*nac));
+ error = ofpacts_pull_openflow_actions__(&openflow, openflow.size,
+ ofp_version,
+ 1u << OVSINST_OFPIT11_APPLY_ACTIONS,
+ out, OFPACT_CT);
+ if (error) {
+ goto out;
+ }
+
+ conntrack = ofpbuf_push_uninit(out, sizeof(*conntrack));
+ out->header = &conntrack->ofpact;
+ ofpact_finish(out, &conntrack->ofpact);
+
+ if (conntrack->ofpact.len > sizeof(*conntrack)
+ && !(conntrack->flags & NX_CT_F_COMMIT)) {
+ const struct ofpact *a;
+ size_t ofpacts_len = conntrack->ofpact.len - sizeof(*conntrack);
+
+ OFPACT_FOR_EACH (a, conntrack->actions, ofpacts_len) {
+ if (a->type != OFPACT_NAT || ofpact_get_NAT(a)->flags
+ || ofpact_get_NAT(a)->range_af != AF_UNSPEC) {
+ VLOG_WARN_RL(&rl, "CT action requires commit flag if actions "
+ "other than NAT without arguments are specified.");
+ error = OFPERR_OFPBAC_BAD_ARGUMENT;
+ goto out;
+ }
+ }
+ }
+
+out:
+ ofpbuf_push_uninit(out, ct_offset);
+ return error;
+}
+
+static void
+encode_CT(const struct ofpact_conntrack *conntrack,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ struct nx_action_conntrack *nac;
+ const size_t ofs = out->size;
+ size_t len;
+
+ nac = put_NXAST_CT(out);
+ nac->flags = htons(conntrack->flags);
+ if (conntrack->zone_src.field) {
+ nac->zone_src = htonl(mf_nxm_header(conntrack->zone_src.field->id));
+ nac->zone_ofs_nbits = nxm_encode_ofs_nbits(conntrack->zone_src.ofs,
+ conntrack->zone_src.n_bits);
+ } else {
+ nac->zone_src = htonl(0);
+ nac->zone_imm = htons(conntrack->zone_imm);
+ }
+ nac->recirc_table = conntrack->recirc_table;
+ nac->alg = htons(conntrack->alg);
+
+ len = ofpacts_put_openflow_actions(conntrack->actions,
+ ofpact_ct_get_action_len(conntrack),
+ out, ofp_version);
+ len += sizeof(*nac);
+ nac = ofpbuf_at(out, ofs, sizeof(*nac));
+ nac->len = htons(len);
+}
+
+static char * OVS_WARN_UNUSED_RESULT parse_NAT(char *arg, struct ofpbuf *,
+ enum ofputil_protocol * OVS_UNUSED);
+
+/* Parses 'arg' as the argument to a "ct" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_CT(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols)
+{
+ const size_t ct_offset = ofpacts_pull(ofpacts);
+ struct ofpact_conntrack *oc;
+ char *error = NULL;
+ char *key, *value;
+
+ oc = ofpact_put_CT(ofpacts);
+ oc->flags = 0;
+ oc->recirc_table = NX_CT_RECIRC_NONE;
+ while (ofputil_parse_key_value(&arg, &key, &value)) {
+ if (!strcmp(key, "commit")) {
+ oc->flags |= NX_CT_F_COMMIT;
+ } else if (!strcmp(key, "table")) {
+ error = str_to_u8(value, "recirc_table", &oc->recirc_table);
+ if (!error && oc->recirc_table == NX_CT_RECIRC_NONE) {
+ error = xasprintf("invalid table %#"PRIx16, oc->recirc_table);
+ }
+ } else if (!strcmp(key, "zone")) {
+ error = str_to_u16(value, "zone", &oc->zone_imm);
+
+ if (error) {
+ free(error);
+ error = mf_parse_subfield(&oc->zone_src, value);
+ if (error) {
+ return error;
+ }
+ }
+ } else if (!strcmp(key, "alg")) {
+ error = str_to_connhelper(value, &oc->alg);
+ } else if (!strcmp(key, "nat")) {
+ const size_t nat_offset = ofpacts_pull(ofpacts);
+
+ error = parse_NAT(value, ofpacts, usable_protocols);
+ /* Update CT action pointer and length. */
+ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
+ oc = ofpacts->header;
+ } else if (!strcmp(key, "exec")) {
+ /* Hide existing actions from ofpacts_parse_copy(), so the
+ * nesting can be handled transparently. */
+ enum ofputil_protocol usable_protocols2;
+ const size_t exec_offset = ofpacts_pull(ofpacts);
+
+ /* Initializes 'usable_protocol2', fold it back to
+ * '*usable_protocols' afterwards, so that we do not lose
+ * restrictions already in there. */
+ error = ofpacts_parse_copy(value, ofpacts, &usable_protocols2,
+ false, OFPACT_CT);
+ *usable_protocols &= usable_protocols2;
+ ofpacts->header = ofpbuf_push_uninit(ofpacts, exec_offset);
+ oc = ofpacts->header;
+ } else {
+ error = xasprintf("invalid argument to \"ct\" action: `%s'", key);
+ }
+ if (error) {
+ break;
+ }
+ }
+
+ ofpact_finish(ofpacts, &oc->ofpact);
+ ofpbuf_push_uninit(ofpacts, ct_offset);
+ return error;
+}
+
+static void
+format_alg(int port, struct ds *s)
+{
+ if (port == IPPORT_FTP) {
+ ds_put_format(s, "alg=ftp,");
+ } else if (port) {
+ ds_put_format(s, "alg=%d,", port);
+ }
+}
+
+static void format_NAT(const struct ofpact_nat *a, struct ds *ds);
+
+static void
+format_CT(const struct ofpact_conntrack *a, struct ds *s)
+{
+ ds_put_cstr(s, "ct(");
+ if (a->flags & NX_CT_F_COMMIT) {
+ ds_put_cstr(s, "commit,");
+ }
+ if (a->recirc_table != NX_CT_RECIRC_NONE) {
+ ds_put_format(s, "table=%"PRIu8",", a->recirc_table);
+ }
+ if (a->zone_src.field) {
+ ds_put_format(s, "zone=");
+ mf_format_subfield(&a->zone_src, s);
+ ds_put_char(s, ',');
+ } else if (a->zone_imm) {
+ ds_put_format(s, "zone=%"PRIu16",", a->zone_imm);
+ }
+ /* If the first action is a NAT action, format it outside of the 'exec'
+ * envelope. */
+ const struct ofpact *action = a->actions;
+ size_t actions_len = ofpact_ct_get_action_len(a);
+ if (actions_len && action->type == OFPACT_NAT) {
+ format_NAT(ofpact_get_NAT(action), s);
+ ds_put_char(s, ',');
+ actions_len -= OFPACT_ALIGN(action->len);
+ action = ofpact_next(action);
+ }
+ if (actions_len) {
+ ds_put_cstr(s, "exec(");
+ ofpacts_format(action, actions_len, s);
+ ds_put_cstr(s, "),");
+ }
+ format_alg(a->alg, s);
+ ds_chomp(s, ',');
+ ds_put_char(s, ')');
+}
+\f
+/* NAT action. */
+
+/* Which optional fields are present? */
+enum nx_nat_range {
+ NX_NAT_RANGE_IPV4_MIN = 1 << 0, /* ovs_be32 */
+ NX_NAT_RANGE_IPV4_MAX = 1 << 1, /* ovs_be32 */
+ NX_NAT_RANGE_IPV6_MIN = 1 << 2, /* struct in6_addr */
+ NX_NAT_RANGE_IPV6_MAX = 1 << 3, /* struct in6_addr */
+ NX_NAT_RANGE_PROTO_MIN = 1 << 4, /* ovs_be16 */
+ NX_NAT_RANGE_PROTO_MAX = 1 << 5, /* ovs_be16 */
+};
+
+/* Action structure for NXAST_NAT. */
+struct nx_action_nat {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* At least 16. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_NAT. */
+ uint8_t pad[2]; /* Must be zero. */
+ ovs_be16 flags; /* Zero or more NX_NAT_F_* flags.
+ * Unspecified flag bits must be zero. */
+ ovs_be16 range_present; /* NX_NAT_RANGE_* */
+ /* Followed by optional parameters as specified by 'range_present' */
+};
+OFP_ASSERT(sizeof(struct nx_action_nat) == 16);
+
+static void
+encode_NAT(const struct ofpact_nat *nat,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct nx_action_nat *nan;
+ const size_t ofs = out->size;
+ uint16_t range_present = 0;
+
+ nan = put_NXAST_NAT(out);
+ nan->flags = htons(nat->flags);
+ if (nat->range_af == AF_INET) {
+ if (nat->range.addr.ipv4.min) {
+ ovs_be32 *min = ofpbuf_put_uninit(out, sizeof *min);
+ *min = nat->range.addr.ipv4.min;
+ range_present |= NX_NAT_RANGE_IPV4_MIN;
+ }
+ if (nat->range.addr.ipv4.max) {
+ ovs_be32 *max = ofpbuf_put_uninit(out, sizeof *max);
+ *max = nat->range.addr.ipv4.max;
+ range_present |= NX_NAT_RANGE_IPV4_MAX;
+ }
+ } else if (nat->range_af == AF_INET6) {
+ if (!ipv6_mask_is_any(&nat->range.addr.ipv6.min)) {
+ struct in6_addr *min = ofpbuf_put_uninit(out, sizeof *min);
+ *min = nat->range.addr.ipv6.min;
+ range_present |= NX_NAT_RANGE_IPV6_MIN;
+ }
+ if (!ipv6_mask_is_any(&nat->range.addr.ipv6.max)) {
+ struct in6_addr *max = ofpbuf_put_uninit(out, sizeof *max);
+ *max = nat->range.addr.ipv6.max;
+ range_present |= NX_NAT_RANGE_IPV6_MAX;
+ }
+ }
+ if (nat->range_af != AF_UNSPEC) {
+ if (nat->range.proto.min) {
+ ovs_be16 *min = ofpbuf_put_uninit(out, sizeof *min);
+ *min = htons(nat->range.proto.min);
+ range_present |= NX_NAT_RANGE_PROTO_MIN;
+ }
+ if (nat->range.proto.max) {
+ ovs_be16 *max = ofpbuf_put_uninit(out, sizeof *max);
+ *max = htons(nat->range.proto.max);
+ range_present |= NX_NAT_RANGE_PROTO_MAX;
+ }
+ }
+ pad_ofpat(out, ofs);
+ nan = ofpbuf_at(out, ofs, sizeof *nan);
+ nan->range_present = htons(range_present);
+}
+
+static enum ofperr
+decode_NXAST_RAW_NAT(const struct nx_action_nat *nan,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_nat *nat;
+ uint16_t range_present = ntohs(nan->range_present);
+ const char *opts = (char *)(nan + 1);
+ uint16_t len = ntohs(nan->len) - sizeof *nan;
+
+ nat = ofpact_put_NAT(out);
+ nat->flags = ntohs(nan->flags);
+
+#define NX_NAT_GET_OPT(DST, SRC, LEN, TYPE) \
+ (LEN >= sizeof(TYPE) \
+ ? (memcpy(DST, SRC, sizeof(TYPE)), LEN -= sizeof(TYPE), \
+ SRC += sizeof(TYPE)) \
+ : NULL)
+
+ nat->range_af = AF_UNSPEC;
+ if (range_present & NX_NAT_RANGE_IPV4_MIN) {
+ if (range_present & (NX_NAT_RANGE_IPV6_MIN | NX_NAT_RANGE_IPV6_MAX)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv4.min, opts, len, ovs_be32)
+ || !nat->range.addr.ipv4.min) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ nat->range_af = AF_INET;
+
+ if (range_present & NX_NAT_RANGE_IPV4_MAX) {
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv4.max, opts, len,
+ ovs_be32)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ if (ntohl(nat->range.addr.ipv4.max)
+ < ntohl(nat->range.addr.ipv4.min)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
+ } else if (range_present & NX_NAT_RANGE_IPV4_MAX) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ } else if (range_present & NX_NAT_RANGE_IPV6_MIN) {
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv6.min, opts, len,
+ struct in6_addr)
+ || ipv6_mask_is_any(&nat->range.addr.ipv6.min)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ nat->range_af = AF_INET6;
+
+ if (range_present & NX_NAT_RANGE_IPV6_MAX) {
+ if (!NX_NAT_GET_OPT(&nat->range.addr.ipv6.max, opts, len,
+ struct in6_addr)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ if (memcmp(&nat->range.addr.ipv6.max, &nat->range.addr.ipv6.min,
+ sizeof(struct in6_addr)) < 0) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
+ } else if (range_present & NX_NAT_RANGE_IPV6_MAX) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ if (range_present & NX_NAT_RANGE_PROTO_MIN) {
+ ovs_be16 proto;
+
+ if (nat->range_af == AF_UNSPEC) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ if (!NX_NAT_GET_OPT(&proto, opts, len, ovs_be16) || proto == 0) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ nat->range.proto.min = ntohs(proto);
+ if (range_present & NX_NAT_RANGE_PROTO_MAX) {
+ if (!NX_NAT_GET_OPT(&proto, opts, len, ovs_be16)) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ nat->range.proto.max = ntohs(proto);
+ if (nat->range.proto.max < nat->range.proto.min) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
+ } else if (range_present & NX_NAT_RANGE_PROTO_MAX) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
+ return 0;
+}
+
+static void
+format_NAT(const struct ofpact_nat *a, struct ds *ds)
+{
+ ds_put_cstr(ds, "nat");
+
+ if (a->flags & (NX_NAT_F_SRC | NX_NAT_F_DST)) {
+ ds_put_char(ds, '(');
+ ds_put_cstr(ds, a->flags & NX_NAT_F_SRC ? "src" : "dst");
+
+ if (a->range_af != AF_UNSPEC) {
+ ds_put_cstr(ds, "=");
+
+ if (a->range_af == AF_INET) {
+ ds_put_format(ds, IP_FMT, IP_ARGS(a->range.addr.ipv4.min));
+
+ if (a->range.addr.ipv4.max
+ && a->range.addr.ipv4.max != a->range.addr.ipv4.min) {
+ ds_put_format(ds, "-"IP_FMT,
+ IP_ARGS(a->range.addr.ipv4.max));
+ }
+ } else if (a->range_af == AF_INET6) {
+ ipv6_format_addr_bracket(&a->range.addr.ipv6.min, ds,
+ a->range.proto.min);
+
+ if (!ipv6_mask_is_any(&a->range.addr.ipv6.max)
+ && memcmp(&a->range.addr.ipv6.max, &a->range.addr.ipv6.min,
+ sizeof(struct in6_addr)) != 0) {
+ ds_put_char(ds, '-');
+ ipv6_format_addr_bracket(&a->range.addr.ipv6.max, ds,
+ a->range.proto.min);
+ }
+ }
+ if (a->range.proto.min) {
+ ds_put_char(ds, ':');
+ ds_put_format(ds, "%"PRIu16, a->range.proto.min);
+
+ if (a->range.proto.max
+ && a->range.proto.max != a->range.proto.min) {
+ ds_put_format(ds, "-%"PRIu16, a->range.proto.max);
+ }
+ }
+ ds_put_char(ds, ',');
+
+ if (a->flags & NX_NAT_F_PERSISTENT) {
+ ds_put_cstr(ds, "persistent,");
+ }
+ if (a->flags & NX_NAT_F_PROTO_HASH) {
+ ds_put_cstr(ds, "hash,");
+ }
+ if (a->flags & NX_NAT_F_PROTO_RANDOM) {
+ ds_put_cstr(ds, "random,");
+ }
+ }
+ ds_chomp(ds, ',');
+ ds_put_char(ds, ')');
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+str_to_nat_range(const char *s, struct ofpact_nat *on)
+{
+ char ipv6_s[IPV6_SCAN_LEN + 1];
+ int n = 0;
+
+ on->range_af = AF_UNSPEC;
+ if (ovs_scan_len(s, &n, IP_SCAN_FMT,
+ IP_SCAN_ARGS(&on->range.addr.ipv4.min))) {
+ on->range_af = AF_INET;
+
+ if (s[n] == '-') {
+ n++;
+ if (!ovs_scan_len(s, &n, IP_SCAN_FMT,
+ IP_SCAN_ARGS(&on->range.addr.ipv4.max))
+ || (ntohl(on->range.addr.ipv4.max)
+ < ntohl(on->range.addr.ipv4.min))) {
+ goto error;
+ }
+ }
+ } else if ((ovs_scan_len(s, &n, IPV6_SCAN_FMT, ipv6_s)
+ || ovs_scan_len(s, &n, "["IPV6_SCAN_FMT"]", ipv6_s))
+ && inet_pton(AF_INET6, ipv6_s, &on->range.addr.ipv6.min) == 1) {
+ on->range_af = AF_INET6;
+
+ if (s[n] == '-') {
+ n++;
+ if (!(ovs_scan_len(s, &n, IPV6_SCAN_FMT, ipv6_s)
+ || ovs_scan_len(s, &n, "["IPV6_SCAN_FMT"]", ipv6_s))
+ || inet_pton(AF_INET6, ipv6_s, &on->range.addr.ipv6.max) != 1
+ || memcmp(&on->range.addr.ipv6.max, &on->range.addr.ipv6.min,
+ sizeof on->range.addr.ipv6.max) < 0) {
+ goto error;
+ }
+ }
+ }
+ if (on->range_af != AF_UNSPEC && s[n] == ':') {
+ n++;
+ if (!ovs_scan_len(s, &n, "%"SCNu16, &on->range.proto.min)) {
+ goto error;
+ }
+ if (s[n] == '-') {
+ n++;
+ if (!ovs_scan_len(s, &n, "%"SCNu16, &on->range.proto.max)
+ || on->range.proto.max < on->range.proto.min) {
+ goto error;
+ }
+ }
+ }
+ if (strlen(s) != n) {
+ return xasprintf("garbage (%s) after nat range \"%s\" (pos: %d)",
+ &s[n], s, n);
+ }
+ return NULL;
+error:
+ return xasprintf("invalid nat range \"%s\"", s);
+}
+
+
+/* Parses 'arg' as the argument to a "nat" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_NAT(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_nat *on = ofpact_put_NAT(ofpacts);
+ char *key, *value;
+
+ on->flags = 0;
+ on->range_af = AF_UNSPEC;
+
+ while (ofputil_parse_key_value(&arg, &key, &value)) {
+ char *error = NULL;
+
+ if (!strcmp(key, "src")) {
+ on->flags |= NX_NAT_F_SRC;
+ error = str_to_nat_range(value, on);
+ } else if (!strcmp(key, "dst")) {
+ on->flags |= NX_NAT_F_DST;
+ error = str_to_nat_range(value, on);
+ } else if (!strcmp(key, "persistent")) {
+ on->flags |= NX_NAT_F_PERSISTENT;
+ } else if (!strcmp(key, "hash")) {
+ on->flags |= NX_NAT_F_PROTO_HASH;
+ } else if (!strcmp(key, "random")) {
+ on->flags |= NX_NAT_F_PROTO_RANDOM;
+ } else {
+ error = xasprintf("invalid key \"%s\" in \"nat\" argument",
+ key);
+ }
+ if (error) {
+ return error;
+ }
+ }
+ if (on->flags & NX_NAT_F_SRC && on->flags & NX_NAT_F_DST) {
+ return xasprintf("May only specify one of \"snat\" or \"dnat\".");
+ }
+ if (!(on->flags & NX_NAT_F_SRC || on->flags & NX_NAT_F_DST)) {
+ if (on->flags) {
+ return xasprintf("Flags allowed only with \"snat\" or \"dnat\".");
+ }
+ if (on->range_af != AF_UNSPEC) {
+ return xasprintf("Range allowed only with \"snat\" or \"dnat\".");
+ }
+ }
+ return NULL;
+}
+
+\f
+/* Meter instruction. */
+
+static void
+encode_METER(const struct ofpact_meter *meter,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (ofp_version >= OFP13_VERSION) {
+ instruction_put_OFPIT13_METER(out)->meter_id = htonl(meter->meter_id);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_METER(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols)
+{
+ *usable_protocols &= OFPUTIL_P_OF13_UP;
+ return str_to_u32(arg, &ofpact_put_METER(ofpacts)->meter_id);
+}
+
+static void
+format_METER(const struct ofpact_meter *a, struct ds *s)
+{
+ ds_put_format(s, "meter:%"PRIu32, a->meter_id);
+}
+\f
+/* Clear-Actions instruction. */
+
+static void
+encode_CLEAR_ACTIONS(const struct ofpact_null *null OVS_UNUSED,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out OVS_UNUSED)
+{
+ if (ofp_version > OFP10_VERSION) {
+ instruction_put_OFPIT11_CLEAR_ACTIONS(out);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_CLEAR_ACTIONS(char *arg OVS_UNUSED, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ ofpact_put_CLEAR_ACTIONS(ofpacts);
+ return NULL;
+}
+
+static void
+format_CLEAR_ACTIONS(const struct ofpact_null *a OVS_UNUSED, struct ds *s)
+{
+ ds_put_cstr(s, "clear_actions");
+}
+\f
+/* Write-Actions instruction. */
+
+static void
+encode_WRITE_ACTIONS(const struct ofpact_nest *actions,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (ofp_version > OFP10_VERSION) {
+ const size_t ofs = out->size;
+
+ instruction_put_OFPIT11_WRITE_ACTIONS(out);
+ ofpacts_put_openflow_actions(actions->actions,
+ ofpact_nest_get_action_len(actions),
+ out, ofp_version);
+ ofpacts_update_instruction_actions(out, ofs);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_WRITE_ACTIONS(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols)
+{
+ size_t ofs = ofpacts_pull(ofpacts);
+ struct ofpact_nest *on;
+ char *error;
+
+ /* Add a Write-Actions instruction and then pull it off. */
+ ofpact_put(ofpacts, OFPACT_WRITE_ACTIONS, sizeof *on);
+ ofpbuf_pull(ofpacts, sizeof *on);
+
+ /* Parse nested actions.
+ *
+ * We pulled off "write-actions" and the previous actions because the
+ * OFPACT_WRITE_ACTIONS is only partially constructed: its length is such
+ * that it doesn't actually include the nested actions. That means that
+ * ofpacts_parse() would reject them as being part of an Apply-Actions that
+ * follows a Write-Actions, which is an invalid order. */
+ error = ofpacts_parse(arg, ofpacts, usable_protocols, false,
+ OFPACT_WRITE_ACTIONS);
+
+ /* Put the Write-Actions back on and update its length. */
+ on = ofpbuf_push_uninit(ofpacts, sizeof *on);
+ on->ofpact.len = ofpacts->size;
+
+ /* Put any previous actions or instructions back on. */
+ ofpbuf_push_uninit(ofpacts, ofs);
+
+ return error;
+}
+
+static void
+format_WRITE_ACTIONS(const struct ofpact_nest *a, struct ds *s)
+{
+ ds_put_cstr(s, "write_actions(");
+ ofpacts_format(a->actions, ofpact_nest_get_action_len(a), s);
+ ds_put_char(s, ')');
+}
+\f
+/* Action structure for NXAST_WRITE_METADATA.
+ *
+ * Modifies the 'mask' bits of the metadata value. */
+struct nx_action_write_metadata {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length is 32. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_WRITE_METADATA. */
+ uint8_t zeros[6]; /* Must be zero. */
+ ovs_be64 metadata; /* Metadata register. */
+ ovs_be64 mask; /* Metadata mask. */
+};
+OFP_ASSERT(sizeof(struct nx_action_write_metadata) == 32);
+
+static enum ofperr
+decode_NXAST_RAW_WRITE_METADATA(const struct nx_action_write_metadata *nawm,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct ofpact_metadata *om;
+
+ if (!is_all_zeros(nawm->zeros, sizeof nawm->zeros)) {
+ return OFPERR_NXBRC_MUST_BE_ZERO;
+ }
+
+ om = ofpact_put_WRITE_METADATA(out);
+ om->metadata = nawm->metadata;
+ om->mask = nawm->mask;
+
+ return 0;
+}
+
+static void
+encode_WRITE_METADATA(const struct ofpact_metadata *metadata,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (ofp_version == OFP10_VERSION) {
+ struct nx_action_write_metadata *nawm;
+
+ nawm = put_NXAST_WRITE_METADATA(out);
+ nawm->metadata = metadata->metadata;
+ nawm->mask = metadata->mask;
+ } else {
+ struct ofp11_instruction_write_metadata *oiwm;
+
+ oiwm = instruction_put_OFPIT11_WRITE_METADATA(out);
+ oiwm->metadata = metadata->metadata;
+ oiwm->metadata_mask = metadata->mask;
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_WRITE_METADATA(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols)
+{
+ struct ofpact_metadata *om;
+ char *mask = strchr(arg, '/');
+
+ *usable_protocols &= OFPUTIL_P_NXM_OF11_UP;
+
+ om = ofpact_put_WRITE_METADATA(ofpacts);
+ if (mask) {
+ char *error;
+
+ *mask = '\0';
+ error = str_to_be64(mask + 1, &om->mask);
+ if (error) {
+ return error;
+ }
+ } else {
+ om->mask = OVS_BE64_MAX;
+ }
+
+ return str_to_be64(arg, &om->metadata);
+}
+
+static void
+format_WRITE_METADATA(const struct ofpact_metadata *a, struct ds *s)
+{
+ ds_put_format(s, "write_metadata:%#"PRIx64, ntohll(a->metadata));
+ if (a->mask != OVS_BE64_MAX) {
+ ds_put_format(s, "/%#"PRIx64, ntohll(a->mask));
+ }
+}
+\f
+/* Goto-Table instruction. */
+
+static void
+encode_GOTO_TABLE(const struct ofpact_goto_table *goto_table,
+ enum ofp_version ofp_version, struct ofpbuf *out)
+{
+ if (ofp_version == OFP10_VERSION) {
+ struct nx_action_resubmit *nar;
+
+ nar = put_NXAST_RESUBMIT_TABLE(out);
+ nar->table = goto_table->table_id;
+ nar->in_port = htons(ofp_to_u16(OFPP_IN_PORT));
+ } else {
+ struct ofp11_instruction_goto_table *oigt;
+
+ oigt = instruction_put_OFPIT11_GOTO_TABLE(out);
+ oigt->table_id = goto_table->table_id;
+ memset(oigt->pad, 0, sizeof oigt->pad);
+ }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_GOTO_TABLE(char *arg, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+ struct ofpact_goto_table *ogt = ofpact_put_GOTO_TABLE(ofpacts);
+ char *table_s = strsep(&arg, ",");
+ if (!table_s || !table_s[0]) {
+ return xstrdup("instruction goto-table needs table id");
+ }
+ return str_to_u8(table_s, "table", &ogt->table_id);
+}
+
+static void
+format_GOTO_TABLE(const struct ofpact_goto_table *a, struct ds *s)
+{
+ ds_put_format(s, "goto_table:%"PRIu8, a->table_id);
+}
+\f
+static void
+log_bad_action(const struct ofp_action_header *actions, size_t actions_len,
+ const struct ofp_action_header *bad_action, enum ofperr error)
+{
+ if (!VLOG_DROP_WARN(&rl)) {
+ struct ds s;
+
+ ds_init(&s);
+ ds_put_hex_dump(&s, actions, actions_len, 0, false);
+ VLOG_WARN("bad action at offset %#"PRIxPTR" (%s):\n%s",
+ (char *)bad_action - (char *)actions,
+ ofperr_get_name(error), ds_cstr(&s));
+ ds_destroy(&s);
+ }
+}
+
+static enum ofperr
+ofpacts_decode(const void *actions, size_t actions_len,
+ enum ofp_version ofp_version, struct ofpbuf *ofpacts)
+{
+ struct ofpbuf openflow = ofpbuf_const_initializer(actions, actions_len);
+ while (openflow.size) {
+ const struct ofp_action_header *action = openflow.data;
+ enum ofp_raw_action_type raw;
+ enum ofperr error;
+ uint64_t arg;
+
+ error = ofpact_pull_raw(&openflow, ofp_version, &raw, &arg);
+ if (!error) {
+ error = ofpact_decode(action, raw, ofp_version, arg, ofpacts);
+ }
+
+ if (error) {
+ log_bad_action(actions, actions_len, action, error);
+ return error;
+ }
+ }
+ return 0;
+}
+
+static enum ofperr
+ofpacts_pull_openflow_actions__(struct ofpbuf *openflow,
+ unsigned int actions_len,
+ enum ofp_version version,
+ uint32_t allowed_ovsinsts,
+ struct ofpbuf *ofpacts,
+ enum ofpact_type outer_action)
+{
+ const struct ofp_action_header *actions;
+ size_t orig_size = ofpacts->size;
+ enum ofperr error;
+
+ if (actions_len % OFP_ACTION_ALIGN != 0) {
+ VLOG_WARN_RL(&rl, "OpenFlow message actions length %u is not a "
+ "multiple of %d", actions_len, OFP_ACTION_ALIGN);
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ actions = ofpbuf_try_pull(openflow, actions_len);
+ if (actions == NULL) {
+ VLOG_WARN_RL(&rl, "OpenFlow message actions length %u exceeds "
+ "remaining message length (%"PRIu32")",
+ actions_len, openflow->size);
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ error = ofpacts_decode(actions, actions_len, version, ofpacts);
+ if (error) {
+ ofpacts->size = orig_size;
+ return error;
+ }
+
+ error = ofpacts_verify(ofpacts->data, ofpacts->size, allowed_ovsinsts,
+ outer_action);
+ if (error) {
+ ofpacts->size = orig_size;
+ }
+ return error;