+ 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_update_len(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_update_len(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_update_len(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_update_len(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);