X-Git-Url: http://git.cascardo.eti.br/?a=blobdiff_plain;f=lib%2Fofp-actions.c;h=702575d248577fbdded533b1e5da951a1f50fdf1;hb=HEAD;hp=556fd052ec1887233896d7329f01b049fa62a026;hpb=e3f8f887480856064fab42bc01ae364d64506d85;p=cascardo%2Fovs.git diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 556fd052e..702575d24 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc. + * Copyright (c) 2008-2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,73 +15,477 @@ */ #include +#include + #include "ofp-actions.h" #include "bundle.h" #include "byte-order.h" #include "compiler.h" +#include "dummy.h" #include "dynamic-string.h" +#include "hmap.h" #include "learn.h" #include "meta-flow.h" #include "multipath.h" #include "nx-match.h" +#include "odp-netlink.h" +#include "ofp-parse.h" +#include "ofp-prop.h" #include "ofp-util.h" #include "ofpbuf.h" +#include "unaligned.h" #include "util.h" -#include "vlog.h" +#include "openvswitch/vlog.h" VLOG_DEFINE_THIS_MODULE(ofp_actions); static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - -/* Converting OpenFlow 1.0 to ofpacts. */ -union ofp_action { - ovs_be16 type; - struct ofp_action_header header; - struct ofp_action_vendor_header vendor; - struct ofp10_action_output output10; - struct ofp_action_vlan_vid vlan_vid; - struct ofp_action_vlan_pcp vlan_pcp; - struct ofp_action_nw_addr nw_addr; - struct ofp_action_nw_tos nw_tos; - struct ofp11_action_nw_ecn nw_ecn; - struct ofp11_action_nw_ttl nw_ttl; - struct ofp_action_tp_port tp_port; - struct ofp_action_dl_addr dl_addr; - struct ofp10_action_enqueue enqueue; - struct ofp11_action_output ofp11_output; - struct ofp11_action_push push; - struct ofp11_action_pop_mpls ofp11_pop_mpls; - struct ofp11_action_set_queue ofp11_set_queue; - struct ofp11_action_mpls_ttl ofp11_mpls_ttl; - struct ofp11_action_group group; - struct ofp12_action_set_field set_field; - struct nx_action_header nxa_header; - struct nx_action_resubmit resubmit; - struct nx_action_set_tunnel set_tunnel; - struct nx_action_set_tunnel64 set_tunnel64; - struct nx_action_write_metadata write_metadata; - struct nx_action_set_queue set_queue; - struct nx_action_reg_move reg_move; - struct nx_action_reg_load reg_load; - struct nx_action_stack stack; - struct nx_action_note note; - struct nx_action_multipath multipath; - struct nx_action_bundle bundle; - struct nx_action_output_reg output_reg; - struct nx_action_cnt_ids cnt_ids; - struct nx_action_fin_timeout fin_timeout; - struct nx_action_controller controller; - struct nx_action_push_mpls push_mpls; - struct nx_action_mpls_ttl mpls_ttl; - struct nx_action_pop_mpls pop_mpls; - struct nx_action_sample sample; - struct nx_action_learn learn; +struct ofp_action_header; + +/* Raw identifiers for OpenFlow actions. + * + * Decoding and encoding OpenFlow actions across multiple versions is difficult + * to do in a clean, consistent way. This enumeration lays out all of the + * forms of actions that Open vSwitch supports. + * + * The comments here must follow a stylized form because the + * "extract-ofp-actions" program parses them at build time to generate data + * tables. + * + * - The first part of each comment specifies the vendor, OpenFlow versions, + * and type for each protocol that supports the action: + * + * # The vendor is OF for standard OpenFlow actions, NX for Nicira + * extension actions. (Support for other vendors can be added, but + * it can't be done just based on a vendor ID definition alone + * because OpenFlow doesn't define a standard way to specify a + * subtype for vendor actions, so other vendors might do it different + * from Nicira.) + * + * # The version can specify a specific OpenFlow version, a version + * range delimited by "-", or an open-ended range with "+". + * + * # The type, in parentheses, is the action type number (for standard + * OpenFlow actions) or subtype (for vendor extension actions). + * + * # Optionally one may add "is deprecated" followed by a + * human-readable reason in parentheses (which will be used in log + * messages), if a particular action should no longer be used. + * + * Multiple such specifications may be separated by commas. + * + * - The second part describes the action's wire format. It may be: + * + * # "struct ": The struct fully specifies the wire format. The + * action is exactly the size of the struct. (Thus, the struct must + * be an exact multiple of 8 bytes in size.) + * + * # "struct , ...": The struct specifies the beginning of the + * wire format. An instance of the action is either the struct's + * exact size, or a multiple of 8 bytes longer. + * + * # "uint_t" or "ovs_be": The action consists of a (standard or + * vendor extension) header, followed by 0 or more pad bytes to align + * to a multiple of bits, followed by an argument of the given + * type, followed by 0 or more pad bytes to bring the total action up + * to a multiple of 8 bytes. + * + * # "void": The action is just a (standard or vendor extension) + * header. + * + * - Optional additional text enclosed in square brackets is commentary for + * the human reader. + */ +enum ofp_raw_action_type { +/* ## ----------------- ## */ +/* ## Standard actions. ## */ +/* ## ----------------- ## */ + + /* OF1.0(0): struct ofp10_action_output. */ + OFPAT_RAW10_OUTPUT, + /* OF1.1+(0): struct ofp11_action_output. */ + OFPAT_RAW11_OUTPUT, + + /* OF1.0(1): uint16_t. */ + OFPAT_RAW10_SET_VLAN_VID, + /* OF1.0(2): uint8_t. */ + OFPAT_RAW10_SET_VLAN_PCP, + + /* OF1.1(1), OF1.2+(1) is deprecated (use Set-Field): uint16_t. + * + * [Semantics differ slightly between the 1.0 and 1.1 versions of the VLAN + * modification actions: the 1.0 versions push a VLAN header if none is + * present, but the 1.1 versions do not. That is the only reason that we + * distinguish their raw action types.] */ + OFPAT_RAW11_SET_VLAN_VID, + /* OF1.1(2), OF1.2+(2) is deprecated (use Set-Field): uint8_t. */ + OFPAT_RAW11_SET_VLAN_PCP, + + /* OF1.1+(17): ovs_be16. + * + * [The argument is the Ethertype, e.g. ETH_TYPE_VLAN_8021Q, not the VID or + * TCI.] */ + OFPAT_RAW11_PUSH_VLAN, + + /* OF1.0(3): void. */ + OFPAT_RAW10_STRIP_VLAN, + /* OF1.1+(18): void. */ + OFPAT_RAW11_POP_VLAN, + + /* OF1.0(4), OF1.1(3), OF1.2+(3) is deprecated (use Set-Field): struct + * ofp_action_dl_addr. */ + OFPAT_RAW_SET_DL_SRC, + + /* OF1.0(5), OF1.1(4), OF1.2+(4) is deprecated (use Set-Field): struct + * ofp_action_dl_addr. */ + OFPAT_RAW_SET_DL_DST, + + /* OF1.0(6), OF1.1(5), OF1.2+(5) is deprecated (use Set-Field): + * ovs_be32. */ + OFPAT_RAW_SET_NW_SRC, + + /* OF1.0(7), OF1.1(6), OF1.2+(6) is deprecated (use Set-Field): + * ovs_be32. */ + OFPAT_RAW_SET_NW_DST, + + /* OF1.0(8), OF1.1(7), OF1.2+(7) is deprecated (use Set-Field): uint8_t. */ + OFPAT_RAW_SET_NW_TOS, + + /* OF1.1(8), OF1.2+(8) is deprecated (use Set-Field): uint8_t. */ + OFPAT_RAW11_SET_NW_ECN, + + /* OF1.0(9), OF1.1(9), OF1.2+(9) is deprecated (use Set-Field): + * ovs_be16. */ + OFPAT_RAW_SET_TP_SRC, + + /* OF1.0(10), OF1.1(10), OF1.2+(10) is deprecated (use Set-Field): + * ovs_be16. */ + OFPAT_RAW_SET_TP_DST, + + /* OF1.0(11): struct ofp10_action_enqueue. */ + OFPAT_RAW10_ENQUEUE, + + /* NX1.0(30), OF1.1(13), OF1.2+(13) is deprecated (use Set-Field): + * ovs_be32. */ + OFPAT_RAW_SET_MPLS_LABEL, + + /* NX1.0(31), OF1.1(14), OF1.2+(14) is deprecated (use Set-Field): + * uint8_t. */ + OFPAT_RAW_SET_MPLS_TC, + + /* NX1.0(25), OF1.1(15), OF1.2+(15) is deprecated (use Set-Field): + * uint8_t. */ + OFPAT_RAW_SET_MPLS_TTL, + + /* NX1.0(26), OF1.1+(16): void. */ + OFPAT_RAW_DEC_MPLS_TTL, + + /* NX1.0(23), OF1.1+(19): ovs_be16. + * + * [The argument is the Ethertype, e.g. ETH_TYPE_MPLS, not the label.] */ + OFPAT_RAW_PUSH_MPLS, + + /* NX1.0(24), OF1.1+(20): ovs_be16. + * + * [The argument is the Ethertype, e.g. ETH_TYPE_IPV4 if at BoS or + * ETH_TYPE_MPLS otherwise, not the label.] */ + OFPAT_RAW_POP_MPLS, + + /* NX1.0(4), OF1.1+(21): uint32_t. */ + OFPAT_RAW_SET_QUEUE, + + /* OF1.1+(22): uint32_t. */ + OFPAT_RAW11_GROUP, + + /* OF1.1+(23): uint8_t. */ + OFPAT_RAW11_SET_NW_TTL, + + /* NX1.0(18), OF1.1+(24): void. */ + OFPAT_RAW_DEC_NW_TTL, + /* NX1.0+(21): struct nx_action_cnt_ids, ... */ + NXAST_RAW_DEC_TTL_CNT_IDS, + + /* OF1.2-1.4(25): struct ofp12_action_set_field, ... */ + OFPAT_RAW12_SET_FIELD, + /* OF1.5+(25): struct ofp12_action_set_field, ... */ + OFPAT_RAW15_SET_FIELD, + /* NX1.0-1.4(7): struct nx_action_reg_load. + * + * [In OpenFlow 1.5, set_field is a superset of reg_load functionality, so + * we drop reg_load.] */ + NXAST_RAW_REG_LOAD, + /* NX1.0-1.4(33): struct nx_action_reg_load2, ... + * + * [In OpenFlow 1.5, set_field is a superset of reg_load2 functionality, so + * we drop reg_load2.] */ + NXAST_RAW_REG_LOAD2, + + /* OF1.5+(28): struct ofp15_action_copy_field, ... */ + OFPAT_RAW15_COPY_FIELD, + /* ONF1.3-1.4(3200): struct onf_action_copy_field, ... */ + ONFACT_RAW13_COPY_FIELD, + /* NX1.0-1.4(6): struct nx_action_reg_move, ... */ + NXAST_RAW_REG_MOVE, + +/* ## ------------------------- ## */ +/* ## Nicira extension actions. ## */ +/* ## ------------------------- ## */ + +/* Actions similar to standard actions are listed with the standard actions. */ + + /* NX1.0+(1): uint16_t. */ + NXAST_RAW_RESUBMIT, + /* NX1.0+(14): struct nx_action_resubmit. */ + NXAST_RAW_RESUBMIT_TABLE, + + /* NX1.0+(2): uint32_t. */ + NXAST_RAW_SET_TUNNEL, + /* NX1.0+(9): uint64_t. */ + NXAST_RAW_SET_TUNNEL64, + + /* NX1.0+(5): void. */ + NXAST_RAW_POP_QUEUE, + + /* NX1.0+(8): struct nx_action_note, ... */ + NXAST_RAW_NOTE, + + /* NX1.0+(10): struct nx_action_multipath. */ + NXAST_RAW_MULTIPATH, + + /* NX1.0+(12): struct nx_action_bundle, ... */ + NXAST_RAW_BUNDLE, + /* NX1.0+(13): struct nx_action_bundle, ... */ + NXAST_RAW_BUNDLE_LOAD, + + /* NX1.0+(15): struct nx_action_output_reg. */ + NXAST_RAW_OUTPUT_REG, + /* NX1.0+(32): struct nx_action_output_reg2. */ + NXAST_RAW_OUTPUT_REG2, + + /* NX1.0+(16): struct nx_action_learn, ... */ + NXAST_RAW_LEARN, + + /* NX1.0+(17): void. */ + NXAST_RAW_EXIT, + + /* NX1.0+(19): struct nx_action_fin_timeout. */ + NXAST_RAW_FIN_TIMEOUT, + + /* NX1.0+(20): struct nx_action_controller. */ + NXAST_RAW_CONTROLLER, + /* NX1.0+(37): struct nx_action_controller2, ... */ + NXAST_RAW_CONTROLLER2, + + /* NX1.0+(22): struct nx_action_write_metadata. */ + NXAST_RAW_WRITE_METADATA, + + /* NX1.0+(27): struct nx_action_stack. */ + NXAST_RAW_STACK_PUSH, + + /* NX1.0+(28): struct nx_action_stack. */ + NXAST_RAW_STACK_POP, + + /* NX1.0+(29): struct nx_action_sample. */ + NXAST_RAW_SAMPLE, + + /* NX1.0+(34): struct nx_action_conjunction. */ + NXAST_RAW_CONJUNCTION, + + /* NX1.0+(35): struct nx_action_conntrack, ... */ + NXAST_RAW_CT, + + /* NX1.0+(36): struct nx_action_nat, ... */ + NXAST_RAW_NAT, + +/* ## ------------------ ## */ +/* ## Debugging actions. ## */ +/* ## ------------------ ## */ + +/* These are intentionally undocumented, subject to change, and ovs-vswitchd */ +/* accepts them only if started with --enable-dummy. */ + + /* NX1.0+(255): void. */ + NXAST_RAW_DEBUG_RECIRC, +}; + +/* OpenFlow actions are always a multiple of 8 bytes in length. */ +#define OFP_ACTION_ALIGN 8 + +/* Define a few functions for working with instructions. */ +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) \ + static inline const struct STRUCT * OVS_UNUSED \ + instruction_get_##ENUM(const struct ofp11_instruction *inst)\ + { \ + ovs_assert(inst->type == htons(ENUM)); \ + return ALIGNED_CAST(struct STRUCT *, inst); \ + } \ + \ + static inline void OVS_UNUSED \ + instruction_init_##ENUM(struct STRUCT *s) \ + { \ + memset(s, 0, sizeof *s); \ + s->type = htons(ENUM); \ + s->len = htons(sizeof *s); \ + } \ + \ + static inline struct STRUCT * OVS_UNUSED \ + instruction_put_##ENUM(struct ofpbuf *buf) \ + { \ + struct STRUCT *s = ofpbuf_put_uninit(buf, sizeof *s); \ + instruction_init_##ENUM(s); \ + return s; \ + } +OVS_INSTRUCTIONS +#undef DEFINE_INST + +static void ofpacts_update_instruction_actions(struct ofpbuf *openflow, + size_t ofs); +static void pad_ofpat(struct ofpbuf *openflow, size_t start_ofs); + +static enum ofperr ofpacts_verify(const struct ofpact[], size_t ofpacts_len, + uint32_t allowed_ovsinsts, + enum ofpact_type outer_action); + +static void ofpact_put_set_field(struct ofpbuf *openflow, enum ofp_version, + enum mf_field_id, uint64_t value); + +static enum ofperr ofpact_pull_raw(struct ofpbuf *, enum ofp_version, + enum ofp_raw_action_type *, uint64_t *arg); +static void *ofpact_put_raw(struct ofpbuf *, enum ofp_version, + enum ofp_raw_action_type, uint64_t arg); + +static char *OVS_WARN_UNUSED_RESULT ofpacts_parse( + char *str, struct ofpbuf *ofpacts, enum ofputil_protocol *usable_protocols, + bool allow_instructions, enum ofpact_type outer_action); +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); +static char * OVS_WARN_UNUSED_RESULT ofpacts_parse_copy( + const char *s_, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols, + bool allow_instructions, enum ofpact_type outer_action); + +/* Returns the ofpact following 'ofpact', except that if 'ofpact' contains + * nested ofpacts it returns the first one. */ +struct ofpact * +ofpact_next_flattened(const struct ofpact *ofpact) +{ + switch (ofpact->type) { + case OFPACT_OUTPUT: + case OFPACT_GROUP: + case OFPACT_CONTROLLER: + case OFPACT_ENQUEUE: + case OFPACT_OUTPUT_REG: + case OFPACT_BUNDLE: + case OFPACT_SET_FIELD: + case OFPACT_SET_VLAN_VID: + case OFPACT_SET_VLAN_PCP: + case OFPACT_STRIP_VLAN: + case OFPACT_PUSH_VLAN: + case OFPACT_SET_ETH_SRC: + case OFPACT_SET_ETH_DST: + case OFPACT_SET_IPV4_SRC: + case OFPACT_SET_IPV4_DST: + case OFPACT_SET_IP_DSCP: + case OFPACT_SET_IP_ECN: + case OFPACT_SET_IP_TTL: + case OFPACT_SET_L4_SRC_PORT: + case OFPACT_SET_L4_DST_PORT: + case OFPACT_REG_MOVE: + case OFPACT_STACK_PUSH: + case OFPACT_STACK_POP: + case OFPACT_DEC_TTL: + case OFPACT_SET_MPLS_LABEL: + case OFPACT_SET_MPLS_TC: + case OFPACT_SET_MPLS_TTL: + case OFPACT_DEC_MPLS_TTL: + case OFPACT_PUSH_MPLS: + case OFPACT_POP_MPLS: + case OFPACT_SET_TUNNEL: + case OFPACT_SET_QUEUE: + case OFPACT_POP_QUEUE: + case OFPACT_FIN_TIMEOUT: + case OFPACT_RESUBMIT: + case OFPACT_LEARN: + case OFPACT_CONJUNCTION: + case OFPACT_MULTIPATH: + case OFPACT_NOTE: + case OFPACT_EXIT: + case OFPACT_SAMPLE: + case OFPACT_UNROLL_XLATE: + case OFPACT_DEBUG_RECIRC: + case OFPACT_METER: + case OFPACT_CLEAR_ACTIONS: + case OFPACT_WRITE_METADATA: + case OFPACT_GOTO_TABLE: + case OFPACT_NAT: + return ofpact_next(ofpact); + + case OFPACT_CT: + return ofpact_get_CT(ofpact)->actions; + + case OFPACT_WRITE_ACTIONS: + return ofpact_get_WRITE_ACTIONS(ofpact)->actions; + } + + OVS_NOT_REACHED(); +} + +/* Pull off existing actions or instructions. Used by nesting actions to keep + * ofpacts_parse() oblivious of actions nesting. + * + * Push the actions back on after nested parsing, e.g.: + * + * size_t ofs = ofpacts_pull(ofpacts); + * ...nested parsing... + * ofpbuf_push_uninit(ofpacts, ofs); + */ +static size_t +ofpacts_pull(struct ofpbuf *ofpacts) +{ + size_t ofs; + + ofs = ofpacts->size; + ofpbuf_pull(ofpacts, ofs); + + return ofs; +} + +#include "ofp-actions.inc1" + +/* Output actions. */ + +/* Action structure for OFPAT10_OUTPUT, which sends packets out 'port'. + * When the 'port' is the OFPP_CONTROLLER, 'max_len' indicates the max + * number of bytes to send. A 'max_len' of zero means no bytes of the + * packet should be sent. */ +struct ofp10_action_output { + ovs_be16 type; /* OFPAT10_OUTPUT. */ + ovs_be16 len; /* Length is 8. */ + ovs_be16 port; /* Output port. */ + ovs_be16 max_len; /* Max length to send to controller. */ +}; +OFP_ASSERT(sizeof(struct ofp10_action_output) == 8); + +/* Action structure for OFPAT_OUTPUT, which sends packets out 'port'. + * When the 'port' is the OFPP_CONTROLLER, 'max_len' indicates the max + * number of bytes to send. A 'max_len' of zero means no bytes of the + * packet should be sent.*/ +struct ofp11_action_output { + ovs_be16 type; /* OFPAT11_OUTPUT. */ + ovs_be16 len; /* Length is 16. */ + ovs_be32 port; /* Output port. */ + ovs_be16 max_len; /* Max length to send to controller. */ + uint8_t pad[6]; /* Pad to 64 bits. */ }; +OFP_ASSERT(sizeof(struct ofp11_action_output) == 16); static enum ofperr -output_from_openflow10(const struct ofp10_action_output *oao, - struct ofpbuf *out) +decode_OFPAT_RAW10_OUTPUT(const struct ofp10_action_output *oao, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { struct ofpact_output *output; @@ -89,2577 +493,6508 @@ output_from_openflow10(const struct ofp10_action_output *oao, output->port = u16_to_ofp(ntohs(oao->port)); output->max_len = ntohs(oao->max_len); - return ofputil_check_output_port(output->port, OFPP_MAX); + return ofpact_check_output_port(output->port, OFPP_MAX); } static enum ofperr -enqueue_from_openflow10(const struct ofp10_action_enqueue *oae, - struct ofpbuf *out) +decode_OFPAT_RAW11_OUTPUT(const struct ofp11_action_output *oao, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - struct ofpact_enqueue *enqueue; + struct ofpact_output *output; + enum ofperr error; - enqueue = ofpact_put_ENQUEUE(out); - enqueue->port = u16_to_ofp(ntohs(oae->port)); - enqueue->queue = ntohl(oae->queue_id); - if (ofp_to_u16(enqueue->port) >= ofp_to_u16(OFPP_MAX) - && enqueue->port != OFPP_IN_PORT - && enqueue->port != OFPP_LOCAL) { - return OFPERR_OFPBAC_BAD_OUT_PORT; + output = ofpact_put_OUTPUT(out); + output->max_len = ntohs(oao->max_len); + + error = ofputil_port_from_ofp11(oao->port, &output->port); + if (error) { + return error; } - return 0; + + return ofpact_check_output_port(output->port, OFPP_MAX); } static void -resubmit_from_openflow(const struct nx_action_resubmit *nar, - struct ofpbuf *out) +encode_OUTPUT(const struct ofpact_output *output, + enum ofp_version ofp_version, struct ofpbuf *out) { - struct ofpact_resubmit *resubmit; + if (ofp_version == OFP10_VERSION) { + struct ofp10_action_output *oao; - resubmit = ofpact_put_RESUBMIT(out); - resubmit->ofpact.compat = OFPUTIL_NXAST_RESUBMIT; - resubmit->in_port = u16_to_ofp(ntohs(nar->in_port)); - resubmit->table_id = 0xff; + oao = put_OFPAT10_OUTPUT(out); + oao->port = htons(ofp_to_u16(output->port)); + oao->max_len = htons(output->max_len); + } else { + struct ofp11_action_output *oao; + + oao = put_OFPAT11_OUTPUT(out); + oao->port = ofputil_port_to_ofp11(output->port); + oao->max_len = htons(output->max_len); + } } -static enum ofperr -resubmit_table_from_openflow(const struct nx_action_resubmit *nar, - struct ofpbuf *out) +static char * OVS_WARN_UNUSED_RESULT +parse_OUTPUT(const char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) { - struct ofpact_resubmit *resubmit; + if (strchr(arg, '[')) { + struct ofpact_output_reg *output_reg; - if (nar->pad[0] || nar->pad[1] || nar->pad[2]) { - return OFPERR_OFPBAC_BAD_ARGUMENT; + output_reg = ofpact_put_OUTPUT_REG(ofpacts); + output_reg->max_len = UINT16_MAX; + return mf_parse_subfield(&output_reg->src, arg); + } else { + struct ofpact_output *output; + + output = ofpact_put_OUTPUT(ofpacts); + if (!ofputil_port_from_string(arg, &output->port)) { + return xasprintf("%s: output to unknown port", arg); + } + output->max_len = output->port == OFPP_CONTROLLER ? UINT16_MAX : 0; + return NULL; } +} - resubmit = ofpact_put_RESUBMIT(out); - resubmit->ofpact.compat = OFPUTIL_NXAST_RESUBMIT_TABLE; - resubmit->in_port = u16_to_ofp(ntohs(nar->in_port)); - resubmit->table_id = nar->table; - return 0; +static void +format_OUTPUT(const struct ofpact_output *a, struct ds *s) +{ + if (ofp_to_u16(a->port) < ofp_to_u16(OFPP_MAX)) { + ds_put_format(s, "output:%"PRIu16, a->port); + } else { + ofputil_format_port(a->port, s); + if (a->port == OFPP_CONTROLLER) { + ds_put_format(s, ":%"PRIu16, a->max_len); + } + } } + +/* Group actions. */ static enum ofperr -output_reg_from_openflow(const struct nx_action_output_reg *naor, +decode_OFPAT_RAW11_GROUP(uint32_t group_id, + enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out) { - struct ofpact_output_reg *output_reg; + ofpact_put_GROUP(out)->group_id = group_id; + return 0; +} - if (!is_all_zeros(naor->zero, sizeof naor->zero)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; +static void +encode_GROUP(const struct ofpact_group *group, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + if (ofp_version == OFP10_VERSION) { + /* XXX */ + } else { + put_OFPAT11_GROUP(out, group->group_id); } +} - output_reg = ofpact_put_OUTPUT_REG(out); - output_reg->src.field = mf_from_nxm_header(ntohl(naor->src)); - output_reg->src.ofs = nxm_decode_ofs(naor->ofs_nbits); - output_reg->src.n_bits = nxm_decode_n_bits(naor->ofs_nbits); - output_reg->max_len = ntohs(naor->max_len); - - return mf_check_src(&output_reg->src, NULL); +static char * OVS_WARN_UNUSED_RESULT +parse_GROUP(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_u32(arg, &ofpact_put_GROUP(ofpacts)->group_id); } static void -fin_timeout_from_openflow(const struct nx_action_fin_timeout *naft, - struct ofpbuf *out) +format_GROUP(const struct ofpact_group *a, struct ds *s) { - 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); + ds_put_format(s, "group:%"PRIu32, a->group_id); } + +/* Action structure for NXAST_CONTROLLER. + * + * This generalizes using OFPAT_OUTPUT to send a packet to OFPP_CONTROLLER. In + * addition to the 'max_len' that OFPAT_OUTPUT supports, it also allows + * specifying: + * + * - 'reason': The reason code to use in the ofp_packet_in or nx_packet_in. + * + * - 'controller_id': The ID of the controller connection to which the + * ofp_packet_in should be sent. The ofp_packet_in or nx_packet_in is + * sent only to controllers that have the specified controller connection + * ID. See "struct nx_controller_id" for more information. */ +struct nx_action_controller { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* Length is 16. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_CONTROLLER. */ + ovs_be16 max_len; /* Maximum length to send to controller. */ + ovs_be16 controller_id; /* Controller ID to send packet-in. */ + uint8_t reason; /* enum ofp_packet_in_reason (OFPR_*). */ + uint8_t zero; /* Must be zero. */ +}; +OFP_ASSERT(sizeof(struct nx_action_controller) == 16); -static void -controller_from_openflow(const struct nx_action_controller *nac, - struct ofpbuf *out) +/* Properties for NXAST_CONTROLLER2. + * + * For more information on the effect of NXAC2PT_PAUSE, see the large comment + * on NXT_PACKET_IN2 in nicira-ext.h */ +enum nx_action_controller2_prop_type { + NXAC2PT_MAX_LEN, /* ovs_be16 max bytes to send (default all). */ + NXAC2PT_CONTROLLER_ID, /* ovs_be16 dest controller ID (default 0). */ + NXAC2PT_REASON, /* uint8_t reason (OFPR_*), default 0. */ + NXAC2PT_USERDATA, /* Data to copy into NXPINT_USERDATA. */ + NXAC2PT_PAUSE, /* Flag to pause pipeline to resume later. */ +}; + +/* Action structure for NXAST_CONTROLLER2. + * + * This replacement for NXAST_CONTROLLER makes it extensible via properties. */ +struct nx_action_controller2 { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* Length is 16 or more. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_CONTROLLER2. */ + uint8_t zeros[6]; /* Must be zero. */ + /* Followed by NXAC2PT_* properties. */ +}; +OFP_ASSERT(sizeof(struct nx_action_controller2) == 16); + +static enum ofperr +decode_NXAST_RAW_CONTROLLER(const struct nx_action_controller *nac, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { struct ofpact_controller *oc; oc = ofpact_put_CONTROLLER(out); + oc->ofpact.raw = NXAST_RAW_CONTROLLER; oc->max_len = ntohs(nac->max_len); oc->controller_id = ntohs(nac->controller_id); oc->reason = nac->reason; + ofpact_finish(out, &oc->ofpact); + + return 0; } static enum ofperr -metadata_from_nxast(const struct nx_action_write_metadata *nawm, - struct ofpbuf *out) +decode_NXAST_RAW_CONTROLLER2(const struct nx_action_controller2 *nac2, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - struct ofpact_metadata *om; - - if (!is_all_zeros(nawm->zeros, sizeof nawm->zeros)) { + if (!is_all_zeros(nac2->zeros, sizeof nac2->zeros)) { return OFPERR_NXBRC_MUST_BE_ZERO; } - om = ofpact_put_WRITE_METADATA(out); - om->metadata = nawm->metadata; - om->mask = nawm->mask; + size_t start_ofs = out->size; + struct ofpact_controller *oc = ofpact_put_CONTROLLER(out); + oc->ofpact.raw = NXAST_RAW_CONTROLLER2; + oc->max_len = UINT16_MAX; + oc->reason = OFPR_ACTION; - return 0; -} + struct ofpbuf properties; + ofpbuf_use_const(&properties, nac2, ntohs(nac2->len)); + ofpbuf_pull(&properties, sizeof *nac2); -static void -note_from_openflow(const struct nx_action_note *nan, struct ofpbuf *out) -{ - struct ofpact_note *note; - unsigned int length; + while (properties.size > 0) { + struct ofpbuf payload; + uint64_t type; - length = ntohs(nan->len) - offsetof(struct nx_action_note, note); - note = ofpact_put(out, OFPACT_NOTE, - offsetof(struct ofpact_note, data) + length); - note->length = length; - memcpy(note->data, nan->note, length); + enum ofperr error = ofpprop_pull(&properties, &payload, &type); + if (error) { + return error; + } + + switch (type) { + case NXAC2PT_MAX_LEN: + error = ofpprop_parse_u16(&payload, &oc->max_len); + break; + + case NXAC2PT_CONTROLLER_ID: + error = ofpprop_parse_u16(&payload, &oc->controller_id); + break; + + case NXAC2PT_REASON: { + uint8_t u8; + error = ofpprop_parse_u8(&payload, &u8); + oc->reason = u8; + break; + } + + case NXAC2PT_USERDATA: + out->size = start_ofs + OFPACT_CONTROLLER_SIZE; + ofpbuf_put(out, payload.msg, ofpbuf_msgsize(&payload)); + oc = ofpbuf_at_assert(out, start_ofs, sizeof *oc); + oc->userdata_len = ofpbuf_msgsize(&payload); + break; + + case NXAC2PT_PAUSE: + oc->pause = true; + break; + + default: + error = OFPPROP_UNKNOWN(false, "NXAST_RAW_CONTROLLER2", type); + break; + } + if (error) { + return error; + } + } + + ofpact_finish(out, &oc->ofpact); + + return 0; } -static enum ofperr -dec_ttl_from_openflow(struct ofpbuf *out, enum ofputil_action_code compat) +static void +encode_CONTROLLER(const struct ofpact_controller *controller, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - uint16_t id = 0; - struct ofpact_cnt_ids *ids; - enum ofperr error = 0; + if (controller->userdata_len + || controller->pause + || controller->ofpact.raw == NXAST_RAW_CONTROLLER2) { + size_t start_ofs = out->size; + put_NXAST_CONTROLLER2(out); + if (controller->max_len != UINT16_MAX) { + ofpprop_put_u16(out, NXAC2PT_MAX_LEN, controller->max_len); + } + if (controller->controller_id != 0) { + ofpprop_put_u16(out, NXAC2PT_CONTROLLER_ID, + controller->controller_id); + } + if (controller->reason != OFPR_ACTION) { + ofpprop_put_u8(out, NXAC2PT_REASON, controller->reason); + } + if (controller->userdata_len != 0) { + ofpprop_put(out, NXAC2PT_USERDATA, controller->userdata, + controller->userdata_len); + } + if (controller->pause) { + ofpprop_put_flag(out, NXAC2PT_PAUSE); + } + pad_ofpat(out, start_ofs); + } else { + struct nx_action_controller *nac; - ids = ofpact_put_DEC_TTL(out); - ids->ofpact.compat = compat; - ids->n_controllers = 1; - ofpbuf_put(out, &id, sizeof id); - ids = out->l2; - ofpact_update_len(out, &ids->ofpact); - return error; + nac = put_NXAST_CONTROLLER(out); + nac->max_len = htons(controller->max_len); + nac->controller_id = htons(controller->controller_id); + nac->reason = controller->reason; + } } -static enum ofperr -dec_ttl_cnt_ids_from_openflow(const struct nx_action_cnt_ids *nac_ids, - struct ofpbuf *out) +static char * OVS_WARN_UNUSED_RESULT +parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) { - struct ofpact_cnt_ids *ids; - size_t ids_size; - int i; - - ids = ofpact_put_DEC_TTL(out); - ids->ofpact.compat = OFPUTIL_NXAST_DEC_TTL_CNT_IDS; - ids->n_controllers = ntohs(nac_ids->n_controllers); - ids_size = ntohs(nac_ids->len) - sizeof *nac_ids; + enum ofp_packet_in_reason reason = OFPR_ACTION; + uint16_t controller_id = 0; + uint16_t max_len = UINT16_MAX; + const char *userdata = NULL; + bool pause = false; + + if (!arg[0]) { + /* Use defaults. */ + } else if (strspn(arg, "0123456789") == strlen(arg)) { + char *error = str_to_u16(arg, "max_len", &max_len); + if (error) { + return error; + } + } else { + char *name, *value; - if (!is_all_zeros(nac_ids->zeros, sizeof nac_ids->zeros)) { - return OFPERR_NXBRC_MUST_BE_ZERO; + while (ofputil_parse_key_value(&arg, &name, &value)) { + if (!strcmp(name, "reason")) { + if (!ofputil_packet_in_reason_from_string(value, &reason)) { + return xasprintf("unknown reason \"%s\"", value); + } + } else if (!strcmp(name, "max_len")) { + char *error = str_to_u16(value, "max_len", &max_len); + if (error) { + return error; + } + } else if (!strcmp(name, "id")) { + char *error = str_to_u16(value, "id", &controller_id); + if (error) { + return error; + } + } else if (!strcmp(name, "userdata")) { + userdata = value; + } else if (!strcmp(name, "pause")) { + pause = true; + } else { + return xasprintf("unknown key \"%s\" parsing controller " + "action", name); + } + } } - if (ids_size < ids->n_controllers * sizeof(ovs_be16)) { - VLOG_WARN_RL(&rl, "Nicira action dec_ttl_cnt_ids only has %zu bytes " - "allocated for controller ids. %zu bytes are required for " - "%"PRIu16" controllers.", ids_size, - ids->n_controllers * sizeof(ovs_be16), ids->n_controllers); - return OFPERR_OFPBAC_BAD_LEN; - } + if (reason == OFPR_ACTION && controller_id == 0 && !userdata && !pause) { + struct ofpact_output *output; - 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->l2; + output = ofpact_put_OUTPUT(ofpacts); + output->port = OFPP_CONTROLLER; + output->max_len = max_len; + } else { + struct ofpact_controller *controller; + + controller = ofpact_put_CONTROLLER(ofpacts); + controller->max_len = max_len; + controller->reason = reason; + controller->controller_id = controller_id; + controller->pause = pause; + + if (userdata) { + size_t start_ofs = ofpacts->size; + const char *end = ofpbuf_put_hex(ofpacts, userdata, NULL); + if (*end) { + return xstrdup("bad hex digit in `controller' " + "action `userdata'"); + } + size_t userdata_len = ofpacts->size - start_ofs; + controller = ofpacts->header; + controller->userdata_len = userdata_len; + } + ofpact_finish(ofpacts, &controller->ofpact); } - ofpact_update_len(out, &ids->ofpact); + return NULL; +} - return 0; +static void +format_hex_arg(struct ds *s, const uint8_t *data, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (i) { + ds_put_char(s, '.'); + } + ds_put_format(s, "%02"PRIx8, data[i]); + } } -static enum ofperr -sample_from_openflow(const struct nx_action_sample *nas, - struct ofpbuf *out) +static void +format_CONTROLLER(const struct ofpact_controller *a, struct ds *s) { - struct ofpact_sample *sample; + if (a->reason == OFPR_ACTION && !a->controller_id && !a->userdata_len + && !a->pause) { + ds_put_format(s, "CONTROLLER:%"PRIu16, a->max_len); + } else { + enum ofp_packet_in_reason reason = a->reason; - 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); + ds_put_cstr(s, "controller("); + if (reason != OFPR_ACTION) { + char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE]; - if (sample->probability == 0) { - return OFPERR_OFPBAC_BAD_ARGUMENT; + ds_put_format(s, "reason=%s,", + ofputil_packet_in_reason_to_string( + reason, reasonbuf, sizeof reasonbuf)); + } + if (a->max_len != UINT16_MAX) { + ds_put_format(s, "max_len=%"PRIu16",", a->max_len); + } + if (a->controller_id != 0) { + ds_put_format(s, "id=%"PRIu16",", a->controller_id); + } + if (a->userdata_len) { + ds_put_cstr(s, "userdata="); + format_hex_arg(s, a->userdata, a->userdata_len); + ds_put_char(s, ','); + } + if (a->pause) { + ds_put_cstr(s, "pause,"); + } + ds_chomp(s, ','); + ds_put_char(s, ')'); } - - return 0; } + +/* Enqueue action. */ +struct ofp10_action_enqueue { + ovs_be16 type; /* OFPAT10_ENQUEUE. */ + ovs_be16 len; /* Len is 16. */ + ovs_be16 port; /* Port that queue belongs. Should + refer to a valid physical port + (i.e. < OFPP_MAX) or OFPP_IN_PORT. */ + uint8_t pad[6]; /* Pad for 64-bit alignment. */ + ovs_be32 queue_id; /* Where to enqueue the packets. */ +}; +OFP_ASSERT(sizeof(struct ofp10_action_enqueue) == 16); static enum ofperr -push_mpls_from_openflow(ovs_be16 ethertype, enum ofpact_mpls_position position, - struct ofpbuf *out) +decode_OFPAT_RAW10_ENQUEUE(const struct ofp10_action_enqueue *oae, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - struct ofpact_push_mpls *oam; + struct ofpact_enqueue *enqueue; - if (!eth_type_mpls(ethertype)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; + enqueue = ofpact_put_ENQUEUE(out); + enqueue->port = u16_to_ofp(ntohs(oae->port)); + enqueue->queue = ntohl(oae->queue_id); + if (ofp_to_u16(enqueue->port) >= ofp_to_u16(OFPP_MAX) + && enqueue->port != OFPP_IN_PORT + && enqueue->port != OFPP_LOCAL) { + return OFPERR_OFPBAC_BAD_OUT_PORT; } - oam = ofpact_put_PUSH_MPLS(out); - oam->ethertype = ethertype; - oam->position = position; - return 0; } -static enum ofperr -decode_nxast_action(const union ofp_action *a, enum ofputil_action_code *code) +static void +encode_ENQUEUE(const struct ofpact_enqueue *enqueue, + enum ofp_version ofp_version, struct ofpbuf *out) { - const struct nx_action_header *nah = &a->nxa_header; - uint16_t len = ntohs(a->header.len); + if (ofp_version == OFP10_VERSION) { + struct ofp10_action_enqueue *oae; - if (len < sizeof(struct nx_action_header)) { - return OFPERR_OFPBAC_BAD_LEN; - } else if (a->vendor.vendor != CONSTANT_HTONL(NX_VENDOR_ID)) { - return OFPERR_OFPBAC_BAD_VENDOR; + oae = put_OFPAT10_ENQUEUE(out); + oae->port = htons(ofp_to_u16(enqueue->port)); + oae->queue_id = htonl(enqueue->queue); + } else { + /* XXX */ } +} - switch (nah->subtype) { -#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) \ - case CONSTANT_HTONS(ENUM): \ - if (EXTENSIBLE \ - ? len >= sizeof(struct STRUCT) \ - : len == sizeof(struct STRUCT)) { \ - *code = OFPUTIL_##ENUM; \ - return 0; \ - } else { \ - return OFPERR_OFPBAC_BAD_LEN; \ - } \ - NOT_REACHED(); -#include "ofp-util.def" +static char * OVS_WARN_UNUSED_RESULT +parse_ENQUEUE(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + char *sp = NULL; + char *port = strtok_r(arg, ":q,", &sp); + char *queue = strtok_r(NULL, "", &sp); + struct ofpact_enqueue *enqueue; - case CONSTANT_HTONS(NXAST_SNAT__OBSOLETE): - case CONSTANT_HTONS(NXAST_DROP_SPOOFED_ARP__OBSOLETE): - default: - return OFPERR_OFPBAC_BAD_TYPE; + if (port == NULL || queue == NULL) { + return xstrdup("\"enqueue\" syntax is \"enqueue:PORT:QUEUE\" or " + "\"enqueue(PORT,QUEUE)\""); } + + enqueue = ofpact_put_ENQUEUE(ofpacts); + if (!ofputil_port_from_string(port, &enqueue->port)) { + return xasprintf("%s: enqueue to unknown port", port); + } + return str_to_u32(queue, &enqueue->queue); } -/* Parses 'a' to determine its type. On success stores the correct type into - * '*code' and returns 0. On failure returns an OFPERR_* error code and - * '*code' is indeterminate. +static void +format_ENQUEUE(const struct ofpact_enqueue *a, struct ds *s) +{ + ds_put_format(s, "enqueue:"); + ofputil_format_port(a->port, s); + ds_put_format(s, ":%"PRIu32, a->queue); +} + +/* Action structure for NXAST_OUTPUT_REG. + * + * Outputs to the OpenFlow port number written to src[ofs:ofs+nbits]. + * + * The format and semantics of 'src' and 'ofs_nbits' are similar to those for + * the NXAST_REG_LOAD action. * - * The caller must have already verified that 'a''s length is potentially - * correct (that is, a->header.len is nonzero and a multiple of - * OFP_ACTION_ALIGN and no longer than the amount of space allocated to 'a'). + * The acceptable nxm_header values for 'src' are the same as the acceptable + * nxm_header values for the 'src' field of NXAST_REG_MOVE. * - * This function verifies that 'a''s length is correct for the type of action - * that it represents. */ + * The 'max_len' field indicates the number of bytes to send when the chosen + * port is OFPP_CONTROLLER. Its semantics are equivalent to the 'max_len' + * field of OFPAT_OUTPUT. + * + * The 'zero' field is required to be zeroed for forward compatibility. */ +struct nx_action_output_reg { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* 24. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_OUTPUT_REG. */ + + ovs_be16 ofs_nbits; /* (ofs << 6) | (n_bits - 1). */ + ovs_be32 src; /* Source. */ + + ovs_be16 max_len; /* Max length to send to controller. */ + + uint8_t zero[6]; /* Reserved, must be zero. */ +}; +OFP_ASSERT(sizeof(struct nx_action_output_reg) == 24); + +/* Action structure for NXAST_OUTPUT_REG2. + * + * Like the NXAST_OUTPUT_REG but organized so that there is room for a 64-bit + * experimenter OXM as 'src'. + */ +struct nx_action_output_reg2 { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* 24. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_OUTPUT_REG2. */ + + ovs_be16 ofs_nbits; /* (ofs << 6) | (n_bits - 1). */ + ovs_be16 max_len; /* Max length to send to controller. */ + + /* Followed by: + * - 'src', as an OXM/NXM header (either 4 or 8 bytes). + * - Enough 0-bytes to pad the action out to 24 bytes. */ + uint8_t pad[10]; +}; +OFP_ASSERT(sizeof(struct nx_action_output_reg2) == 24); + static enum ofperr -decode_openflow10_action(const union ofp_action *a, - enum ofputil_action_code *code) +decode_NXAST_RAW_OUTPUT_REG(const struct nx_action_output_reg *naor, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - switch (a->type) { - case CONSTANT_HTONS(OFPAT10_VENDOR): - return decode_nxast_action(a, code); - -#define OFPAT10_ACTION(ENUM, STRUCT, NAME) \ - case CONSTANT_HTONS(ENUM): \ - if (a->header.len == htons(sizeof(struct STRUCT))) { \ - *code = OFPUTIL_##ENUM; \ - return 0; \ - } else { \ - return OFPERR_OFPBAC_BAD_LEN; \ - } \ - break; -#include "ofp-util.def" + struct ofpact_output_reg *output_reg; - default: - return OFPERR_OFPBAC_BAD_TYPE; + if (!is_all_zeros(naor->zero, sizeof naor->zero)) { + return OFPERR_OFPBAC_BAD_ARGUMENT; } + + output_reg = ofpact_put_OUTPUT_REG(out); + output_reg->ofpact.raw = NXAST_RAW_OUTPUT_REG; + output_reg->src.field = mf_from_nxm_header(ntohl(naor->src)); + output_reg->src.ofs = nxm_decode_ofs(naor->ofs_nbits); + output_reg->src.n_bits = nxm_decode_n_bits(naor->ofs_nbits); + output_reg->max_len = ntohs(naor->max_len); + + return mf_check_src(&output_reg->src, NULL); } static enum ofperr -ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code, - struct ofpbuf *out) +decode_NXAST_RAW_OUTPUT_REG2(const struct nx_action_output_reg2 *naor, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - struct ofpact_tunnel *tunnel; - enum ofperr error = 0; - - switch (code) { - case OFPUTIL_ACTION_INVALID: -#define OFPAT10_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM: -#define OFPAT11_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: -#include "ofp-util.def" - NOT_REACHED(); - - case OFPUTIL_NXAST_RESUBMIT: - resubmit_from_openflow(&a->resubmit, out); - break; + struct ofpact_output_reg *output_reg; + output_reg = ofpact_put_OUTPUT_REG(out); + output_reg->ofpact.raw = NXAST_RAW_OUTPUT_REG2; + output_reg->src.ofs = nxm_decode_ofs(naor->ofs_nbits); + output_reg->src.n_bits = nxm_decode_n_bits(naor->ofs_nbits); + output_reg->max_len = ntohs(naor->max_len); - case OFPUTIL_NXAST_SET_TUNNEL: - tunnel = ofpact_put_SET_TUNNEL(out); - tunnel->ofpact.compat = code; - tunnel->tun_id = ntohl(a->set_tunnel.tun_id); - break; + struct ofpbuf b = ofpbuf_const_initializer(naor, ntohs(naor->len)); + ofpbuf_pull(&b, OBJECT_OFFSETOF(naor, pad)); - case OFPUTIL_NXAST_WRITE_METADATA: - error = metadata_from_nxast(&a->write_metadata, out); - break; + enum ofperr error = nx_pull_header(&b, &output_reg->src.field, NULL); + if (error) { + return error; + } + if (!is_all_zeros(b.data, b.size)) { + return OFPERR_NXBRC_MUST_BE_ZERO; + } - case OFPUTIL_NXAST_SET_QUEUE: - ofpact_put_SET_QUEUE(out)->queue_id = ntohl(a->set_queue.queue_id); - break; + return mf_check_src(&output_reg->src, NULL); +} - case OFPUTIL_NXAST_POP_QUEUE: - ofpact_put_POP_QUEUE(out); - break; +static void +encode_OUTPUT_REG(const struct ofpact_output_reg *output_reg, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + /* If 'output_reg' came in as an NXAST_RAW_OUTPUT_REG2 action, or if it + * cannot be encoded in the older form, encode it as + * NXAST_RAW_OUTPUT_REG2. */ + if (output_reg->ofpact.raw == NXAST_RAW_OUTPUT_REG2 + || !mf_nxm_header(output_reg->src.field->id)) { + struct nx_action_output_reg2 *naor = put_NXAST_OUTPUT_REG2(out); + size_t size = out->size; + + naor->ofs_nbits = nxm_encode_ofs_nbits(output_reg->src.ofs, + output_reg->src.n_bits); + naor->max_len = htons(output_reg->max_len); + + out->size = size - sizeof naor->pad; + nx_put_header(out, output_reg->src.field->id, 0, false); + out->size = size; + } else { + struct nx_action_output_reg *naor = put_NXAST_OUTPUT_REG(out); - case OFPUTIL_NXAST_REG_MOVE: - error = nxm_reg_move_from_openflow(&a->reg_move, out); - break; + naor->ofs_nbits = nxm_encode_ofs_nbits(output_reg->src.ofs, + output_reg->src.n_bits); + naor->src = htonl(mf_nxm_header(output_reg->src.field->id)); + naor->max_len = htons(output_reg->max_len); + } +} - case OFPUTIL_NXAST_REG_LOAD: - error = nxm_reg_load_from_openflow(&a->reg_load, out); - break; +static char * OVS_WARN_UNUSED_RESULT +parse_OUTPUT_REG(const char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return parse_OUTPUT(arg, ofpacts, usable_protocols); +} - case OFPUTIL_NXAST_STACK_PUSH: - error = nxm_stack_push_from_openflow(&a->stack, out); - break; +static void +format_OUTPUT_REG(const struct ofpact_output_reg *a, struct ds *s) +{ + ds_put_cstr(s, "output:"); + mf_format_subfield(&a->src, s); +} + +/* Action structure for NXAST_BUNDLE and NXAST_BUNDLE_LOAD. + * + * The bundle actions choose a slave from a supplied list of options. + * NXAST_BUNDLE outputs to its selection. NXAST_BUNDLE_LOAD writes its + * selection to a register. + * + * The list of possible slaves follows the nx_action_bundle structure. The size + * of each slave is governed by its type as indicated by the 'slave_type' + * parameter. The list of slaves should be padded at its end with zeros to make + * the total length of the action a multiple of 8. + * + * Switches infer from the 'slave_type' parameter the size of each slave. All + * implementations must support the NXM_OF_IN_PORT 'slave_type' which indicates + * that the slaves are OpenFlow port numbers with NXM_LENGTH(NXM_OF_IN_PORT) == + * 2 byte width. Switches should reject actions which indicate unknown or + * unsupported slave types. + * + * Switches use a strategy dictated by the 'algorithm' parameter to choose a + * slave. If the switch does not support the specified 'algorithm' parameter, + * it should reject the action. + * + * Several algorithms take into account liveness when selecting slaves. The + * liveness of a slave is implementation defined (with one exception), but will + * generally take into account things like its carrier status and the results + * of any link monitoring protocols which happen to be running on it. In order + * to give controllers a place-holder value, the OFPP_NONE port is always + * considered live. + * + * Some slave selection strategies require the use of a hash function, in which + * case the 'fields' and 'basis' parameters should be populated. The 'fields' + * parameter (one of NX_HASH_FIELDS_*) designates which parts of the flow to + * hash. Refer to the definition of "enum nx_hash_fields" for details. The + * 'basis' parameter is used as a universal hash parameter. Different values + * of 'basis' yield different hash results. + * + * The 'zero' parameter at the end of the action structure is reserved for + * future use. Switches are required to reject actions which have nonzero + * bytes in the 'zero' field. + * + * NXAST_BUNDLE actions should have 'ofs_nbits' and 'dst' zeroed. Switches + * should reject actions which have nonzero bytes in either of these fields. + * + * NXAST_BUNDLE_LOAD stores the OpenFlow port number of the selected slave 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. */ +struct nx_action_bundle { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* Length including slaves. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_BUNDLE or NXAST_BUNDLE_LOAD. */ - case OFPUTIL_NXAST_STACK_POP: - error = nxm_stack_pop_from_openflow(&a->stack, out); - break; + /* Slave choice algorithm to apply to hash value. */ + ovs_be16 algorithm; /* One of NX_BD_ALG_*. */ - case OFPUTIL_NXAST_NOTE: - note_from_openflow(&a->note, out); - break; + /* What fields to hash and how. */ + ovs_be16 fields; /* One of NX_HASH_FIELDS_*. */ + ovs_be16 basis; /* Universal hash parameter. */ - case OFPUTIL_NXAST_SET_TUNNEL64: - tunnel = ofpact_put_SET_TUNNEL(out); - tunnel->ofpact.compat = code; - tunnel->tun_id = ntohll(a->set_tunnel64.tun_id); - break; + ovs_be32 slave_type; /* NXM_OF_IN_PORT. */ + ovs_be16 n_slaves; /* Number of slaves. */ - case OFPUTIL_NXAST_MULTIPATH: - error = multipath_from_openflow(&a->multipath, - ofpact_put_MULTIPATH(out)); - break; + ovs_be16 ofs_nbits; /* (ofs << 6) | (n_bits - 1). */ + ovs_be32 dst; /* Destination. */ - case OFPUTIL_NXAST_BUNDLE: - case OFPUTIL_NXAST_BUNDLE_LOAD: - error = bundle_from_openflow(&a->bundle, out); - break; + uint8_t zero[4]; /* Reserved. Must be zero. */ +}; +OFP_ASSERT(sizeof(struct nx_action_bundle) == 32); - case OFPUTIL_NXAST_OUTPUT_REG: - error = output_reg_from_openflow(&a->output_reg, out); - break; +static enum ofperr +decode_bundle(bool load, const struct nx_action_bundle *nab, + struct ofpbuf *ofpacts) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + struct ofpact_bundle *bundle; + uint32_t slave_type; + size_t slaves_size, i; + enum ofperr error; - case OFPUTIL_NXAST_RESUBMIT_TABLE: - error = resubmit_table_from_openflow(&a->resubmit, out); - break; + bundle = ofpact_put_BUNDLE(ofpacts); + + bundle->n_slaves = ntohs(nab->n_slaves); + bundle->basis = ntohs(nab->basis); + bundle->fields = ntohs(nab->fields); + bundle->algorithm = ntohs(nab->algorithm); + slave_type = ntohl(nab->slave_type); + slaves_size = ntohs(nab->len) - sizeof *nab; + + error = OFPERR_OFPBAC_BAD_ARGUMENT; + if (!flow_hash_fields_valid(bundle->fields)) { + VLOG_WARN_RL(&rl, "unsupported fields %d", (int) bundle->fields); + } else if (bundle->n_slaves > BUNDLE_MAX_SLAVES) { + VLOG_WARN_RL(&rl, "too many slaves"); + } else if (bundle->algorithm != NX_BD_ALG_HRW + && bundle->algorithm != NX_BD_ALG_ACTIVE_BACKUP) { + VLOG_WARN_RL(&rl, "unsupported algorithm %d", (int) bundle->algorithm); + } else if (slave_type != mf_nxm_header(MFF_IN_PORT)) { + VLOG_WARN_RL(&rl, "unsupported slave type %"PRIu16, slave_type); + } else { + error = 0; + } - case OFPUTIL_NXAST_LEARN: - error = learn_from_openflow(&a->learn, out); - break; + if (!is_all_zeros(nab->zero, sizeof nab->zero)) { + VLOG_WARN_RL(&rl, "reserved field is nonzero"); + error = OFPERR_OFPBAC_BAD_ARGUMENT; + } - case OFPUTIL_NXAST_EXIT: - ofpact_put_EXIT(out); - break; + if (load) { + bundle->dst.field = mf_from_nxm_header(ntohl(nab->dst)); + bundle->dst.ofs = nxm_decode_ofs(nab->ofs_nbits); + bundle->dst.n_bits = nxm_decode_n_bits(nab->ofs_nbits); - case OFPUTIL_NXAST_DEC_TTL: - error = dec_ttl_from_openflow(out, code); - break; + if (bundle->dst.n_bits < 16) { + VLOG_WARN_RL(&rl, "bundle_load action requires at least 16 bit " + "destination."); + error = OFPERR_OFPBAC_BAD_ARGUMENT; + } + } else { + if (nab->ofs_nbits || nab->dst) { + VLOG_WARN_RL(&rl, "bundle action has nonzero reserved fields"); + error = OFPERR_OFPBAC_BAD_ARGUMENT; + } + } - case OFPUTIL_NXAST_DEC_TTL_CNT_IDS: - error = dec_ttl_cnt_ids_from_openflow(&a->cnt_ids, out); - break; + if (slaves_size < bundle->n_slaves * sizeof(ovs_be16)) { + VLOG_WARN_RL(&rl, "Nicira action %s only has %"PRIuSIZE" bytes " + "allocated for slaves. %"PRIuSIZE" bytes are required " + "for %"PRIu16" slaves.", + load ? "bundle_load" : "bundle", slaves_size, + bundle->n_slaves * sizeof(ovs_be16), bundle->n_slaves); + error = OFPERR_OFPBAC_BAD_LEN; + } - case OFPUTIL_NXAST_FIN_TIMEOUT: - fin_timeout_from_openflow(&a->fin_timeout, out); - break; + for (i = 0; i < bundle->n_slaves; i++) { + uint16_t ofp_port = ntohs(((ovs_be16 *)(nab + 1))[i]); + ofpbuf_put(ofpacts, &ofp_port, sizeof ofp_port); + bundle = ofpacts->header; + } - case OFPUTIL_NXAST_CONTROLLER: - controller_from_openflow(&a->controller, out); - break; + ofpact_finish(ofpacts, &bundle->ofpact); - case OFPUTIL_NXAST_PUSH_MPLS: - error = push_mpls_from_openflow(a->push_mpls.ethertype, - OFPACT_MPLS_AFTER_VLAN, out); - break; + if (!error) { + error = bundle_check(bundle, OFPP_MAX, NULL); + } + return error; +} - case OFPUTIL_NXAST_SET_MPLS_TTL: - ofpact_put_SET_MPLS_TTL(out)->ttl = a->mpls_ttl.ttl; - break; +static enum ofperr +decode_NXAST_RAW_BUNDLE(const struct nx_action_bundle *nab, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + return decode_bundle(false, nab, out); +} - case OFPUTIL_NXAST_DEC_MPLS_TTL: - ofpact_put_DEC_MPLS_TTL(out); - break; +static enum ofperr +decode_NXAST_RAW_BUNDLE_LOAD(const struct nx_action_bundle *nab, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + return decode_bundle(true, nab, out); +} - case OFPUTIL_NXAST_POP_MPLS: - if (eth_type_mpls(a->pop_mpls.ethertype)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_POP_MPLS(out)->ethertype = a->pop_mpls.ethertype; - break; +static void +encode_BUNDLE(const struct ofpact_bundle *bundle, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + int slaves_len = ROUND_UP(2 * bundle->n_slaves, OFP_ACTION_ALIGN); + struct nx_action_bundle *nab; + ovs_be16 *slaves; + size_t i; - case OFPUTIL_NXAST_SAMPLE: - error = sample_from_openflow(&a->sample, out); - break; + nab = (bundle->dst.field + ? put_NXAST_BUNDLE_LOAD(out) + : put_NXAST_BUNDLE(out)); + nab->len = htons(ntohs(nab->len) + slaves_len); + nab->algorithm = htons(bundle->algorithm); + nab->fields = htons(bundle->fields); + nab->basis = htons(bundle->basis); + nab->slave_type = htonl(mf_nxm_header(MFF_IN_PORT)); + nab->n_slaves = htons(bundle->n_slaves); + if (bundle->dst.field) { + nab->ofs_nbits = nxm_encode_ofs_nbits(bundle->dst.ofs, + bundle->dst.n_bits); + nab->dst = htonl(mf_nxm_header(bundle->dst.field->id)); } - return error; + slaves = ofpbuf_put_zeros(out, slaves_len); + for (i = 0; i < bundle->n_slaves; i++) { + slaves[i] = htons(ofp_to_u16(bundle->slaves[i])); + } } -static enum ofperr -ofpact_from_openflow10(const union ofp_action *a, - enum ofp_version version OVS_UNUSED, - struct ofpbuf *out) +static char * OVS_WARN_UNUSED_RESULT +parse_BUNDLE(const char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) { - enum ofputil_action_code code; - enum ofperr error; - struct ofpact_vlan_vid *vlan_vid; - struct ofpact_vlan_pcp *vlan_pcp; + return bundle_parse(arg, ofpacts); +} - error = decode_openflow10_action(a, &code); - if (error) { - return error; - } +static char * OVS_WARN_UNUSED_RESULT +parse_bundle_load(const char *arg, struct ofpbuf *ofpacts) +{ + return bundle_parse_load(arg, ofpacts); +} - switch (code) { - case OFPUTIL_ACTION_INVALID: -#define OFPAT11_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: -#include "ofp-util.def" - NOT_REACHED(); +static void +format_BUNDLE(const struct ofpact_bundle *a, struct ds *s) +{ + bundle_format(a, s); +} + +/* Set VLAN actions. */ - case OFPUTIL_OFPAT10_OUTPUT: - return output_from_openflow10(&a->output10, out); +static enum ofperr +decode_set_vlan_vid(uint16_t vid, bool push_vlan_if_needed, struct ofpbuf *out) +{ + if (vid & ~0xfff) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } else { + struct ofpact_vlan_vid *vlan_vid = ofpact_put_SET_VLAN_VID(out); + vlan_vid->vlan_vid = vid; + vlan_vid->push_vlan_if_needed = push_vlan_if_needed; + return 0; + } +} - case OFPUTIL_OFPAT10_SET_VLAN_VID: - if (a->vlan_vid.vlan_vid & ~htons(0xfff)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - vlan_vid = ofpact_put_SET_VLAN_VID(out); - vlan_vid->vlan_vid = ntohs(a->vlan_vid.vlan_vid); - vlan_vid->push_vlan_if_needed = true; - vlan_vid->ofpact.compat = code; - break; +static enum ofperr +decode_OFPAT_RAW10_SET_VLAN_VID(uint16_t vid, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + return decode_set_vlan_vid(vid, true, out); +} - case OFPUTIL_OFPAT10_SET_VLAN_PCP: - if (a->vlan_pcp.vlan_pcp & ~7) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - vlan_pcp = ofpact_put_SET_VLAN_PCP(out); - vlan_pcp->vlan_pcp = a->vlan_pcp.vlan_pcp; - vlan_pcp->push_vlan_if_needed = true; - vlan_pcp->ofpact.compat = code; - break; +static enum ofperr +decode_OFPAT_RAW11_SET_VLAN_VID(uint16_t vid, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + return decode_set_vlan_vid(vid, false, out); +} - case OFPUTIL_OFPAT10_STRIP_VLAN: - ofpact_put_STRIP_VLAN(out)->ofpact.compat = code; - break; +static void +encode_SET_VLAN_VID(const struct ofpact_vlan_vid *vlan_vid, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + uint16_t vid = vlan_vid->vlan_vid; + + /* Push a VLAN tag, if none is present and this form of the action calls + * for such a feature. */ + if (ofp_version > OFP10_VERSION + && vlan_vid->push_vlan_if_needed + && !vlan_vid->flow_has_vlan) { + put_OFPAT11_PUSH_VLAN(out, htons(ETH_TYPE_VLAN_8021Q)); + } - case OFPUTIL_OFPAT10_SET_DL_SRC: - memcpy(ofpact_put_SET_ETH_SRC(out)->mac, a->dl_addr.dl_addr, - ETH_ADDR_LEN); - break; + if (ofp_version == OFP10_VERSION) { + put_OFPAT10_SET_VLAN_VID(out, vid); + } else if (ofp_version == OFP11_VERSION) { + put_OFPAT11_SET_VLAN_VID(out, vid); + } else { + ofpact_put_set_field(out, ofp_version, + MFF_VLAN_VID, vid | OFPVID12_PRESENT); + } +} - case OFPUTIL_OFPAT10_SET_DL_DST: - memcpy(ofpact_put_SET_ETH_DST(out)->mac, a->dl_addr.dl_addr, - ETH_ADDR_LEN); - break; +static char * OVS_WARN_UNUSED_RESULT +parse_set_vlan_vid(char *arg, struct ofpbuf *ofpacts, bool push_vlan_if_needed) +{ + struct ofpact_vlan_vid *vlan_vid; + uint16_t vid; + char *error; - case OFPUTIL_OFPAT10_SET_NW_SRC: - ofpact_put_SET_IPV4_SRC(out)->ipv4 = a->nw_addr.nw_addr; - break; + error = str_to_u16(arg, "VLAN VID", &vid); + if (error) { + return error; + } - case OFPUTIL_OFPAT10_SET_NW_DST: - ofpact_put_SET_IPV4_DST(out)->ipv4 = a->nw_addr.nw_addr; - break; + if (vid & ~VLAN_VID_MASK) { + return xasprintf("%s: not a valid VLAN VID", arg); + } + vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts); + vlan_vid->vlan_vid = vid; + vlan_vid->push_vlan_if_needed = push_vlan_if_needed; + return NULL; +} - case OFPUTIL_OFPAT10_SET_NW_TOS: - if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_SET_IP_DSCP(out)->dscp = a->nw_tos.nw_tos; - break; +static char * OVS_WARN_UNUSED_RESULT +parse_SET_VLAN_VID(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return parse_set_vlan_vid(arg, ofpacts, false); +} - case OFPUTIL_OFPAT10_SET_TP_SRC: - ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(a->tp_port.tp_port); - break; +static void +format_SET_VLAN_VID(const struct ofpact_vlan_vid *a, struct ds *s) +{ + ds_put_format(s, "%s:%"PRIu16, + a->push_vlan_if_needed ? "mod_vlan_vid" : "set_vlan_vid", + a->vlan_vid); +} + +/* Set PCP actions. */ - case OFPUTIL_OFPAT10_SET_TP_DST: - ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(a->tp_port.tp_port); +static enum ofperr +decode_set_vlan_pcp(uint8_t pcp, bool push_vlan_if_needed, struct ofpbuf *out) +{ + if (pcp & ~7) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } else { + struct ofpact_vlan_pcp *vlan_pcp = ofpact_put_SET_VLAN_PCP(out); + vlan_pcp->vlan_pcp = pcp; + vlan_pcp->push_vlan_if_needed = push_vlan_if_needed; + return 0; + } +} - break; +static enum ofperr +decode_OFPAT_RAW10_SET_VLAN_PCP(uint8_t pcp, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + return decode_set_vlan_pcp(pcp, true, out); +} - case OFPUTIL_OFPAT10_ENQUEUE: - error = enqueue_from_openflow10(&a->enqueue, out); - break; +static enum ofperr +decode_OFPAT_RAW11_SET_VLAN_PCP(uint8_t pcp, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + return decode_set_vlan_pcp(pcp, false, out); +} -#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: -#include "ofp-util.def" - return ofpact_from_nxast(a, code, out); +static void +encode_SET_VLAN_PCP(const struct ofpact_vlan_pcp *vlan_pcp, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + uint8_t pcp = vlan_pcp->vlan_pcp; + + /* Push a VLAN tag, if none is present and this form of the action calls + * for such a feature. */ + if (ofp_version > OFP10_VERSION + && vlan_pcp->push_vlan_if_needed + && !vlan_pcp->flow_has_vlan) { + put_OFPAT11_PUSH_VLAN(out, htons(ETH_TYPE_VLAN_8021Q)); } - return error; + if (ofp_version == OFP10_VERSION) { + put_OFPAT10_SET_VLAN_PCP(out, pcp); + } else if (ofp_version == OFP11_VERSION) { + put_OFPAT11_SET_VLAN_PCP(out, pcp); + } else { + ofpact_put_set_field(out, ofp_version, MFF_VLAN_PCP, pcp); + } } -static enum ofperr ofpact_from_openflow11(const union ofp_action *, - enum ofp_version, - struct ofpbuf *out); - -static inline union ofp_action * -action_next(const union ofp_action *a) +static char * OVS_WARN_UNUSED_RESULT +parse_set_vlan_pcp(char *arg, struct ofpbuf *ofpacts, bool push_vlan_if_needed) { - return ((union ofp_action *) (void *) - ((uint8_t *) a + ntohs(a->header.len))); + struct ofpact_vlan_pcp *vlan_pcp; + uint8_t pcp; + char *error; + + error = str_to_u8(arg, "VLAN PCP", &pcp); + if (error) { + return error; + } + + if (pcp & ~7) { + return xasprintf("%s: not a valid VLAN PCP", arg); + } + vlan_pcp = ofpact_put_SET_VLAN_PCP(ofpacts); + vlan_pcp->vlan_pcp = pcp; + vlan_pcp->push_vlan_if_needed = push_vlan_if_needed; + return NULL; } -static inline bool -action_is_valid(const union ofp_action *a, size_t max_actions) +static char * OVS_WARN_UNUSED_RESULT +parse_SET_VLAN_PCP(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) { - uint16_t len = ntohs(a->header.len); - return (!(len % OFP_ACTION_ALIGN) - && len >= OFP_ACTION_ALIGN - && len / OFP_ACTION_ALIGN <= max_actions); + return parse_set_vlan_pcp(arg, ofpacts, false); } -/* This macro is careful to check for actions with bad lengths. */ -#define ACTION_FOR_EACH(ITER, LEFT, ACTIONS, MAX_ACTIONS) \ - for ((ITER) = (ACTIONS), (LEFT) = (MAX_ACTIONS); \ - (LEFT) > 0 && action_is_valid(ITER, LEFT); \ - ((LEFT) -= ntohs((ITER)->header.len) / OFP_ACTION_ALIGN, \ - (ITER) = action_next(ITER))) - static void -log_bad_action(const union ofp_action *actions, size_t max_actions, - const union ofp_action *bad_action, enum ofperr error) +format_SET_VLAN_PCP(const struct ofpact_vlan_pcp *a, struct ds *s) { - if (!VLOG_DROP_WARN(&rl)) { - struct ds s; - - ds_init(&s); - ds_put_hex_dump(&s, actions, max_actions * OFP_ACTION_ALIGN, 0, false); - VLOG_WARN("bad action at offset %#tx (%s):\n%s", - (char *)bad_action - (char *)actions, - ofperr_get_name(error), ds_cstr(&s)); - ds_destroy(&s); - } + ds_put_format(s, "%s:%"PRIu8, + a->push_vlan_if_needed ? "mod_vlan_pcp" : "set_vlan_pcp", + a->vlan_pcp); } + +/* Strip VLAN actions. */ static enum ofperr -ofpacts_from_openflow(const union ofp_action *in, size_t n_in, - enum ofp_version version, struct ofpbuf *out) +decode_OFPAT_RAW10_STRIP_VLAN(struct ofpbuf *out) { - const union ofp_action *a; - size_t left; + ofpact_put_STRIP_VLAN(out)->ofpact.raw = OFPAT_RAW10_STRIP_VLAN; + return 0; +} - enum ofperr (*ofpact_from_openflow)(const union ofp_action *a, - enum ofp_version, - struct ofpbuf *out) = - (version == OFP10_VERSION) ? - ofpact_from_openflow10 : ofpact_from_openflow11; +static enum ofperr +decode_OFPAT_RAW11_POP_VLAN(struct ofpbuf *out) +{ + ofpact_put_STRIP_VLAN(out)->ofpact.raw = OFPAT_RAW11_POP_VLAN; + return 0; +} - ACTION_FOR_EACH (a, left, in, n_in) { - enum ofperr error = ofpact_from_openflow(a, version, out); - if (error) { - log_bad_action(in, n_in, a, error); - return error; - } - } - if (left) { - enum ofperr error = OFPERR_OFPBAC_BAD_LEN; - log_bad_action(in, n_in, a, error); - return error; +static void +encode_STRIP_VLAN(const struct ofpact_null *null OVS_UNUSED, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + if (ofp_version == OFP10_VERSION) { + put_OFPAT10_STRIP_VLAN(out); + } else { + put_OFPAT11_POP_VLAN(out); } +} - ofpact_pad(out); - return 0; +static char * OVS_WARN_UNUSED_RESULT +parse_STRIP_VLAN(char *arg OVS_UNUSED, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + ofpact_put_STRIP_VLAN(ofpacts)->ofpact.raw = OFPAT_RAW10_STRIP_VLAN; + return NULL; } -/* Attempts to convert 'actions_len' bytes of OpenFlow actions from the - * front of 'openflow' into ofpacts. On success, replaces any existing content - * in 'ofpacts' by the converted ofpacts; on failure, clears 'ofpacts'. - * Returns 0 if successful, otherwise an OpenFlow error. - * - * Actions are processed according to their OpenFlow version which - * is provided in the 'version' parameter. - * - * In most places in OpenFlow 1.1 and 1.2, actions appear encapsulated in - * instructions, so you should call ofpacts_pull_openflow_instructions() - * instead of this function. - * - * The parsed actions are valid generically, but they may not be valid in a - * specific context. For example, port numbers up to OFPP_MAX are valid - * generically, but specific datapaths may only support port numbers in a - * smaller range. Use ofpacts_check() to additional check whether actions are - * valid in a specific context. */ -enum ofperr -ofpacts_pull_openflow_actions(struct ofpbuf *openflow, - unsigned int actions_len, - enum ofp_version version, - struct ofpbuf *ofpacts) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - const union ofp_action *actions; - enum ofperr error; +static char * OVS_WARN_UNUSED_RESULT +parse_pop_vlan(struct ofpbuf *ofpacts) +{ + ofpact_put_STRIP_VLAN(ofpacts)->ofpact.raw = OFPAT_RAW11_POP_VLAN; + return NULL; +} - ofpbuf_clear(ofpacts); +static void +format_STRIP_VLAN(const struct ofpact_null *a, struct ds *s) +{ + ds_put_cstr(s, (a->ofpact.raw == OFPAT_RAW11_POP_VLAN + ? "pop_vlan" + : "strip_vlan")); +} + +/* Push VLAN action. */ - 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; +static enum ofperr +decode_OFPAT_RAW11_PUSH_VLAN(ovs_be16 eth_type, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + if (eth_type != htons(ETH_TYPE_VLAN_8021Q)) { + /* XXX 802.1AD(QinQ) isn't supported at the moment */ + return OFPERR_OFPBAC_BAD_ARGUMENT; } + ofpact_put_PUSH_VLAN(out); + return 0; +} - actions = ofpbuf_try_pull(openflow, actions_len); - if (actions == NULL) { - VLOG_WARN_RL(&rl, "OpenFlow message actions length %u exceeds " - "remaining message length (%zu)", - actions_len, openflow->size); - return OFPERR_OFPBRC_BAD_LEN; +static void +encode_PUSH_VLAN(const struct ofpact_null *null OVS_UNUSED, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + if (ofp_version == OFP10_VERSION) { + /* PUSH is a side effect of a SET_VLAN_VID/PCP, which should + * follow this action. */ + } else { + /* XXX ETH_TYPE_VLAN_8021AD case */ + put_OFPAT11_PUSH_VLAN(out, htons(ETH_TYPE_VLAN_8021Q)); } +} + +static char * OVS_WARN_UNUSED_RESULT +parse_PUSH_VLAN(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + uint16_t ethertype; + char *error; - error = ofpacts_from_openflow(actions, actions_len / OFP_ACTION_ALIGN, - version, ofpacts); + *usable_protocols &= OFPUTIL_P_OF11_UP; + error = str_to_u16(arg, "ethertype", ðertype); if (error) { - ofpbuf_clear(ofpacts); return error; } - error = ofpacts_verify(ofpacts->data, ofpacts->size); - if (error) { - ofpbuf_clear(ofpacts); + if (ethertype != ETH_TYPE_VLAN_8021Q) { + /* XXX ETH_TYPE_VLAN_8021AD case isn't supported */ + return xasprintf("%s: not a valid VLAN ethertype", arg); } - return error; + + ofpact_put_PUSH_VLAN(ofpacts); + return NULL; } +static void +format_PUSH_VLAN(const struct ofpact_null *a OVS_UNUSED, struct ds *s) +{ + /* XXX 802.1AD case*/ + ds_put_format(s, "push_vlan:%#"PRIx16, ETH_TYPE_VLAN_8021Q); +} -/* OpenFlow 1.1 actions. */ +/* Action structure for OFPAT10_SET_DL_SRC/DST and OFPAT11_SET_DL_SRC/DST. */ +struct ofp_action_dl_addr { + ovs_be16 type; /* Type. */ + ovs_be16 len; /* Length is 16. */ + struct eth_addr dl_addr; /* Ethernet address. */ + uint8_t pad[6]; +}; +OFP_ASSERT(sizeof(struct ofp_action_dl_addr) == 16); -/* Parses 'a' to determine its type. On success stores the correct type into - * '*code' and returns 0. On failure returns an OFPERR_* error code and - * '*code' is indeterminate. - * - * The caller must have already verified that 'a''s length is potentially - * correct (that is, a->header.len is nonzero and a multiple of - * OFP_ACTION_ALIGN and no longer than the amount of space allocated to 'a'). - * - * This function verifies that 'a''s length is correct for the type of action - * that it represents. */ static enum ofperr -decode_openflow11_action(const union ofp_action *a, - enum ofputil_action_code *code) +decode_OFPAT_RAW_SET_DL_SRC(const struct ofp_action_dl_addr *a, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - uint16_t len; - - switch (a->type) { - case CONSTANT_HTONS(OFPAT11_EXPERIMENTER): - return decode_nxast_action(a, code); - -#define OFPAT11_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) \ - case CONSTANT_HTONS(ENUM): \ - len = ntohs(a->header.len); \ - if (EXTENSIBLE \ - ? len >= sizeof(struct STRUCT) \ - : len == sizeof(struct STRUCT)) { \ - *code = OFPUTIL_##ENUM; \ - return 0; \ - } else { \ - return OFPERR_OFPBAC_BAD_LEN; \ - } \ - NOT_REACHED(); -#include "ofp-util.def" - - default: - return OFPERR_OFPBAC_BAD_TYPE; - } + ofpact_put_SET_ETH_SRC(out)->mac = a->dl_addr; + return 0; } static enum ofperr -set_field_from_openflow(const struct ofp12_action_set_field *oasf, - struct ofpbuf *ofpacts) +decode_OFPAT_RAW_SET_DL_DST(const struct ofp_action_dl_addr *a, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - uint16_t oasf_len = ntohs(oasf->len); - uint32_t oxm_header = ntohl(oasf->dst); - uint8_t oxm_length = NXM_LENGTH(oxm_header); - struct ofpact_set_field *sf; - const struct mf_field *mf; + ofpact_put_SET_ETH_DST(out)->mac = a->dl_addr; + return 0; +} - /* ofp12_action_set_field is padded to 64 bits by zero */ - if (oasf_len != ROUND_UP(sizeof *oasf + oxm_length, 8)) { - return OFPERR_OFPBAC_BAD_SET_LEN; - } - if (!is_all_zeros((const uint8_t *)oasf + sizeof *oasf + oxm_length, - oasf_len - oxm_length - sizeof *oasf)) { - return OFPERR_OFPBAC_BAD_SET_ARGUMENT; - } +static void +encode_SET_ETH_addr(const struct ofpact_mac *mac, enum ofp_version ofp_version, + enum ofp_raw_action_type raw, enum mf_field_id field, + struct ofpbuf *out) +{ + if (ofp_version < OFP12_VERSION) { + struct ofp_action_dl_addr *oada; - if (NXM_HASMASK(oxm_header)) { - return OFPERR_OFPBAC_BAD_SET_TYPE; - } - mf = mf_from_nxm_header(oxm_header); - if (!mf) { - return OFPERR_OFPBAC_BAD_SET_TYPE; - } - ovs_assert(mf->n_bytes == oxm_length); - /* oxm_length is now validated to be compatible with mf_value. */ - if (!mf->writable) { - VLOG_WARN_RL(&rl, "destination field %s is not writable", mf->name); - return OFPERR_OFPBAC_BAD_SET_ARGUMENT; + oada = ofpact_put_raw(out, ofp_version, raw, 0); + oada->dl_addr = mac->mac; + } else { + ofpact_put_set_field(out, ofp_version, field, + eth_addr_to_uint64(mac->mac)); } - sf = ofpact_put_SET_FIELD(ofpacts); - sf->field = mf; - memcpy(&sf->value, oasf + 1, mf->n_bytes); +} - /* The value must be valid for match and must have the OFPVID_PRESENT bit - * on for OXM_OF_VLAN_VID. */ - if (!mf_is_value_valid(mf, &sf->value) - || (mf->id == MFF_VLAN_VID - && !(sf->value.be16 & htons(OFPVID12_PRESENT)))) { - struct ds ds = DS_EMPTY_INITIALIZER; - mf_format(mf, &sf->value, NULL, &ds); - VLOG_WARN_RL(&rl, "Invalid value for set field %s: %s", - mf->name, ds_cstr(&ds)); - ds_destroy(&ds); +static void +encode_SET_ETH_SRC(const struct ofpact_mac *mac, enum ofp_version ofp_version, + struct ofpbuf *out) +{ + encode_SET_ETH_addr(mac, ofp_version, OFPAT_RAW_SET_DL_SRC, MFF_ETH_SRC, + out); - return OFPERR_OFPBAC_BAD_SET_ARGUMENT; - } - return 0; } static void -set_field_to_openflow12(const struct ofpact_set_field *sf, - struct ofpbuf *openflow) +encode_SET_ETH_DST(const struct ofpact_mac *mac, + enum ofp_version ofp_version, + struct ofpbuf *out) { - uint16_t padded_value_len = ROUND_UP(sf->field->n_bytes, 8); - struct ofp12_action_set_field *oasf; - char *value; + encode_SET_ETH_addr(mac, ofp_version, OFPAT_RAW_SET_DL_DST, MFF_ETH_DST, + out); + +} - oasf = ofputil_put_OFPAT12_SET_FIELD(openflow); - oasf->dst = htonl(sf->field->oxm_header); - oasf->len = htons(sizeof *oasf + padded_value_len); +static char * OVS_WARN_UNUSED_RESULT +parse_SET_ETH_SRC(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_mac(arg, &ofpact_put_SET_ETH_SRC(ofpacts)->mac); +} - value = ofpbuf_put_zeros(openflow, padded_value_len); - memcpy(value, &sf->value, sf->field->n_bytes); +static char * OVS_WARN_UNUSED_RESULT +parse_SET_ETH_DST(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_mac(arg, &ofpact_put_SET_ETH_DST(ofpacts)->mac); } -/* Convert 'sf' to one or two REG_LOADs. */ static void -set_field_to_nxast(const struct ofpact_set_field *sf, struct ofpbuf *openflow) +format_SET_ETH_SRC(const struct ofpact_mac *a, struct ds *s) { - const struct mf_field *mf = sf->field; - struct nx_action_reg_load *narl; - - if (mf->n_bits > 64) { - ovs_assert(mf->n_bytes == 16); /* IPv6 addr. */ - /* Split into 64bit chunks */ - /* Lower bits first. */ - narl = ofputil_put_NXAST_REG_LOAD(openflow); - narl->ofs_nbits = nxm_encode_ofs_nbits(0, 64); - narl->dst = htonl(mf->nxm_header); - memcpy(&narl->value, &sf->value.ipv6.s6_addr[8], sizeof narl->value); - /* Higher bits next. */ - narl = ofputil_put_NXAST_REG_LOAD(openflow); - narl->ofs_nbits = nxm_encode_ofs_nbits(64, mf->n_bits - 64); - narl->dst = htonl(mf->nxm_header); - memcpy(&narl->value, &sf->value.ipv6.s6_addr[0], sizeof narl->value); - } else { - narl = ofputil_put_NXAST_REG_LOAD(openflow); - narl->ofs_nbits = nxm_encode_ofs_nbits(0, mf->n_bits); - narl->dst = htonl(mf->nxm_header); - memset(&narl->value, 0, 8 - mf->n_bytes); - memcpy((char*)&narl->value + (8 - mf->n_bytes), - &sf->value, mf->n_bytes); - } + ds_put_format(s, "mod_dl_src:"ETH_ADDR_FMT, ETH_ADDR_ARGS(a->mac)); } -/* Convert 'sf' to standard OpenFlow 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_openflow11(const struct ofpact_set_field *sf, - struct ofpbuf *openflow) +format_SET_ETH_DST(const struct ofpact_mac *a, struct ds *s) { - switch ((int) sf->field->id) { - case MFF_VLAN_TCI: - /* 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 (sf->value.be16 & htons(VLAN_CFI)) { - /* Push a VLAN tag, if one was not seen at action validation - * time. */ - if (!sf->flow_has_vlan) { - ofputil_put_OFPAT11_PUSH_VLAN(openflow)->ethertype - = htons(ETH_TYPE_VLAN_8021Q); - } - ofputil_put_OFPAT11_SET_VLAN_VID(openflow)->vlan_vid - = sf->value.be16 & htons(VLAN_VID_MASK); - ofputil_put_OFPAT11_SET_VLAN_PCP(openflow)->vlan_pcp - = vlan_tci_to_pcp(sf->value.be16); - } 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. */ - ofputil_put_OFPAT11_POP_VLAN(openflow); - } - break; - - case MFF_VLAN_VID: - /* OXM VLAN_PCP to OpenFlow 1.1. - * Set field on OXM_OF_VLAN_VID onlyapplies to an existing vlan - * tag. Clear the OFPVID_PRESENT bit. - */ - ofputil_put_OFPAT11_SET_VLAN_VID(openflow)->vlan_vid - = sf->value.be16 & htons(VLAN_VID_MASK); - break; - - case MFF_VLAN_PCP: - /* OXM VLAN_PCP to OpenFlow 1.1. - * OXM_OF_VLAN_PCP only applies to existing vlan tag. */ - ofputil_put_OFPAT11_SET_VLAN_PCP(openflow)->vlan_pcp = sf->value.u8; - break; - - case MFF_ETH_SRC: - memcpy(ofputil_put_OFPAT11_SET_DL_SRC(openflow)->dl_addr, - sf->value.mac, ETH_ADDR_LEN); - break; - - case MFF_ETH_DST: - memcpy(ofputil_put_OFPAT11_SET_DL_DST(openflow)->dl_addr, - sf->value.mac, ETH_ADDR_LEN); - break; - - case MFF_IPV4_SRC: - ofputil_put_OFPAT11_SET_NW_SRC(openflow)->nw_addr = sf->value.be32; - break; - - case MFF_IPV4_DST: - ofputil_put_OFPAT11_SET_NW_DST(openflow)->nw_addr = sf->value.be32; - break; - - case MFF_IP_DSCP: - ofputil_put_OFPAT11_SET_NW_TOS(openflow)->nw_tos = sf->value.u8; - break; - - case MFF_IP_DSCP_SHIFTED: - ofputil_put_OFPAT11_SET_NW_TOS(openflow)->nw_tos = sf->value.u8 << 2; - break; - - case MFF_IP_ECN: - ofputil_put_OFPAT11_SET_NW_ECN(openflow)->nw_ecn = sf->value.u8; - break; - - case MFF_IP_TTL: - ofputil_put_OFPAT11_SET_NW_TTL(openflow)->nw_ttl = sf->value.u8; - break; + ds_put_format(s, "mod_dl_dst:"ETH_ADDR_FMT, ETH_ADDR_ARGS(a->mac)); +} + +/* Set IPv4 address actions. */ - case MFF_TCP_SRC: - case MFF_UDP_SRC: - case MFF_SCTP_SRC: - ofputil_put_OFPAT11_SET_TP_SRC(openflow)->tp_port = sf->value.be16; - break; +static enum ofperr +decode_OFPAT_RAW_SET_NW_SRC(ovs_be32 ipv4, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + ofpact_put_SET_IPV4_SRC(out)->ipv4 = ipv4; + return 0; +} - case MFF_TCP_DST: - case MFF_UDP_DST: - case MFF_SCTP_DST: - ofputil_put_OFPAT11_SET_TP_DST(openflow)->tp_port = sf->value.be16; - break; +static enum ofperr +decode_OFPAT_RAW_SET_NW_DST(ovs_be32 ipv4, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + ofpact_put_SET_IPV4_DST(out)->ipv4 = ipv4; + return 0; +} - case MFF_MPLS_TC: /* XXX */ - case MFF_MPLS_LABEL: /* XXX */ - default: - set_field_to_nxast(sf, openflow); - break; +static void +encode_SET_IPV4_addr(const struct ofpact_ipv4 *ipv4, + enum ofp_version ofp_version, + enum ofp_raw_action_type raw, enum mf_field_id field, + struct ofpbuf *out) +{ + ovs_be32 addr = ipv4->ipv4; + if (ofp_version < OFP12_VERSION) { + ofpact_put_raw(out, ofp_version, raw, ntohl(addr)); + } else { + ofpact_put_set_field(out, ofp_version, field, ntohl(addr)); } } -/* Convert 'sf' to standard OpenFlow 1.0 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_openflow10(const struct ofpact_set_field *sf, - struct ofpbuf *openflow) +encode_SET_IPV4_SRC(const struct ofpact_ipv4 *ipv4, + enum ofp_version ofp_version, struct ofpbuf *out) { - switch ((int) sf->field->id) { - case MFF_VLAN_TCI: - /* 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 (sf->value.be16 & htons(VLAN_CFI)) { - ofputil_put_OFPAT10_SET_VLAN_VID(openflow)->vlan_vid - = sf->value.be16 & htons(VLAN_VID_MASK); - ofputil_put_OFPAT10_SET_VLAN_PCP(openflow)->vlan_pcp - = vlan_tci_to_pcp(sf->value.be16); - } else { - ofputil_put_OFPAT10_STRIP_VLAN(openflow); - } - break; - - case MFF_VLAN_VID: - /* OXM VLAN_VID to OpenFlow 1.0. - * Set field on OXM_OF_VLAN_VID onlyapplies to an existing vlan - * tag. Clear the OFPVID_PRESENT bit. - */ - ofputil_put_OFPAT10_SET_VLAN_VID(openflow)->vlan_vid - = sf->value.be16 & htons(VLAN_VID_MASK); - break; + encode_SET_IPV4_addr(ipv4, ofp_version, OFPAT_RAW_SET_NW_SRC, MFF_IPV4_SRC, + out); +} - case MFF_VLAN_PCP: - /* OXM VLAN_PCP to OpenFlow 1.0. - * OXM_OF_VLAN_PCP only applies to existing vlan tag. */ - ofputil_put_OFPAT10_SET_VLAN_PCP(openflow)->vlan_pcp = sf->value.u8; - break; +static void +encode_SET_IPV4_DST(const struct ofpact_ipv4 *ipv4, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + encode_SET_IPV4_addr(ipv4, ofp_version, OFPAT_RAW_SET_NW_DST, MFF_IPV4_DST, + out); +} - case MFF_ETH_SRC: - memcpy(ofputil_put_OFPAT10_SET_DL_SRC(openflow)->dl_addr, - sf->value.mac, ETH_ADDR_LEN); - break; +static char * OVS_WARN_UNUSED_RESULT +parse_SET_IPV4_SRC(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_ip(arg, &ofpact_put_SET_IPV4_SRC(ofpacts)->ipv4); +} - case MFF_ETH_DST: - memcpy(ofputil_put_OFPAT10_SET_DL_DST(openflow)->dl_addr, - sf->value.mac, ETH_ADDR_LEN); - break; +static char * OVS_WARN_UNUSED_RESULT +parse_SET_IPV4_DST(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_ip(arg, &ofpact_put_SET_IPV4_DST(ofpacts)->ipv4); +} - case MFF_IPV4_SRC: - ofputil_put_OFPAT10_SET_NW_SRC(openflow)->nw_addr = sf->value.be32; - break; +static void +format_SET_IPV4_SRC(const struct ofpact_ipv4 *a, struct ds *s) +{ + ds_put_format(s, "mod_nw_src:"IP_FMT, IP_ARGS(a->ipv4)); +} - case MFF_IPV4_DST: - ofputil_put_OFPAT10_SET_NW_DST(openflow)->nw_addr = sf->value.be32; - break; +static void +format_SET_IPV4_DST(const struct ofpact_ipv4 *a, struct ds *s) +{ + ds_put_format(s, "mod_nw_dst:"IP_FMT, IP_ARGS(a->ipv4)); +} + +/* Set IPv4/v6 TOS actions. */ - case MFF_IP_DSCP: - ofputil_put_OFPAT10_SET_NW_TOS(openflow)->nw_tos = sf->value.u8; - break; +static enum ofperr +decode_OFPAT_RAW_SET_NW_TOS(uint8_t dscp, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + if (dscp & ~IP_DSCP_MASK) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } else { + ofpact_put_SET_IP_DSCP(out)->dscp = dscp; + return 0; + } +} - case MFF_IP_DSCP_SHIFTED: - ofputil_put_OFPAT10_SET_NW_TOS(openflow)->nw_tos = sf->value.u8 << 2; - break; +static void +encode_SET_IP_DSCP(const struct ofpact_dscp *dscp, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + if (ofp_version < OFP12_VERSION) { + put_OFPAT_SET_NW_TOS(out, ofp_version, dscp->dscp); + } else { + ofpact_put_set_field(out, ofp_version, + MFF_IP_DSCP_SHIFTED, dscp->dscp >> 2); + } +} - case MFF_TCP_SRC: - case MFF_UDP_SRC: - ofputil_put_OFPAT10_SET_TP_SRC(openflow)->tp_port = sf->value.be16; - break; +static char * OVS_WARN_UNUSED_RESULT +parse_SET_IP_DSCP(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + uint8_t tos; + char *error; - case MFF_TCP_DST: - case MFF_UDP_DST: - ofputil_put_OFPAT10_SET_TP_DST(openflow)->tp_port = sf->value.be16; - break; + error = str_to_u8(arg, "TOS", &tos); + if (error) { + return error; + } - default: - set_field_to_nxast(sf, openflow); - break; + if (tos & ~IP_DSCP_MASK) { + return xasprintf("%s: not a valid TOS", arg); } + ofpact_put_SET_IP_DSCP(ofpacts)->dscp = tos; + return NULL; } static void -set_field_to_openflow(const struct ofpact_set_field *sf, - struct ofpbuf *openflow) +format_SET_IP_DSCP(const struct ofpact_dscp *a, struct ds *s) { - struct ofp_header *oh = (struct ofp_header *)openflow->l2; + ds_put_format(s, "mod_nw_tos:%d", a->dscp); +} + +/* Set IPv4/v6 ECN actions. */ - if (oh->version >= OFP12_VERSION) { - set_field_to_openflow12(sf, openflow); - } else if (oh->version == OFP11_VERSION) { - set_field_to_openflow11(sf, openflow); - } else if (oh->version == OFP10_VERSION) { - set_field_to_openflow10(sf, openflow); +static enum ofperr +decode_OFPAT_RAW11_SET_NW_ECN(uint8_t ecn, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + if (ecn & ~IP_ECN_MASK) { + return OFPERR_OFPBAC_BAD_ARGUMENT; } else { - NOT_REACHED(); + ofpact_put_SET_IP_ECN(out)->ecn = ecn; + return 0; } } -static enum ofperr -output_from_openflow11(const struct ofp11_action_output *oao, - struct ofpbuf *out) +static void +encode_SET_IP_ECN(const struct ofpact_ecn *ip_ecn, + enum ofp_version ofp_version, struct ofpbuf *out) { - struct ofpact_output *output; - enum ofperr error; + uint8_t ecn = ip_ecn->ecn; + if (ofp_version == OFP10_VERSION) { + /* XXX */ + } else if (ofp_version == OFP11_VERSION) { + put_OFPAT11_SET_NW_ECN(out, ecn); + } else { + ofpact_put_set_field(out, ofp_version, MFF_IP_ECN, ecn); + } +} - output = ofpact_put_OUTPUT(out); - output->max_len = ntohs(oao->max_len); +static char * OVS_WARN_UNUSED_RESULT +parse_SET_IP_ECN(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + uint8_t ecn; + char *error; - error = ofputil_port_from_ofp11(oao->port, &output->port); + error = str_to_u8(arg, "ECN", &ecn); if (error) { return error; } - return ofputil_check_output_port(output->port, OFPP_MAX); + if (ecn & ~IP_ECN_MASK) { + return xasprintf("%s: not a valid ECN", arg); + } + ofpact_put_SET_IP_ECN(ofpacts)->ecn = ecn; + return NULL; +} + +static void +format_SET_IP_ECN(const struct ofpact_ecn *a, struct ds *s) +{ + ds_put_format(s, "mod_nw_ecn:%d", a->ecn); } + +/* Set IPv4/v6 TTL actions. */ static enum ofperr -ofpact_from_openflow11(const union ofp_action *a, enum ofp_version version, - struct ofpbuf *out) +decode_OFPAT_RAW11_SET_NW_TTL(uint8_t ttl, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) { - enum ofputil_action_code code; - enum ofperr error; - struct ofpact_vlan_vid *vlan_vid; - struct ofpact_vlan_pcp *vlan_pcp; + ofpact_put_SET_IP_TTL(out)->ttl = ttl; + return 0; +} + +static void +encode_SET_IP_TTL(const struct ofpact_ip_ttl *ttl, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + if (ofp_version >= OFP11_VERSION) { + put_OFPAT11_SET_NW_TTL(out, ttl->ttl); + } else { + /* XXX */ + } +} + +static char * OVS_WARN_UNUSED_RESULT +parse_SET_IP_TTL(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + uint8_t ttl; + char *error; - error = decode_openflow11_action(a, &code); + error = str_to_u8(arg, "TTL", &ttl); if (error) { return error; } - switch (code) { - case OFPUTIL_ACTION_INVALID: -#define OFPAT10_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM: -#include "ofp-util.def" - NOT_REACHED(); + ofpact_put_SET_IP_TTL(ofpacts)->ttl = ttl; + return NULL; +} - case OFPUTIL_OFPAT11_OUTPUT: - return output_from_openflow11(&a->ofp11_output, out); +static void +format_SET_IP_TTL(const struct ofpact_ip_ttl *a, struct ds *s) +{ + ds_put_format(s, "mod_nw_ttl:%d", a->ttl); +} + +/* Set TCP/UDP/SCTP port actions. */ - case OFPUTIL_OFPAT11_SET_VLAN_VID: - if (a->vlan_vid.vlan_vid & ~htons(0xfff)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - vlan_vid = ofpact_put_SET_VLAN_VID(out); - vlan_vid->vlan_vid = ntohs(a->vlan_vid.vlan_vid); - vlan_vid->push_vlan_if_needed = false; - vlan_vid->ofpact.compat = code; - break; +static enum ofperr +decode_OFPAT_RAW_SET_TP_SRC(ovs_be16 port, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(port); + return 0; +} - case OFPUTIL_OFPAT11_SET_VLAN_PCP: - if (a->vlan_pcp.vlan_pcp & ~7) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - vlan_pcp = ofpact_put_SET_VLAN_PCP(out); - vlan_pcp->vlan_pcp = a->vlan_pcp.vlan_pcp; - vlan_pcp->push_vlan_if_needed = false; - vlan_pcp->ofpact.compat = code; - break; +static enum ofperr +decode_OFPAT_RAW_SET_TP_DST(ovs_be16 port, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(port); + return 0; +} - case OFPUTIL_OFPAT11_PUSH_VLAN: - if (a->push.ethertype != htons(ETH_TYPE_VLAN_8021Q)) { - /* XXX 802.1AD(QinQ) isn't supported at the moment */ - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_PUSH_VLAN(out); - break; +static void +encode_SET_L4_port(const struct ofpact_l4_port *l4_port, + enum ofp_version ofp_version, enum ofp_raw_action_type raw, + enum mf_field_id field, struct ofpbuf *out) +{ + uint16_t port = l4_port->port; - case OFPUTIL_OFPAT11_POP_VLAN: - ofpact_put_STRIP_VLAN(out)->ofpact.compat = code; - break; + if (ofp_version >= OFP12_VERSION && field != MFF_N_IDS) { + ofpact_put_set_field(out, ofp_version, field, port); + } else { + ofpact_put_raw(out, ofp_version, raw, port); + } +} - case OFPUTIL_OFPAT11_SET_QUEUE: - ofpact_put_SET_QUEUE(out)->queue_id = - ntohl(a->ofp11_set_queue.queue_id); - break; +static void +encode_SET_L4_SRC_PORT(const struct ofpact_l4_port *l4_port, + enum ofp_version ofp_version, struct ofpbuf *out) +{ + uint8_t proto = l4_port->flow_ip_proto; + enum mf_field_id field = (proto == IPPROTO_TCP ? MFF_TCP_SRC + : proto == IPPROTO_UDP ? MFF_UDP_SRC + : proto == IPPROTO_SCTP ? MFF_SCTP_SRC + : MFF_N_IDS); - case OFPUTIL_OFPAT11_SET_DL_SRC: - memcpy(ofpact_put_SET_ETH_SRC(out)->mac, a->dl_addr.dl_addr, - ETH_ADDR_LEN); - break; + encode_SET_L4_port(l4_port, ofp_version, OFPAT_RAW_SET_TP_SRC, field, out); +} - case OFPUTIL_OFPAT11_SET_DL_DST: - memcpy(ofpact_put_SET_ETH_DST(out)->mac, a->dl_addr.dl_addr, - ETH_ADDR_LEN); - break; +static void +encode_SET_L4_DST_PORT(const struct ofpact_l4_port *l4_port, + enum ofp_version ofp_version, + struct ofpbuf *out) +{ + uint8_t proto = l4_port->flow_ip_proto; + enum mf_field_id field = (proto == IPPROTO_TCP ? MFF_TCP_DST + : proto == IPPROTO_UDP ? MFF_UDP_DST + : proto == IPPROTO_SCTP ? MFF_SCTP_DST + : MFF_N_IDS); - case OFPUTIL_OFPAT11_DEC_NW_TTL: - dec_ttl_from_openflow(out, code); - break; + encode_SET_L4_port(l4_port, ofp_version, OFPAT_RAW_SET_TP_DST, field, out); +} - case OFPUTIL_OFPAT11_SET_NW_SRC: - ofpact_put_SET_IPV4_SRC(out)->ipv4 = a->nw_addr.nw_addr; - break; +static char * OVS_WARN_UNUSED_RESULT +parse_SET_L4_SRC_PORT(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_u16(arg, "source port", + &ofpact_put_SET_L4_SRC_PORT(ofpacts)->port); +} - case OFPUTIL_OFPAT11_SET_NW_DST: - ofpact_put_SET_IPV4_DST(out)->ipv4 = a->nw_addr.nw_addr; - break; +static char * OVS_WARN_UNUSED_RESULT +parse_SET_L4_DST_PORT(char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + return str_to_u16(arg, "destination port", + &ofpact_put_SET_L4_DST_PORT(ofpacts)->port); +} - case OFPUTIL_OFPAT11_SET_NW_TOS: - if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_SET_IP_DSCP(out)->dscp = a->nw_tos.nw_tos; - break; +static void +format_SET_L4_SRC_PORT(const struct ofpact_l4_port *a, struct ds *s) +{ + ds_put_format(s, "mod_tp_src:%d", a->port); +} - case OFPUTIL_OFPAT11_SET_NW_ECN: - if (a->nw_ecn.nw_ecn & ~IP_ECN_MASK) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_SET_IP_ECN(out)->ecn = a->nw_ecn.nw_ecn; - break; +static void +format_SET_L4_DST_PORT(const struct ofpact_l4_port *a, struct ds *s) +{ + ds_put_format(s, "mod_tp_dst:%d", a->port); +} + +/* Action structure for OFPAT_COPY_FIELD. */ +struct ofp15_action_copy_field { + ovs_be16 type; /* OFPAT_COPY_FIELD. */ + ovs_be16 len; /* Length is padded to 64 bits. */ + ovs_be16 n_bits; /* Number of bits to copy. */ + ovs_be16 src_offset; /* Starting bit offset in source. */ + ovs_be16 dst_offset; /* Starting bit offset in destination. */ + uint8_t pad[2]; + /* Followed by: + * - OXM header for source field. + * - OXM header for destination field. + * - Padding with 0-bytes to a multiple of 8 bytes. + * The "pad2" member is the beginning of the above. */ + uint8_t pad2[4]; +}; +OFP_ASSERT(sizeof(struct ofp15_action_copy_field) == 16); + +/* Action structure for OpenFlow 1.3 extension copy-field action.. */ +struct onf_action_copy_field { + ovs_be16 type; /* OFPAT_EXPERIMENTER. */ + ovs_be16 len; /* Length is padded to 64 bits. */ + ovs_be32 experimenter; /* ONF_VENDOR_ID. */ + ovs_be16 exp_type; /* 3200. */ + uint8_t pad[2]; /* Not used. */ + ovs_be16 n_bits; /* Number of bits to copy. */ + ovs_be16 src_offset; /* Starting bit offset in source. */ + ovs_be16 dst_offset; /* Starting bit offset in destination. */ + uint8_t pad2[2]; /* Not used. */ + /* Followed by: + * - OXM header for source field. + * - OXM header for destination field. + * - Padding with 0-bytes (either 0 or 4 of them) to a multiple of 8 bytes. + * The "pad3" member is the beginning of the above. */ + uint8_t pad3[4]; /* Not used. */ +}; +OFP_ASSERT(sizeof(struct onf_action_copy_field) == 24); - case OFPUTIL_OFPAT11_SET_NW_TTL: - ofpact_put_SET_IP_TTL(out)->ttl = a->nw_ttl.nw_ttl; - break; +/* Action structure for NXAST_REG_MOVE. + * + * Copies src[src_ofs:src_ofs+n_bits] to dst[dst_ofs:dst_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. + * + * 'src' and 'dst' are nxm_header values with nxm_hasmask=0. (It doesn't make + * sense to use nxm_hasmask=1 because the action does not do any kind of + * matching; it uses the actual value of a field.) + * + * The following nxm_header values are potentially acceptable as 'src': + * + * - NXM_OF_IN_PORT + * - NXM_OF_ETH_DST + * - NXM_OF_ETH_SRC + * - NXM_OF_ETH_TYPE + * - NXM_OF_VLAN_TCI + * - NXM_OF_IP_TOS + * - NXM_OF_IP_PROTO + * - NXM_OF_IP_SRC + * - NXM_OF_IP_DST + * - NXM_OF_TCP_SRC + * - NXM_OF_TCP_DST + * - NXM_OF_UDP_SRC + * - NXM_OF_UDP_DST + * - NXM_OF_ICMP_TYPE + * - NXM_OF_ICMP_CODE + * - NXM_OF_ARP_OP + * - NXM_OF_ARP_SPA + * - NXM_OF_ARP_TPA + * - NXM_NX_TUN_ID + * - NXM_NX_ARP_SHA + * - NXM_NX_ARP_THA + * - NXM_NX_ICMPV6_TYPE + * - NXM_NX_ICMPV6_CODE + * - NXM_NX_ND_SLL + * - NXM_NX_ND_TLL + * - NXM_NX_REG(idx) for idx in the switch's accepted range. + * - NXM_NX_PKT_MARK + * - NXM_NX_TUN_IPV4_SRC + * - NXM_NX_TUN_IPV4_DST + * + * The following nxm_header values are potentially acceptable as 'dst': + * + * - NXM_OF_ETH_DST + * - NXM_OF_ETH_SRC + * - NXM_OF_IP_TOS + * - NXM_OF_IP_SRC + * - NXM_OF_IP_DST + * - NXM_OF_TCP_SRC + * - NXM_OF_TCP_DST + * - NXM_OF_UDP_SRC + * - NXM_OF_UDP_DST + * - NXM_OF_ICMP_TYPE + * - NXM_OF_ICMP_CODE + * - NXM_NX_ICMPV6_TYPE + * - NXM_NX_ICMPV6_CODE + * - NXM_NX_ARP_SHA + * - NXM_NX_ARP_THA + * - NXM_OF_ARP_OP + * - NXM_OF_ARP_SPA + * - NXM_OF_ARP_TPA + * Modifying any of the above fields changes the corresponding packet + * header. + * + * - NXM_OF_IN_PORT + * + * - NXM_NX_REG(idx) for idx in the switch's accepted range. + * + * - NXM_NX_PKT_MARK + * + * - NXM_OF_VLAN_TCI. Modifying this field's value has side effects on the + * packet's 802.1Q header. Setting a value with CFI=0 removes the 802.1Q + * header (if any), ignoring the other bits. Setting a value with CFI=1 + * adds or modifies the 802.1Q header appropriately, setting the TCI field + * to the field's new value (with the CFI bit masked out). + * + * - NXM_NX_TUN_ID, NXM_NX_TUN_IPV4_SRC, NXM_NX_TUN_IPV4_DST. Modifying + * any of these values modifies the corresponding tunnel header field used + * for the packet's next tunnel encapsulation, if allowed by the + * configuration of the output tunnel port. + * + * A given nxm_header value may be used as 'src' or 'dst' only on a flow whose + * nx_match satisfies its prerequisites. For example, NXM_OF_IP_TOS may be + * used only if the flow's nx_match includes an nxm_entry that specifies + * nxm_type=NXM_OF_ETH_TYPE, nxm_hasmask=0, and nxm_value=0x0800. + * + * The switch will reject actions for which src_ofs+n_bits is greater than the + * width of 'src' or dst_ofs+n_bits is greater than the width of 'dst' with + * error type OFPET_BAD_ACTION, code OFPBAC_BAD_ARGUMENT. + * + * This action behaves properly when 'src' overlaps with 'dst', that is, it + * behaves as if 'src' were copied out to a temporary buffer, then the + * temporary buffer copied to 'dst'. + */ +struct nx_action_reg_move { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* Length is 24. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_REG_MOVE. */ + ovs_be16 n_bits; /* Number of bits. */ + ovs_be16 src_ofs; /* Starting bit offset in source. */ + ovs_be16 dst_ofs; /* Starting bit offset in destination. */ + /* Followed by: + * - OXM/NXM header for source field (4 or 8 bytes). + * - OXM/NXM header for destination field (4 or 8 bytes). + * - Padding with 0-bytes to a multiple of 8 bytes, if necessary. */ +}; +OFP_ASSERT(sizeof(struct nx_action_reg_move) == 16); - case OFPUTIL_OFPAT11_SET_TP_SRC: - ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(a->tp_port.tp_port); - break; +static enum ofperr +decode_copy_field__(ovs_be16 src_offset, ovs_be16 dst_offset, ovs_be16 n_bits, + const void *action, ovs_be16 action_len, size_t oxm_offset, + struct ofpbuf *ofpacts) +{ + 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); - case OFPUTIL_OFPAT11_SET_TP_DST: - ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(a->tp_port.tp_port); - break; + struct ofpbuf b = ofpbuf_const_initializer(action, ntohs(action_len)); + ofpbuf_pull(&b, oxm_offset); - case OFPUTIL_OFPAT12_SET_FIELD: - return set_field_from_openflow(&a->set_field, out); + 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; + } - case OFPUTIL_OFPAT11_SET_MPLS_TTL: - ofpact_put_SET_MPLS_TTL(out)->ttl = a->ofp11_mpls_ttl.mpls_ttl; - break; + if (!is_all_zeros(b.data, b.size)) { + return OFPERR_NXBRC_MUST_BE_ZERO; + } - case OFPUTIL_OFPAT11_DEC_MPLS_TTL: - ofpact_put_DEC_MPLS_TTL(out); - break; + return nxm_reg_move_check(move, NULL); +} - case OFPUTIL_OFPAT11_PUSH_MPLS: - /* OpenFlow 1.3 has different semantics. */ - error = push_mpls_from_openflow(a->push.ethertype, - version >= OFP13_VERSION ? - OFPACT_MPLS_BEFORE_VLAN : - OFPACT_MPLS_AFTER_VLAN, out); - break; +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); +} - case OFPUTIL_OFPAT11_POP_MPLS: - if (eth_type_mpls(a->ofp11_pop_mpls.ethertype)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_POP_MPLS(out)->ethertype = a->ofp11_pop_mpls.ethertype; - break; +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); +} - case OFPUTIL_OFPAT11_GROUP: - ofpact_put_GROUP(out)->group_id = ntohl(a->group.group_id); - break; +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); -#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: -#include "ofp-util.def" - return ofpact_from_nxast(a, code, out); + 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 error; + return nxm_reg_move_check(move, NULL); } -/* True if an action sets the value of a field - * in a way that is compatibile with the action set. - * False otherwise. */ -static bool -ofpact_is_set_action(const struct ofpact *a) +static void +encode_REG_MOVE(const struct ofpact_reg_move *move, + enum ofp_version ofp_version, struct ofpbuf *out) { - switch (a->type) { - case OFPACT_SET_FIELD: - case OFPACT_REG_LOAD: - case OFPACT_SET_ETH_DST: - case OFPACT_SET_ETH_SRC: - case OFPACT_SET_IP_DSCP: - case OFPACT_SET_IP_ECN: - case OFPACT_SET_IP_TTL: - case OFPACT_SET_IPV4_DST: - case OFPACT_SET_IPV4_SRC: - case OFPACT_SET_L4_DST_PORT: - case OFPACT_SET_L4_SRC_PORT: - case OFPACT_SET_MPLS_TTL: - case OFPACT_SET_QUEUE: - case OFPACT_SET_TUNNEL: - case OFPACT_SET_VLAN_PCP: - case OFPACT_SET_VLAN_VID: - return true; - case OFPACT_BUNDLE: - case OFPACT_CLEAR_ACTIONS: - case OFPACT_CONTROLLER: - case OFPACT_DEC_MPLS_TTL: - case OFPACT_DEC_TTL: - case OFPACT_ENQUEUE: - case OFPACT_EXIT: - case OFPACT_FIN_TIMEOUT: - case OFPACT_GOTO_TABLE: - case OFPACT_GROUP: - case OFPACT_LEARN: - case OFPACT_METER: - case OFPACT_MULTIPATH: - case OFPACT_NOTE: - case OFPACT_OUTPUT: - case OFPACT_OUTPUT_REG: - case OFPACT_POP_MPLS: - case OFPACT_POP_QUEUE: - case OFPACT_PUSH_MPLS: - case OFPACT_PUSH_VLAN: - case OFPACT_REG_MOVE: - case OFPACT_RESUBMIT: - case OFPACT_SAMPLE: - case OFPACT_STACK_POP: - case OFPACT_STACK_PUSH: - case OFPACT_STRIP_VLAN: - case OFPACT_WRITE_ACTIONS: - case OFPACT_WRITE_METADATA: - return false; - default: - NOT_REACHED(); + /* 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); } -/* True if an action is allowed in the action set. - * False otherwise. */ -static bool -ofpact_is_allowed_in_actions_set(const struct ofpact *a) +static char * OVS_WARN_UNUSED_RESULT +parse_REG_MOVE(const char *arg, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols OVS_UNUSED) { - switch (a->type) { - case OFPACT_DEC_MPLS_TTL: - case OFPACT_DEC_TTL: - case OFPACT_GROUP: - case OFPACT_OUTPUT: - case OFPACT_POP_MPLS: - case OFPACT_PUSH_MPLS: - case OFPACT_PUSH_VLAN: - case OFPACT_REG_LOAD: - case OFPACT_SET_FIELD: - case OFPACT_SET_ETH_DST: - case OFPACT_SET_ETH_SRC: - case OFPACT_SET_IP_DSCP: - case OFPACT_SET_IP_ECN: - case OFPACT_SET_IP_TTL: - case OFPACT_SET_IPV4_DST: - case OFPACT_SET_IPV4_SRC: - case OFPACT_SET_L4_DST_PORT: - case OFPACT_SET_L4_SRC_PORT: - case OFPACT_SET_MPLS_TTL: - case OFPACT_SET_QUEUE: - case OFPACT_SET_TUNNEL: - case OFPACT_SET_VLAN_PCP: - case OFPACT_SET_VLAN_VID: - case OFPACT_STRIP_VLAN: - return true; + struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts); + const char *full_arg = arg; + char *error; - /* In general these actions are excluded because they are not part of - * the OpenFlow specification nor map to actions that are defined in - * the specification. Thus the order in which they should be applied - * in the action set is undefined. */ - case OFPACT_BUNDLE: - case OFPACT_CONTROLLER: - case OFPACT_ENQUEUE: - case OFPACT_EXIT: - case OFPACT_FIN_TIMEOUT: - case OFPACT_LEARN: - case OFPACT_MULTIPATH: - case OFPACT_NOTE: - case OFPACT_OUTPUT_REG: - case OFPACT_POP_QUEUE: - case OFPACT_REG_MOVE: - case OFPACT_RESUBMIT: - case OFPACT_SAMPLE: - case OFPACT_STACK_POP: - case OFPACT_STACK_PUSH: + 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; + } - /* The action set may only include actions and thus - * may not include any instructions */ - case OFPACT_CLEAR_ACTIONS: - case OFPACT_GOTO_TABLE: - case OFPACT_METER: - case OFPACT_WRITE_ACTIONS: - case OFPACT_WRITE_METADATA: - return false; - default: - NOT_REACHED(); + 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; } -/* Append ofpact 'a' onto the tail of 'out' */ static void -ofpact_copy(struct ofpbuf *out, const struct ofpact *a) +format_REG_MOVE(const struct ofpact_reg_move *a, struct ds *s) { - ofpbuf_put(out, a, OFPACT_ALIGN(a->len)); + nxm_format_reg_move(a, s); } + +/* 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); -/* Copies the last ofpact whose type is 'filter' from 'in' to 'out'. */ -static bool -ofpacts_copy_last(struct ofpbuf *out, const struct ofpbuf *in, - enum ofpact_type filter) +/* 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) { - const struct ofpact *target; - const struct ofpact *a; + struct ofpbuf b = ofpbuf_const_initializer(oasf, ntohs(oasf->len)); + ofpbuf_pull(&b, OBJECT_OFFSETOF(oasf, pad)); - target = NULL; - OFPACT_FOR_EACH (a, in->data, in->size) { - if (a->type == filter) { - target = a; - } + 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 (target) { - ofpact_copy(out, target); + if (!may_mask) { + memset(&sf->mask, 0xff, sf->field->n_bytes); } - return target != NULL; -} -/* Append all ofpacts, for which 'filter' returns true, from 'in' to 'out'. - * The order of appended ofpacts is preserved between 'in' and 'out' */ -static void -ofpacts_copy_all(struct ofpbuf *out, const struct ofpbuf *in, - bool (*filter)(const struct ofpact *)) -{ - const struct ofpact *a; + if (!is_all_zeros(b.data, b.size)) { + return OFPERR_OFPBAC_BAD_SET_ARGUMENT; + } - OFPACT_FOR_EACH (a, in->data, in->size) { - if (filter(a)) { - ofpact_copy(out, a); - } + /* 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; } -} -/* Reads 'action_set', which contains ofpacts accumulated by - * OFPACT_WRITE_ACTIONS instructions, and writes equivalent actions to be - * executed directly into 'action_list'. (These names correspond to the - * "Action Set" and "Action List" terms used in OpenFlow 1.1+.) - * - * In general this involves appending the last instance of each action that is - * adimissible in the action set in the order described in the OpenFlow - * specification. - * - * Exceptions: - * + output action is only appended if no group action was present in 'in'. - * + As a simplification all set actions are copied in the order the are - * provided in 'in' as many set actions applied to a field has the same - * affect as only applying the last action that sets a field and - * duplicates are removed by do_xlate_actions(). - * This has an unwanted side-effect of compsoting multiple - * LOAD_REG actions that touch different regions of the same field. */ -void -ofpacts_execute_action_set(struct ofpbuf *action_list, - const struct ofpbuf *action_set) -{ - /* The OpenFlow spec "Action Set" section specifies this order. */ - ofpacts_copy_last(action_list, action_set, OFPACT_STRIP_VLAN); - ofpacts_copy_last(action_list, action_set, OFPACT_POP_MPLS); - ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_MPLS); - ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_VLAN); - ofpacts_copy_last(action_list, action_set, OFPACT_DEC_TTL); - ofpacts_copy_last(action_list, action_set, OFPACT_DEC_MPLS_TTL); - ofpacts_copy_all(action_list, action_set, ofpact_is_set_action); - ofpacts_copy_last(action_list, action_set, OFPACT_SET_QUEUE); + /* 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; + } - /* If both OFPACT_GROUP and OFPACT_OUTPUT are present, OpenFlow says that - * we should execute only OFPACT_GROUP. - * - * If neither OFPACT_GROUP nor OFPACT_OUTPUT is present, then we can drop - * all the actions because there's no point in modifying a packet that will - * not be sent anywhere. */ - if (!ofpacts_copy_last(action_list, action_set, OFPACT_GROUP) && - !ofpacts_copy_last(action_list, action_set, OFPACT_OUTPUT)) { - ofpbuf_clear(action_list); + /* 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 -ofpacts_from_openflow11_for_action_set(const union ofp_action *in, - size_t n_in, enum ofp_version version, - struct ofpbuf *out) +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; - struct ofpact *a; - size_t start = out->size; - error = ofpacts_from_openflow(in, n_in, version, out); + 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; } - OFPACT_FOR_EACH (a, ofpact_end(out->data, start), out->size - start) { - if (!ofpact_is_allowed_in_actions_set(a)) { - VLOG_WARN_RL(&rl, "disallowed action in action set"); - return OFPERR_OFPBAC_BAD_TYPE; - } + /* 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; } - -/* OpenFlow 1.1 instructions. */ +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; -#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) \ - static inline const struct STRUCT * OVS_UNUSED \ - instruction_get_##ENUM(const struct ofp11_instruction *inst)\ - { \ - ovs_assert(inst->type == htons(ENUM)); \ - return ALIGNED_CAST(struct STRUCT *, inst); \ - } \ - \ - static inline void OVS_UNUSED \ - instruction_init_##ENUM(struct STRUCT *s) \ - { \ - memset(s, 0, sizeof *s); \ - s->type = htons(ENUM); \ - s->len = htons(sizeof *s); \ - } \ - \ - static inline struct STRUCT * OVS_UNUSED \ - instruction_put_##ENUM(struct ofpbuf *buf) \ - { \ - struct STRUCT *s = ofpbuf_put_uninit(buf, sizeof *s); \ - instruction_init_##ENUM(s); \ - return s; \ - } -OVS_INSTRUCTIONS -#undef DEFINE_INST + struct ofpbuf b = ofpbuf_const_initializer(narl, ntohs(narl->len)); + ofpbuf_pull(&b, OBJECT_OFFSETOF(narl, pad)); -struct instruction_type_info { - enum ovs_instruction_type type; - const char *name; -}; + 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; + } -static const struct instruction_type_info inst_info[] = { -#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) {OVSINST_##ENUM, NAME}, -OVS_INSTRUCTIONS -#undef DEFINE_INST -}; + 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; +} -const char * -ovs_instruction_name_from_type(enum ovs_instruction_type type) +static void +ofpact_put_set_field(struct ofpbuf *openflow, enum ofp_version ofp_version, + enum mf_field_id field, uint64_t value_) { - return inst_info[type].name; + 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); } -int -ovs_instruction_type_from_name(const char *name) +static bool +next_load_segment(const struct ofpact_set_field *sf, + struct mf_subfield *dst, uint64_t *value) { - const struct instruction_type_info *p; - for (p = inst_info; p < &inst_info[ARRAY_SIZE(inst_info)]; p++) { - if (!strcasecmp(name, p->name)) { - return p->type; + 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 -1; + return false; } -enum ovs_instruction_type -ovs_instruction_type_from_ofpact_type(enum ofpact_type type) +/* Convert 'sf' to a series of REG_LOADs. */ +static void +set_field_to_nxast(const struct ofpact_set_field *sf, struct ofpbuf *openflow) { - switch (type) { - case OFPACT_METER: - return OVSINST_OFPIT13_METER; - case OFPACT_CLEAR_ACTIONS: - return OVSINST_OFPIT11_CLEAR_ACTIONS; - case OFPACT_WRITE_ACTIONS: - return OVSINST_OFPIT11_WRITE_ACTIONS; - case OFPACT_WRITE_METADATA: - return OVSINST_OFPIT11_WRITE_METADATA; - case OFPACT_GOTO_TABLE: - return OVSINST_OFPIT11_GOTO_TABLE; - case OFPACT_OUTPUT: - case OFPACT_GROUP: - case OFPACT_CONTROLLER: - case OFPACT_ENQUEUE: - case OFPACT_OUTPUT_REG: - case OFPACT_BUNDLE: - case OFPACT_SET_VLAN_VID: - case OFPACT_SET_VLAN_PCP: - case OFPACT_STRIP_VLAN: - case OFPACT_PUSH_VLAN: - case OFPACT_SET_ETH_SRC: - case OFPACT_SET_ETH_DST: - case OFPACT_SET_IPV4_SRC: - case OFPACT_SET_IPV4_DST: - case OFPACT_SET_IP_DSCP: - case OFPACT_SET_IP_ECN: - case OFPACT_SET_IP_TTL: - case OFPACT_SET_L4_SRC_PORT: - case OFPACT_SET_L4_DST_PORT: - case OFPACT_REG_MOVE: - case OFPACT_REG_LOAD: - case OFPACT_SET_FIELD: - case OFPACT_STACK_PUSH: - case OFPACT_STACK_POP: - case OFPACT_DEC_TTL: - case OFPACT_SET_MPLS_TTL: - case OFPACT_DEC_MPLS_TTL: - case OFPACT_PUSH_MPLS: - case OFPACT_POP_MPLS: - case OFPACT_SET_TUNNEL: - case OFPACT_SET_QUEUE: - case OFPACT_POP_QUEUE: - case OFPACT_FIN_TIMEOUT: - case OFPACT_RESUBMIT: - case OFPACT_LEARN: - case OFPACT_MULTIPATH: - case OFPACT_NOTE: - case OFPACT_EXIT: - case OFPACT_SAMPLE: - default: - return OVSINST_OFPIT11_APPLY_ACTIONS; + /* 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); + } } } -static inline struct ofp11_instruction * -instruction_next(const struct ofp11_instruction *inst) +/* 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) { - return ((struct ofp11_instruction *) (void *) - ((uint8_t *) inst + ntohs(inst->len))); -} + 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; + } -static inline bool -instruction_is_valid(const struct ofp11_instruction *inst, - size_t n_instructions) -{ - uint16_t len = ntohs(inst->len); - return (!(len % OFP11_INSTRUCTION_ALIGN) - && len >= sizeof *inst - && len / sizeof *inst <= n_instructions); -} + 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; + } -/* This macro is careful to check for instructions with bad lengths. */ -#define INSTRUCTION_FOR_EACH(ITER, LEFT, INSTRUCTIONS, N_INSTRUCTIONS) \ - for ((ITER) = (INSTRUCTIONS), (LEFT) = (N_INSTRUCTIONS); \ - (LEFT) > 0 && instruction_is_valid(ITER, LEFT); \ - ((LEFT) -= (ntohs((ITER)->len) \ - / sizeof(struct ofp11_instruction)), \ - (ITER) = instruction_next(ITER))) + 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; -static enum ofperr -decode_openflow11_instruction(const struct ofp11_instruction *inst, - enum ovs_instruction_type *type) -{ - uint16_t len = ntohs(inst->len); + case MFF_ETH_SRC: + put_OFPAT_SET_DL_SRC(out, ofp_version)->dl_addr = sf->value.mac; + break; - switch (inst->type) { - case CONSTANT_HTONS(OFPIT11_EXPERIMENTER): - return OFPERR_OFPBIC_BAD_EXPERIMENTER; + case MFF_ETH_DST: + put_OFPAT_SET_DL_DST(out, ofp_version)->dl_addr = sf->value.mac; + break; -#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) \ - case CONSTANT_HTONS(ENUM): \ - if (EXTENSIBLE \ - ? len >= sizeof(struct STRUCT) \ - : len == sizeof(struct STRUCT)) { \ - *type = OVSINST_##ENUM; \ - return 0; \ - } else { \ - return OFPERR_OFPBIC_BAD_LEN; \ - } -OVS_INSTRUCTIONS -#undef DEFINE_INST + case MFF_IPV4_SRC: + put_OFPAT_SET_NW_SRC(out, ofp_version, sf->value.be32); + break; - default: - return OFPERR_OFPBIC_UNKNOWN_INST; - } -} + case MFF_IPV4_DST: + put_OFPAT_SET_NW_DST(out, ofp_version, sf->value.be32); + break; -static enum ofperr -decode_openflow11_instructions(const struct ofp11_instruction insts[], - size_t n_insts, - const struct ofp11_instruction *out[]) -{ - const struct ofp11_instruction *inst; - size_t left; + case MFF_IP_DSCP: + put_OFPAT_SET_NW_TOS(out, ofp_version, sf->value.u8); + break; - memset(out, 0, N_OVS_INSTRUCTIONS * sizeof *out); - INSTRUCTION_FOR_EACH (inst, left, insts, n_insts) { - enum ovs_instruction_type type; - enum ofperr error; + case MFF_IP_DSCP_SHIFTED: + put_OFPAT_SET_NW_TOS(out, ofp_version, sf->value.u8 << 2); + break; - error = decode_openflow11_instruction(inst, &type); - if (error) { - return error; - } + case MFF_TCP_SRC: + case MFF_UDP_SRC: + put_OFPAT_SET_TP_SRC(out, sf->value.be16); + break; - if (out[type]) { - return OFPERR_ONFBIC_DUP_INSTRUCTION; - } - out[type] = inst; - } + case MFF_TCP_DST: + case MFF_UDP_DST: + put_OFPAT_SET_TP_DST(out, sf->value.be16); + break; - if (left) { - VLOG_WARN_RL(&rl, "bad instruction format at offset %zu", - (n_insts - left) * sizeof *inst); - return OFPERR_OFPBIC_BAD_LEN; + default: + set_field_to_nxast(sf, out); + break; } - return 0; } static void -get_actions_from_instruction(const struct ofp11_instruction *inst, - const union ofp_action **actions, - size_t *max_actions) +set_field_to_set_field(const struct ofpact_set_field *sf, + enum ofp_version ofp_version, struct ofpbuf *out) { - *actions = ALIGNED_CAST(const union ofp_action *, inst + 1); - *max_actions = (ntohs(inst->len) - sizeof *inst) / OFP11_INSTRUCTION_ALIGN; + 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); } -enum ofperr -ofpacts_pull_openflow_instructions(struct ofpbuf *openflow, - unsigned int instructions_len, - enum ofp_version version, - struct ofpbuf *ofpacts) +static void +encode_SET_FIELD(const struct ofpact_set_field *sf, + enum ofp_version ofp_version, struct ofpbuf *out) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - const struct ofp11_instruction *instructions; - const struct ofp11_instruction *insts[N_OVS_INSTRUCTIONS]; - enum ofperr error; + 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); + } +} - ofpbuf_clear(ofpacts); +/* 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; - if (instructions_len % OFP11_INSTRUCTION_ALIGN != 0) { - VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u is not a " - "multiple of %d", - instructions_len, OFP11_INSTRUCTION_ALIGN); - error = OFPERR_OFPBIC_BAD_LEN; - goto exit; + *value = arg; + value_end = strstr(arg, "->"); + *key = value_end + strlen("->"); + if (delim) { + *delim = value_end; } - instructions = ofpbuf_try_pull(openflow, instructions_len); - if (instructions == NULL) { - VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u exceeds " - "remaining message length (%zu)", - instructions_len, openflow->size); - error = OFPERR_OFPBIC_BAD_LEN; - goto exit; + if (!value_end) { + return xasprintf("%s: missing `->'", arg); } - - error = decode_openflow11_instructions( - instructions, instructions_len / OFP11_INSTRUCTION_ALIGN, - insts); - if (error) { - goto exit; + if (strlen(value_end) <= strlen("->")) { + return xasprintf("%s: missing field name following `->'", arg); } - if (insts[OVSINST_OFPIT13_METER]) { - const struct ofp13_instruction_meter *oim; - struct ofpact_meter *om; + return NULL; +} - oim = ALIGNED_CAST(const struct ofp13_instruction_meter *, - insts[OVSINST_OFPIT13_METER]); +/* 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; - om = ofpact_put_METER(ofpacts); - om->meter_id = ntohl(oim->meter_id); + error = set_field_split_str(arg, &key, &value, &delim); + if (error) { + return error; } - if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) { - const union ofp_action *actions; - size_t max_actions; - get_actions_from_instruction(insts[OVSINST_OFPIT11_APPLY_ACTIONS], - &actions, &max_actions); - error = ofpacts_from_openflow(actions, max_actions, version, ofpacts); - if (error) { - goto exit; - } - } - if (insts[OVSINST_OFPIT11_CLEAR_ACTIONS]) { - instruction_get_OFPIT11_CLEAR_ACTIONS( - insts[OVSINST_OFPIT11_CLEAR_ACTIONS]); - ofpact_put_CLEAR_ACTIONS(ofpacts); + mf = mf_from_name(key); + if (!mf) { + return xasprintf("%s is not a valid OXM field name", key); } - if (insts[OVSINST_OFPIT11_WRITE_ACTIONS]) { - struct ofpact_nest *on; - const union ofp_action *actions; - size_t max_actions; - size_t start; - - ofpact_pad(ofpacts); - start = ofpacts->size; - on = ofpact_put(ofpacts, OFPACT_WRITE_ACTIONS, - offsetof(struct ofpact_nest, actions)); - get_actions_from_instruction(insts[OVSINST_OFPIT11_WRITE_ACTIONS], - &actions, &max_actions); - error = ofpacts_from_openflow11_for_action_set(actions, max_actions, - version, ofpacts); - if (error) { - goto exit; - } - on = ofpbuf_at_assert(ofpacts, start, sizeof *on); - on->ofpact.len = ofpacts->size - start; + if (!mf->writable) { + return xasprintf("%s is read-only", key); } - if (insts[OVSINST_OFPIT11_WRITE_METADATA]) { - const struct ofp11_instruction_write_metadata *oiwm; - struct ofpact_metadata *om; - - oiwm = ALIGNED_CAST(const struct ofp11_instruction_write_metadata *, - insts[OVSINST_OFPIT11_WRITE_METADATA]); - - om = ofpact_put_WRITE_METADATA(ofpacts); - om->metadata = oiwm->metadata; - om->mask = oiwm->metadata_mask; + sf->field = mf; + delim[0] = '\0'; + error = mf_parse(mf, value, &sf->value, &sf->mask); + if (error) { + return error; } - if (insts[OVSINST_OFPIT11_GOTO_TABLE]) { - const struct ofp11_instruction_goto_table *oigt; - struct ofpact_goto_table *ogt; - oigt = instruction_get_OFPIT11_GOTO_TABLE( - insts[OVSINST_OFPIT11_GOTO_TABLE]); - ogt = ofpact_put_GOTO_TABLE(ofpacts); - ogt->table_id = oigt->table_id; + if (!mf_is_value_valid(mf, &sf->value)) { + return xasprintf("%s is not a valid value for field %s", value, key); } - error = ofpacts_verify(ofpacts->data, ofpacts->size); -exit: - if (error) { - ofpbuf_clear(ofpacts); - } - return error; + *usable_protocols &= mf->usable_protocols_exact; + return NULL; } - -/* May modify flow->dl_type and flow->vlan_tci, caller must restore them. + +/* Parses 'arg' as the argument to a "set_field" action, and appends such an + * action to 'ofpacts'. * - * Modifies some actions, filling in fields that could not be properly set - * without context. */ -static enum ofperr -ofpact_check__(struct ofpact *a, struct flow *flow, ofp_port_t max_ports, - uint8_t table_id, bool enforce_consistency) + * 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) { - const struct ofpact_enqueue *enqueue; - const struct mf_field *mf; + char *copy = xstrdup(arg); + char *error = set_field_parse__(copy, ofpacts, usable_protocols); + free(copy); + return error; +} - switch (a->type) { - case OFPACT_OUTPUT: - return ofputil_check_output_port(ofpact_get_OUTPUT(a)->port, - max_ports); +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; - case OFPACT_CONTROLLER: - return 0; + error = set_field_split_str(arg, &key, &value_str, NULL); + if (error) { + return error; + } - case OFPACT_ENQUEUE: - enqueue = ofpact_get_ENQUEUE(a); - if (ofp_to_u16(enqueue->port) >= ofp_to_u16(max_ports) - && enqueue->port != OFPP_IN_PORT - && enqueue->port != OFPP_LOCAL) { - return OFPERR_OFPBAC_BAD_OUT_PORT; - } - return 0; + error = mf_parse_subfield(&dst, key); + if (error) { + return error; + } - case OFPACT_OUTPUT_REG: - return mf_check_src(&ofpact_get_OUTPUT_REG(a)->src, flow); + if (parse_int_string(value_str, (uint8_t *)&value, dst.field->n_bytes, + &key)) { + return xasprintf("%s: cannot parse integer value", arg); + } - case OFPACT_BUNDLE: - return bundle_check(ofpact_get_BUNDLE(a), max_ports, flow); + if (!bitwise_is_all_zeros(&value, dst.field->n_bytes, dst.n_bits, + dst.field->n_bytes * 8 - dst.n_bits)) { + struct ds ds; - case OFPACT_SET_VLAN_VID: - /* Remember if we saw a vlan tag in the flow to aid translating to - * OpenFlow 1.1+ if need be. */ - ofpact_get_SET_VLAN_VID(a)->flow_has_vlan = - (flow->vlan_tci & htons(VLAN_CFI)) == htons(VLAN_CFI); - if (!(flow->vlan_tci & htons(VLAN_CFI)) && - !ofpact_get_SET_VLAN_VID(a)->push_vlan_if_needed) { - goto inconsistent; - } - /* Temporary mark that we have a vlan tag. */ - flow->vlan_tci |= htons(VLAN_CFI); - return 0; + 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; + } - case OFPACT_SET_VLAN_PCP: - /* Remember if we saw a vlan tag in the flow to aid translating to - * OpenFlow 1.1+ if need be. */ - ofpact_get_SET_VLAN_PCP(a)->flow_has_vlan = - (flow->vlan_tci & htons(VLAN_CFI)) == htons(VLAN_CFI); - if (!(flow->vlan_tci & htons(VLAN_CFI)) && - !ofpact_get_SET_VLAN_PCP(a)->push_vlan_if_needed) { - goto inconsistent; - } - /* Temporary mark that we have a vlan tag. */ - flow->vlan_tci |= htons(VLAN_CFI); - return 0; + 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); - case OFPACT_STRIP_VLAN: - if (!(flow->vlan_tci & htons(VLAN_CFI))) { - goto inconsistent; - } - /* Temporary mark that we have no vlan tag. */ - flow->vlan_tci = htons(0); - return 0; + return NULL; +} - case OFPACT_PUSH_VLAN: - if (flow->vlan_tci & htons(VLAN_CFI)) { - /* Multiple VLAN headers not supported. */ - return OFPERR_OFPBAC_BAD_TAG; +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, ','); } - /* Temporary mark that we have a vlan tag. */ - flow->vlan_tci |= htons(VLAN_CFI); - return 0; + 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); + } +} - case OFPACT_SET_ETH_SRC: - case OFPACT_SET_ETH_DST: - return 0; +/* 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; +} + +/* 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); - case OFPACT_SET_IPV4_SRC: - case OFPACT_SET_IPV4_DST: - if (flow->dl_type != htons(ETH_TYPE_IP)) { - goto inconsistent; - } - return 0; +static enum ofperr +decode_stack_action(const struct nx_action_stack *nasp, + struct ofpact_stack *stack_action) +{ + stack_action->subfield.ofs = ntohs(nasp->offset); - case OFPACT_SET_IP_DSCP: - case OFPACT_SET_IP_ECN: - case OFPACT_SET_IP_TTL: - case OFPACT_DEC_TTL: - if (!is_ip_any(flow)) { - goto inconsistent; - } - return 0; + 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; + } - case OFPACT_SET_L4_SRC_PORT: - case OFPACT_SET_L4_DST_PORT: - if (!is_ip_any(flow) || - (flow->nw_proto != IPPROTO_TCP && flow->nw_proto != IPPROTO_UDP - && flow->nw_proto != IPPROTO_SCTP)) { - goto inconsistent; - } - return 0; + return 0; +} - case OFPACT_REG_MOVE: - return nxm_reg_move_check(ofpact_get_REG_MOVE(a), flow); +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); +} - case OFPACT_REG_LOAD: - return nxm_reg_load_check(ofpact_get_REG_LOAD(a), flow); +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); +} - case OFPACT_SET_FIELD: - mf = ofpact_get_SET_FIELD(a)->field; - /* Require OXM_OF_VLAN_VID to have an existing VLAN header. */ - if (!mf_are_prereqs_ok(mf, flow) || - (mf->id == MFF_VLAN_VID && !(flow->vlan_tci & htons(VLAN_CFI)))) { - VLOG_WARN_RL(&rl, "set_field %s lacks correct prerequisities", - mf->name); - return OFPERR_OFPBAC_MATCH_INCONSISTENT; - } - /* Remember if we saw a vlan tag in the flow to aid translating to - * OpenFlow 1.1 if need be. */ - ofpact_get_SET_FIELD(a)->flow_has_vlan = - (flow->vlan_tci & htons(VLAN_CFI)) == htons(VLAN_CFI); - if (mf->id == MFF_VLAN_TCI) { - /* The set field may add or remove the vlan tag, - * Mark the status temporarily. */ - flow->vlan_tci = ofpact_get_SET_FIELD(a)->value.be16; - } - return 0; +static void +encode_STACK_op(const struct ofpact_stack *stack_action, + struct nx_action_stack *nasp) +{ + struct ofpbuf b; + ovs_be16 n_bits; - case OFPACT_STACK_PUSH: - return nxm_stack_push_check(ofpact_get_STACK_PUSH(a), flow); + nasp->offset = htons(stack_action->subfield.ofs); - case OFPACT_STACK_POP: - return nxm_stack_pop_check(ofpact_get_STACK_POP(a), flow); + 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); +} - case OFPACT_SET_MPLS_TTL: - case OFPACT_DEC_MPLS_TTL: - if (!eth_type_mpls(flow->dl_type)) { - goto inconsistent; - } - return 0; +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)); +} - case OFPACT_SET_TUNNEL: - case OFPACT_SET_QUEUE: - case OFPACT_POP_QUEUE: - case OFPACT_RESUBMIT: - return 0; +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)); +} - case OFPACT_FIN_TIMEOUT: - if (flow->nw_proto != IPPROTO_TCP) { - goto inconsistent; - } - return 0; +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); +} - case OFPACT_LEARN: - return learn_check(ofpact_get_LEARN(a), flow); +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); +} - case OFPACT_MULTIPATH: - return multipath_check(ofpact_get_MULTIPATH(a), flow); +static void +format_STACK_PUSH(const struct ofpact_stack *a, struct ds *s) +{ + nxm_format_stack_push(a, s); +} - case OFPACT_NOTE: - case OFPACT_EXIT: +static void +format_STACK_POP(const struct ofpact_stack *a, struct ds *s) +{ + nxm_format_stack_pop(a, s); +} + +/* 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, ")"); + } +} + +/* 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)); +} + +/* 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); +} + +/* 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); +} + +/* 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"); +} + +/* 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)); +} + +/* 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)); +} + +/* 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); +} + +/* 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); +} + +/* 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"); +} + +/* 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, ')'); +} + +/* 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, ')'); + } +} + +/* 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); +} + +/* 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; +} + +/* 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); +} + +/* 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); +} + +/* 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"); +} + +/* 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)); +} + +/* 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); +} + +/* 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, ')'); +} + +/* 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; +} + + +/* 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); +} + +/* 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"); +} + +/* 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, ')'); +} + +/* 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)); + } +} + +/* 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); +} + +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; +} + +/* Attempts to convert 'actions_len' bytes of OpenFlow actions from the front + * of 'openflow' into ofpacts. On success, appends the converted actions to + * 'ofpacts'; on failure, 'ofpacts' is unchanged (but might be reallocated) . + * Returns 0 if successful, otherwise an OpenFlow error. + * + * Actions are processed according to their OpenFlow version which + * is provided in the 'version' parameter. + * + * In most places in OpenFlow, actions appear encapsulated in instructions, so + * you should call ofpacts_pull_openflow_instructions() instead of this + * function. + * + * The parsed actions are valid generically, but they may not be valid in a + * specific context. For example, port numbers up to OFPP_MAX are valid + * generically, but specific datapaths may only support port numbers in a + * smaller range. Use ofpacts_check() to additional check whether actions are + * valid in a specific context. */ +enum ofperr +ofpacts_pull_openflow_actions(struct ofpbuf *openflow, + unsigned int actions_len, + enum ofp_version version, + struct ofpbuf *ofpacts) +{ + return ofpacts_pull_openflow_actions__(openflow, actions_len, version, + 1u << OVSINST_OFPIT11_APPLY_ACTIONS, + ofpacts, 0); +} + +/* OpenFlow 1.1 actions. */ + +/* True if an action sets the value of a field + * in a way that is compatibile with the action set. + * The field can be set via either a set or a move action. + * False otherwise. */ +static bool +ofpact_is_set_or_move_action(const struct ofpact *a) +{ + switch (a->type) { + case OFPACT_SET_FIELD: + case OFPACT_REG_MOVE: + case OFPACT_SET_ETH_DST: + case OFPACT_SET_ETH_SRC: + case OFPACT_SET_IP_DSCP: + case OFPACT_SET_IP_ECN: + case OFPACT_SET_IP_TTL: + case OFPACT_SET_IPV4_DST: + case OFPACT_SET_IPV4_SRC: + case OFPACT_SET_L4_DST_PORT: + case OFPACT_SET_L4_SRC_PORT: + case OFPACT_SET_MPLS_LABEL: + case OFPACT_SET_MPLS_TC: + case OFPACT_SET_MPLS_TTL: + case OFPACT_SET_QUEUE: + case OFPACT_SET_TUNNEL: + case OFPACT_SET_VLAN_PCP: + case OFPACT_SET_VLAN_VID: + return true; + case OFPACT_BUNDLE: + case OFPACT_CLEAR_ACTIONS: + case OFPACT_CT: + case OFPACT_NAT: + case OFPACT_CONTROLLER: + case OFPACT_DEC_MPLS_TTL: + case OFPACT_DEC_TTL: + case OFPACT_ENQUEUE: + case OFPACT_EXIT: + case OFPACT_UNROLL_XLATE: + case OFPACT_FIN_TIMEOUT: + case OFPACT_GOTO_TABLE: + case OFPACT_GROUP: + case OFPACT_LEARN: + case OFPACT_CONJUNCTION: + case OFPACT_METER: + case OFPACT_MULTIPATH: + case OFPACT_NOTE: + case OFPACT_OUTPUT: + case OFPACT_OUTPUT_REG: + case OFPACT_POP_MPLS: + case OFPACT_POP_QUEUE: case OFPACT_PUSH_MPLS: - flow->dl_type = ofpact_get_PUSH_MPLS(a)->ethertype; - return 0; + case OFPACT_PUSH_VLAN: + case OFPACT_RESUBMIT: + case OFPACT_SAMPLE: + case OFPACT_STACK_POP: + case OFPACT_STACK_PUSH: + case OFPACT_STRIP_VLAN: + case OFPACT_WRITE_ACTIONS: + case OFPACT_WRITE_METADATA: + case OFPACT_DEBUG_RECIRC: + return false; + default: + OVS_NOT_REACHED(); + } +} +/* True if an action is allowed in the action set. + * False otherwise. */ +static bool +ofpact_is_allowed_in_actions_set(const struct ofpact *a) +{ + switch (a->type) { + case OFPACT_DEC_MPLS_TTL: + case OFPACT_DEC_TTL: + case OFPACT_GROUP: + case OFPACT_OUTPUT: case OFPACT_POP_MPLS: - flow->dl_type = ofpact_get_POP_MPLS(a)->ethertype; - if (!eth_type_mpls(flow->dl_type)) { - goto inconsistent; - } - return 0; + case OFPACT_PUSH_MPLS: + case OFPACT_PUSH_VLAN: + case OFPACT_REG_MOVE: + case OFPACT_SET_FIELD: + case OFPACT_SET_ETH_DST: + case OFPACT_SET_ETH_SRC: + case OFPACT_SET_IP_DSCP: + case OFPACT_SET_IP_ECN: + case OFPACT_SET_IP_TTL: + case OFPACT_SET_IPV4_DST: + case OFPACT_SET_IPV4_SRC: + case OFPACT_SET_L4_DST_PORT: + case OFPACT_SET_L4_SRC_PORT: + case OFPACT_SET_MPLS_LABEL: + case OFPACT_SET_MPLS_TC: + case OFPACT_SET_MPLS_TTL: + case OFPACT_SET_QUEUE: + case OFPACT_SET_TUNNEL: + case OFPACT_SET_VLAN_PCP: + case OFPACT_SET_VLAN_VID: + case OFPACT_STRIP_VLAN: + return true; + /* In general these actions are excluded because they are not part of + * the OpenFlow specification nor map to actions that are defined in + * the specification. Thus the order in which they should be applied + * in the action set is undefined. */ + case OFPACT_BUNDLE: + case OFPACT_CONTROLLER: + case OFPACT_CT: + case OFPACT_NAT: + case OFPACT_ENQUEUE: + case OFPACT_EXIT: + case OFPACT_UNROLL_XLATE: + case OFPACT_FIN_TIMEOUT: + case OFPACT_LEARN: + case OFPACT_CONJUNCTION: + case OFPACT_MULTIPATH: + case OFPACT_NOTE: + case OFPACT_OUTPUT_REG: + case OFPACT_POP_QUEUE: + case OFPACT_RESUBMIT: case OFPACT_SAMPLE: - return 0; + case OFPACT_STACK_POP: + case OFPACT_STACK_PUSH: + case OFPACT_DEBUG_RECIRC: + /* The action set may only include actions and thus + * may not include any instructions */ case OFPACT_CLEAR_ACTIONS: - return 0; - - case OFPACT_WRITE_ACTIONS: { - struct ofpact_nest *on = ofpact_get_WRITE_ACTIONS(a); - return ofpacts_check(on->actions, ofpact_nest_get_action_len(on), - flow, max_ports, table_id, false); - } - + case OFPACT_GOTO_TABLE: + case OFPACT_METER: + case OFPACT_WRITE_ACTIONS: case OFPACT_WRITE_METADATA: - return 0; - - case OFPACT_METER: { - uint32_t mid = ofpact_get_METER(a)->meter_id; - if (mid == 0 || mid > OFPM13_MAX) { - return OFPERR_OFPMMFC_INVALID_METER; - } - return 0; + return false; + default: + OVS_NOT_REACHED(); } +} - case OFPACT_GOTO_TABLE: - if (ofpact_get_GOTO_TABLE(a)->table_id <= table_id) { - return OFPERR_OFPBRC_BAD_TABLE_ID; - } - return 0; +/* Append ofpact 'a' onto the tail of 'out' */ +static void +ofpact_copy(struct ofpbuf *out, const struct ofpact *a) +{ + ofpbuf_put(out, a, OFPACT_ALIGN(a->len)); +} - case OFPACT_GROUP: - return 0; +/* Copies the last ofpact whose type is 'filter' from 'in' to 'out'. */ +static bool +ofpacts_copy_last(struct ofpbuf *out, const struct ofpbuf *in, + enum ofpact_type filter) +{ + const struct ofpact *target; + const struct ofpact *a; - default: - NOT_REACHED(); + target = NULL; + OFPACT_FOR_EACH (a, in->data, in->size) { + if (a->type == filter) { + target = a; + } + } + if (target) { + ofpact_copy(out, target); } + return target != NULL; +} + +/* Append all ofpacts, for which 'filter' returns true, from 'in' to 'out'. + * The order of appended ofpacts is preserved between 'in' and 'out' */ +static void +ofpacts_copy_all(struct ofpbuf *out, const struct ofpbuf *in, + bool (*filter)(const struct ofpact *)) +{ + const struct ofpact *a; - inconsistent: - if (enforce_consistency) { - return OFPERR_OFPBAC_MATCH_INCONSISTENT; + OFPACT_FOR_EACH (a, in->data, in->size) { + if (filter(a)) { + ofpact_copy(out, a); + } } - return 0; } -/* Checks that the 'ofpacts_len' bytes of actions in 'ofpacts' are - * appropriate for a packet with the prerequisites satisfied by 'flow' in a - * switch with no more than 'max_ports' ports. +/* Reads 'action_set', which contains ofpacts accumulated by + * OFPACT_WRITE_ACTIONS instructions, and writes equivalent actions to be + * executed directly into 'action_list'. (These names correspond to the + * "Action Set" and "Action List" terms used in OpenFlow 1.1+.) * - * May annotate ofpacts with information gathered from the 'flow'. + * In general this involves appending the last instance of each action that is + * admissible in the action set in the order described in the OpenFlow + * specification. * - * May temporarily modify 'flow', but restores the changes before returning. */ -enum ofperr -ofpacts_check(struct ofpact ofpacts[], size_t ofpacts_len, - struct flow *flow, ofp_port_t max_ports, uint8_t table_id, - bool enforce_consistency) + * Exceptions: + * + output action is only appended if no group action was present in 'in'. + * + As a simplification all set actions are copied in the order the are + * provided in 'in' as many set actions applied to a field has the same + * affect as only applying the last action that sets a field and + * duplicates are removed by do_xlate_actions(). + * This has an unwanted side-effect of compsoting multiple + * LOAD_REG actions that touch different regions of the same field. */ +void +ofpacts_execute_action_set(struct ofpbuf *action_list, + const struct ofpbuf *action_set) { - struct ofpact *a; - ovs_be16 dl_type = flow->dl_type; - ovs_be16 vlan_tci = flow->vlan_tci; - enum ofperr error = 0; + /* The OpenFlow spec "Action Set" section specifies this order. */ + ofpacts_copy_last(action_list, action_set, OFPACT_STRIP_VLAN); + ofpacts_copy_last(action_list, action_set, OFPACT_POP_MPLS); + ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_MPLS); + ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_VLAN); + ofpacts_copy_last(action_list, action_set, OFPACT_DEC_TTL); + ofpacts_copy_last(action_list, action_set, OFPACT_DEC_MPLS_TTL); + ofpacts_copy_all(action_list, action_set, ofpact_is_set_or_move_action); + ofpacts_copy_last(action_list, action_set, OFPACT_SET_QUEUE); - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { - error = ofpact_check__(a, flow, max_ports, table_id, - enforce_consistency); - if (error) { - break; - } + /* If both OFPACT_GROUP and OFPACT_OUTPUT are present, OpenFlow says that + * we should execute only OFPACT_GROUP. + * + * If neither OFPACT_GROUP nor OFPACT_OUTPUT is present, then we can drop + * all the actions because there's no point in modifying a packet that will + * not be sent anywhere. */ + if (!ofpacts_copy_last(action_list, action_set, OFPACT_GROUP) && + !ofpacts_copy_last(action_list, action_set, OFPACT_OUTPUT) && + !ofpacts_copy_last(action_list, action_set, OFPACT_RESUBMIT)) { + ofpbuf_clear(action_list); } - /* Restore fields that may have been modified. */ - flow->dl_type = dl_type; - flow->vlan_tci = vlan_tci; - return error; } -/* Verifies that the 'ofpacts_len' bytes of actions in 'ofpacts' are - * in the appropriate order as defined by the OpenFlow spec. */ -enum ofperr -ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len) + +static enum ofperr +ofpacts_decode_for_action_set(const struct ofp_action_header *in, + size_t n_in, enum ofp_version version, + struct ofpbuf *out) { - const struct ofpact *a; - enum ovs_instruction_type inst; + enum ofperr error; + struct ofpact *a; + size_t start = out->size; - inst = OVSINST_OFPIT11_APPLY_ACTIONS; - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { - enum ovs_instruction_type next; + error = ofpacts_decode(in, n_in, version, out); - next = ovs_instruction_type_from_ofpact_type(a->type); - if (inst == OVSINST_OFPIT11_APPLY_ACTIONS - ? next < inst - : next <= inst) { - const char *name = ovs_instruction_name_from_type(inst); - const char *next_name = ovs_instruction_name_from_type(next); + if (error) { + return error; + } - if (next == inst) { - VLOG_WARN("duplicate %s instruction not allowed, for OpenFlow " - "1.1+ compatibility", name); - } else { - VLOG_WARN("invalid instruction ordering: %s must appear " - "before %s, for OpenFlow 1.1+ compatibility", - next_name, name); - } - return OFPERR_OFPBAC_UNSUPPORTED_ORDER; + OFPACT_FOR_EACH (a, ofpact_end(out->data, start), out->size - start) { + if (!ofpact_is_allowed_in_actions_set(a)) { + VLOG_WARN_RL(&rl, "disallowed action in action set"); + return OFPERR_OFPBAC_BAD_TYPE; } - - inst = next; } return 0; } -/* Converting ofpacts to Nicira OpenFlow extensions. */ +/* OpenFlow 1.1 instructions. */ -static void -ofpact_output_reg_to_nxast(const struct ofpact_output_reg *output_reg, - struct ofpbuf *out) -{ - struct nx_action_output_reg *naor = ofputil_put_NXAST_OUTPUT_REG(out); +struct instruction_type_info { + enum ovs_instruction_type type; + const char *name; +}; - naor->ofs_nbits = nxm_encode_ofs_nbits(output_reg->src.ofs, - output_reg->src.n_bits); - naor->src = htonl(output_reg->src.field->nxm_header); - naor->max_len = htons(output_reg->max_len); -} +static const struct instruction_type_info inst_info[] = { +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) {OVSINST_##ENUM, NAME}, +OVS_INSTRUCTIONS +#undef DEFINE_INST +}; -static void -ofpact_resubmit_to_nxast(const struct ofpact_resubmit *resubmit, - struct ofpbuf *out) +const char * +ovs_instruction_name_from_type(enum ovs_instruction_type type) { - struct nx_action_resubmit *nar; - - if (resubmit->table_id == 0xff - && resubmit->ofpact.compat != OFPUTIL_NXAST_RESUBMIT_TABLE) { - nar = ofputil_put_NXAST_RESUBMIT(out); - } else { - nar = ofputil_put_NXAST_RESUBMIT_TABLE(out); - nar->table = resubmit->table_id; - } - nar->in_port = htons(ofp_to_u16(resubmit->in_port)); + return inst_info[type].name; } -static void -ofpact_set_tunnel_to_nxast(const struct ofpact_tunnel *tunnel, - struct ofpbuf *out) +int +ovs_instruction_type_from_name(const char *name) { - uint64_t tun_id = tunnel->tun_id; - - if (tun_id <= UINT32_MAX - && tunnel->ofpact.compat != OFPUTIL_NXAST_SET_TUNNEL64) { - ofputil_put_NXAST_SET_TUNNEL(out)->tun_id = htonl(tun_id); - } else { - ofputil_put_NXAST_SET_TUNNEL64(out)->tun_id = htonll(tun_id); + const struct instruction_type_info *p; + for (p = inst_info; p < &inst_info[ARRAY_SIZE(inst_info)]; p++) { + if (!strcasecmp(name, p->name)) { + return p->type; + } } + return -1; } -static void -ofpact_write_metadata_to_nxast(const struct ofpact_metadata *om, - struct ofpbuf *out) +enum ovs_instruction_type +ovs_instruction_type_from_ofpact_type(enum ofpact_type type) { - struct nx_action_write_metadata *nawm; - - nawm = ofputil_put_NXAST_WRITE_METADATA(out); - nawm->metadata = om->metadata; - nawm->mask = om->mask; + switch (type) { + case OFPACT_METER: + return OVSINST_OFPIT13_METER; + case OFPACT_CLEAR_ACTIONS: + return OVSINST_OFPIT11_CLEAR_ACTIONS; + case OFPACT_WRITE_ACTIONS: + return OVSINST_OFPIT11_WRITE_ACTIONS; + case OFPACT_WRITE_METADATA: + return OVSINST_OFPIT11_WRITE_METADATA; + case OFPACT_GOTO_TABLE: + return OVSINST_OFPIT11_GOTO_TABLE; + case OFPACT_OUTPUT: + case OFPACT_GROUP: + case OFPACT_CONTROLLER: + case OFPACT_ENQUEUE: + case OFPACT_OUTPUT_REG: + case OFPACT_BUNDLE: + case OFPACT_SET_VLAN_VID: + case OFPACT_SET_VLAN_PCP: + case OFPACT_STRIP_VLAN: + case OFPACT_PUSH_VLAN: + case OFPACT_SET_ETH_SRC: + case OFPACT_SET_ETH_DST: + case OFPACT_SET_IPV4_SRC: + case OFPACT_SET_IPV4_DST: + case OFPACT_SET_IP_DSCP: + case OFPACT_SET_IP_ECN: + case OFPACT_SET_IP_TTL: + case OFPACT_SET_L4_SRC_PORT: + case OFPACT_SET_L4_DST_PORT: + case OFPACT_REG_MOVE: + case OFPACT_SET_FIELD: + case OFPACT_STACK_PUSH: + case OFPACT_STACK_POP: + case OFPACT_DEC_TTL: + case OFPACT_SET_MPLS_LABEL: + case OFPACT_SET_MPLS_TC: + case OFPACT_SET_MPLS_TTL: + case OFPACT_DEC_MPLS_TTL: + case OFPACT_PUSH_MPLS: + case OFPACT_POP_MPLS: + case OFPACT_SET_TUNNEL: + case OFPACT_SET_QUEUE: + case OFPACT_POP_QUEUE: + case OFPACT_FIN_TIMEOUT: + case OFPACT_RESUBMIT: + case OFPACT_LEARN: + case OFPACT_CONJUNCTION: + case OFPACT_MULTIPATH: + case OFPACT_NOTE: + case OFPACT_EXIT: + case OFPACT_UNROLL_XLATE: + case OFPACT_SAMPLE: + case OFPACT_DEBUG_RECIRC: + case OFPACT_CT: + case OFPACT_NAT: + default: + return OVSINST_OFPIT11_APPLY_ACTIONS; + } } -static void -ofpact_note_to_nxast(const struct ofpact_note *note, struct ofpbuf *out) +enum ofperr +ovs_instruction_type_from_inst_type(enum ovs_instruction_type *instruction_type, + const uint16_t inst_type) { - size_t start_ofs = out->size; - struct nx_action_note *nan; - unsigned int remainder; - unsigned int len; + switch (inst_type) { - nan = ofputil_put_NXAST_NOTE(out); - out->size -= sizeof nan->note; - - ofpbuf_put(out, note->data, note->length); +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) \ + case ENUM: \ + *instruction_type = OVSINST_##ENUM; \ + return 0; +OVS_INSTRUCTIONS +#undef DEFINE_INST - len = out->size - start_ofs; - remainder = len % OFP_ACTION_ALIGN; - if (remainder) { - ofpbuf_put_zeros(out, OFP_ACTION_ALIGN - remainder); + default: + return OFPERR_OFPBIC_UNKNOWN_INST; } - nan = ofpbuf_at(out, start_ofs, sizeof *nan); - nan->len = htons(out->size - start_ofs); } -static void -ofpact_controller_to_nxast(const struct ofpact_controller *oc, - struct ofpbuf *out) -{ - struct nx_action_controller *nac; +/* Two-way translation between OVS's internal "OVSINST_*" representation of + * instructions and the "OFPIT_*" representation used in OpenFlow. */ +struct ovsinst_map { + enum ovs_instruction_type ovsinst; /* Internal name for instruction. */ + int ofpit; /* OFPIT_* number from OpenFlow spec. */ +}; - nac = ofputil_put_NXAST_CONTROLLER(out); - nac->max_len = htons(oc->max_len); - nac->controller_id = htons(oc->controller_id); - nac->reason = oc->reason; +static const struct ovsinst_map * +get_ovsinst_map(enum ofp_version version) +{ + /* OpenFlow 1.1 and 1.2 instructions. */ + static const struct ovsinst_map of11[] = { + { OVSINST_OFPIT11_GOTO_TABLE, 1 }, + { OVSINST_OFPIT11_WRITE_METADATA, 2 }, + { OVSINST_OFPIT11_WRITE_ACTIONS, 3 }, + { OVSINST_OFPIT11_APPLY_ACTIONS, 4 }, + { OVSINST_OFPIT11_CLEAR_ACTIONS, 5 }, + { 0, -1 }, + }; + + /* OpenFlow 1.3+ instructions. */ + static const struct ovsinst_map of13[] = { + { OVSINST_OFPIT11_GOTO_TABLE, 1 }, + { OVSINST_OFPIT11_WRITE_METADATA, 2 }, + { OVSINST_OFPIT11_WRITE_ACTIONS, 3 }, + { OVSINST_OFPIT11_APPLY_ACTIONS, 4 }, + { OVSINST_OFPIT11_CLEAR_ACTIONS, 5 }, + { OVSINST_OFPIT13_METER, 6 }, + { 0, -1 }, + }; + + return version < OFP13_VERSION ? of11 : of13; } -static void -ofpact_dec_ttl_to_nxast(const struct ofpact_cnt_ids *oc_ids, - struct ofpbuf *out) +/* Converts 'ovsinst_bitmap', a bitmap whose bits correspond to OVSINST_* + * values, into a bitmap of instructions suitable for OpenFlow 'version' + * (OFP11_VERSION or later), and returns the result. */ +ovs_be32 +ovsinst_bitmap_to_openflow(uint32_t ovsinst_bitmap, enum ofp_version version) { - if (oc_ids->ofpact.compat == OFPUTIL_NXAST_DEC_TTL) { - ofputil_put_NXAST_DEC_TTL(out); - } else { - struct nx_action_cnt_ids *nac_ids = - ofputil_put_NXAST_DEC_TTL_CNT_IDS(out); - int ids_len = ROUND_UP(2 * oc_ids->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(oc_ids->n_controllers); + uint32_t ofpit_bitmap = 0; + const struct ovsinst_map *x; - ids = ofpbuf_put_zeros(out, ids_len); - for (i = 0; i < oc_ids->n_controllers; i++) { - ids[i] = htons(oc_ids->cnt_ids[i]); + for (x = get_ovsinst_map(version); x->ofpit >= 0; x++) { + if (ovsinst_bitmap & (1u << x->ovsinst)) { + ofpit_bitmap |= 1u << x->ofpit; } } + return htonl(ofpit_bitmap); } -static void -ofpact_fin_timeout_to_nxast(const struct ofpact_fin_timeout *fin_timeout, - struct ofpbuf *out) +/* Converts 'ofpit_bitmap', a bitmap of instructions from an OpenFlow message + * with the given 'version' (OFP11_VERSION or later) into a bitmap whose bits + * correspond to OVSINST_* values, and returns the result. */ +uint32_t +ovsinst_bitmap_from_openflow(ovs_be32 ofpit_bitmap, enum ofp_version version) { - struct nx_action_fin_timeout *naft = ofputil_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); + uint32_t ovsinst_bitmap = 0; + const struct ovsinst_map *x; + + for (x = get_ovsinst_map(version); x->ofpit >= 0; x++) { + if (ofpit_bitmap & htonl(1u << x->ofpit)) { + ovsinst_bitmap |= 1u << x->ovsinst; + } + } + return ovsinst_bitmap; } -static void -ofpact_sample_to_nxast(const struct ofpact_sample *os, - struct ofpbuf *out) +static inline struct ofp11_instruction * +instruction_next(const struct ofp11_instruction *inst) { - struct nx_action_sample *nas; - - nas = ofputil_put_NXAST_SAMPLE(out); - nas->probability = htons(os->probability); - nas->collector_set_id = htonl(os->collector_set_id); - nas->obs_domain_id = htonl(os->obs_domain_id); - nas->obs_point_id = htonl(os->obs_point_id); + return ((struct ofp11_instruction *) (void *) + ((uint8_t *) inst + ntohs(inst->len))); } -static void -ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out) +static inline bool +instruction_is_valid(const struct ofp11_instruction *inst, + size_t n_instructions) { - switch (a->type) { - case OFPACT_CONTROLLER: - ofpact_controller_to_nxast(ofpact_get_CONTROLLER(a), out); - break; + uint16_t len = ntohs(inst->len); + return (!(len % OFP11_INSTRUCTION_ALIGN) + && len >= sizeof *inst + && len / sizeof *inst <= n_instructions); +} - case OFPACT_OUTPUT_REG: - ofpact_output_reg_to_nxast(ofpact_get_OUTPUT_REG(a), out); - break; +/* This macro is careful to check for instructions with bad lengths. */ +#define INSTRUCTION_FOR_EACH(ITER, LEFT, INSTRUCTIONS, N_INSTRUCTIONS) \ + for ((ITER) = (INSTRUCTIONS), (LEFT) = (N_INSTRUCTIONS); \ + (LEFT) > 0 && instruction_is_valid(ITER, LEFT); \ + ((LEFT) -= (ntohs((ITER)->len) \ + / sizeof(struct ofp11_instruction)), \ + (ITER) = instruction_next(ITER))) - case OFPACT_BUNDLE: - bundle_to_nxast(ofpact_get_BUNDLE(a), out); - break; +static enum ofperr +decode_openflow11_instruction(const struct ofp11_instruction *inst, + enum ovs_instruction_type *type) +{ + uint16_t len = ntohs(inst->len); - case OFPACT_REG_MOVE: - nxm_reg_move_to_nxast(ofpact_get_REG_MOVE(a), out); - break; + switch (inst->type) { + case CONSTANT_HTONS(OFPIT11_EXPERIMENTER): + return OFPERR_OFPBIC_BAD_EXPERIMENTER; - case OFPACT_REG_LOAD: - nxm_reg_load_to_nxast(ofpact_get_REG_LOAD(a), out); - break; +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) \ + case CONSTANT_HTONS(ENUM): \ + if (EXTENSIBLE \ + ? len >= sizeof(struct STRUCT) \ + : len == sizeof(struct STRUCT)) { \ + *type = OVSINST_##ENUM; \ + return 0; \ + } else { \ + return OFPERR_OFPBIC_BAD_LEN; \ + } +OVS_INSTRUCTIONS +#undef DEFINE_INST - case OFPACT_STACK_PUSH: - nxm_stack_push_to_nxast(ofpact_get_STACK_PUSH(a), out); - break; + default: + return OFPERR_OFPBIC_UNKNOWN_INST; + } +} - case OFPACT_STACK_POP: - nxm_stack_pop_to_nxast(ofpact_get_STACK_POP(a), out); - break; +static enum ofperr +decode_openflow11_instructions(const struct ofp11_instruction insts[], + size_t n_insts, + const struct ofp11_instruction *out[]) +{ + const struct ofp11_instruction *inst; + size_t left; - case OFPACT_DEC_TTL: - ofpact_dec_ttl_to_nxast(ofpact_get_DEC_TTL(a), out); - break; + memset(out, 0, N_OVS_INSTRUCTIONS * sizeof *out); + INSTRUCTION_FOR_EACH (inst, left, insts, n_insts) { + enum ovs_instruction_type type; + enum ofperr error; - case OFPACT_SET_MPLS_TTL: - ofputil_put_NXAST_SET_MPLS_TTL(out)->ttl - = ofpact_get_SET_MPLS_TTL(a)->ttl; - break; + error = decode_openflow11_instruction(inst, &type); + if (error) { + return error; + } - case OFPACT_DEC_MPLS_TTL: - ofputil_put_NXAST_DEC_MPLS_TTL(out); - break; + if (out[type]) { + return OFPERR_OFPBIC_DUP_INST; + } + out[type] = inst; + } - case OFPACT_SET_TUNNEL: - ofpact_set_tunnel_to_nxast(ofpact_get_SET_TUNNEL(a), out); - break; + if (left) { + VLOG_WARN_RL(&rl, "bad instruction format at offset %"PRIuSIZE, + (n_insts - left) * sizeof *inst); + return OFPERR_OFPBIC_BAD_LEN; + } + return 0; +} - case OFPACT_WRITE_METADATA: - ofpact_write_metadata_to_nxast(ofpact_get_WRITE_METADATA(a), out); - break; +static void +get_actions_from_instruction(const struct ofp11_instruction *inst, + const struct ofp_action_header **actions, + size_t *actions_len) +{ + *actions = ALIGNED_CAST(const struct ofp_action_header *, inst + 1); + *actions_len = ntohs(inst->len) - sizeof *inst; +} - case OFPACT_SET_QUEUE: - ofputil_put_NXAST_SET_QUEUE(out)->queue_id - = htonl(ofpact_get_SET_QUEUE(a)->queue_id); - break; +enum ofperr +ofpacts_pull_openflow_instructions(struct ofpbuf *openflow, + unsigned int instructions_len, + enum ofp_version version, + struct ofpbuf *ofpacts) +{ + const struct ofp11_instruction *instructions; + const struct ofp11_instruction *insts[N_OVS_INSTRUCTIONS]; + enum ofperr error; - case OFPACT_POP_QUEUE: - ofputil_put_NXAST_POP_QUEUE(out); - break; + ofpbuf_clear(ofpacts); + if (version == OFP10_VERSION) { + return ofpacts_pull_openflow_actions__(openflow, instructions_len, + version, + (1u << N_OVS_INSTRUCTIONS) - 1, + ofpacts, 0); + } - case OFPACT_FIN_TIMEOUT: - ofpact_fin_timeout_to_nxast(ofpact_get_FIN_TIMEOUT(a), out); - break; + if (instructions_len % OFP11_INSTRUCTION_ALIGN != 0) { + VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u is not a " + "multiple of %d", + instructions_len, OFP11_INSTRUCTION_ALIGN); + error = OFPERR_OFPBIC_BAD_LEN; + goto exit; + } - case OFPACT_RESUBMIT: - ofpact_resubmit_to_nxast(ofpact_get_RESUBMIT(a), out); - break; + instructions = ofpbuf_try_pull(openflow, instructions_len); + if (instructions == NULL) { + VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u exceeds " + "remaining message length (%"PRIu32")", + instructions_len, openflow->size); + error = OFPERR_OFPBIC_BAD_LEN; + goto exit; + } - case OFPACT_LEARN: - learn_to_nxast(ofpact_get_LEARN(a), out); - break; + error = decode_openflow11_instructions( + instructions, instructions_len / OFP11_INSTRUCTION_ALIGN, + insts); + if (error) { + goto exit; + } - case OFPACT_MULTIPATH: - multipath_to_nxast(ofpact_get_MULTIPATH(a), out); - break; + if (insts[OVSINST_OFPIT13_METER]) { + const struct ofp13_instruction_meter *oim; + struct ofpact_meter *om; - case OFPACT_NOTE: - ofpact_note_to_nxast(ofpact_get_NOTE(a), out); - break; + oim = ALIGNED_CAST(const struct ofp13_instruction_meter *, + insts[OVSINST_OFPIT13_METER]); - case OFPACT_EXIT: - ofputil_put_NXAST_EXIT(out); - break; + om = ofpact_put_METER(ofpacts); + om->meter_id = ntohl(oim->meter_id); + } + if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) { + const struct ofp_action_header *actions; + size_t actions_len; - case OFPACT_PUSH_MPLS: - ofputil_put_NXAST_PUSH_MPLS(out)->ethertype = - ofpact_get_PUSH_MPLS(a)->ethertype; - break; + get_actions_from_instruction(insts[OVSINST_OFPIT11_APPLY_ACTIONS], + &actions, &actions_len); + error = ofpacts_decode(actions, actions_len, version, ofpacts); + if (error) { + goto exit; + } + } + if (insts[OVSINST_OFPIT11_CLEAR_ACTIONS]) { + instruction_get_OFPIT11_CLEAR_ACTIONS( + insts[OVSINST_OFPIT11_CLEAR_ACTIONS]); + ofpact_put_CLEAR_ACTIONS(ofpacts); + } + if (insts[OVSINST_OFPIT11_WRITE_ACTIONS]) { + struct ofpact_nest *on; + const struct ofp_action_header *actions; + size_t actions_len; + size_t start = ofpacts->size; + ofpact_put(ofpacts, OFPACT_WRITE_ACTIONS, + offsetof(struct ofpact_nest, actions)); + get_actions_from_instruction(insts[OVSINST_OFPIT11_WRITE_ACTIONS], + &actions, &actions_len); + error = ofpacts_decode_for_action_set(actions, actions_len, + version, ofpacts); + if (error) { + goto exit; + } + on = ofpbuf_at_assert(ofpacts, start, sizeof *on); + on->ofpact.len = ofpacts->size - start; + } + if (insts[OVSINST_OFPIT11_WRITE_METADATA]) { + const struct ofp11_instruction_write_metadata *oiwm; + struct ofpact_metadata *om; - case OFPACT_POP_MPLS: - ofputil_put_NXAST_POP_MPLS(out)->ethertype = - ofpact_get_POP_MPLS(a)->ethertype; - break; + oiwm = ALIGNED_CAST(const struct ofp11_instruction_write_metadata *, + insts[OVSINST_OFPIT11_WRITE_METADATA]); - case OFPACT_SAMPLE: - ofpact_sample_to_nxast(ofpact_get_SAMPLE(a), out); - break; + om = ofpact_put_WRITE_METADATA(ofpacts); + om->metadata = oiwm->metadata; + om->mask = oiwm->metadata_mask; + } + if (insts[OVSINST_OFPIT11_GOTO_TABLE]) { + const struct ofp11_instruction_goto_table *oigt; + struct ofpact_goto_table *ogt; - case OFPACT_GROUP: - case OFPACT_OUTPUT: - case OFPACT_ENQUEUE: - case OFPACT_SET_VLAN_VID: - case OFPACT_SET_VLAN_PCP: - case OFPACT_STRIP_VLAN: - case OFPACT_PUSH_VLAN: - case OFPACT_SET_ETH_SRC: - case OFPACT_SET_ETH_DST: - case OFPACT_SET_IPV4_SRC: - case OFPACT_SET_IPV4_DST: - case OFPACT_SET_IP_DSCP: - case OFPACT_SET_IP_ECN: - case OFPACT_SET_IP_TTL: - case OFPACT_SET_L4_SRC_PORT: - case OFPACT_SET_L4_DST_PORT: - case OFPACT_WRITE_ACTIONS: - case OFPACT_CLEAR_ACTIONS: - case OFPACT_GOTO_TABLE: - case OFPACT_METER: - case OFPACT_SET_FIELD: - NOT_REACHED(); + oigt = instruction_get_OFPIT11_GOTO_TABLE( + insts[OVSINST_OFPIT11_GOTO_TABLE]); + ogt = ofpact_put_GOTO_TABLE(ofpacts); + ogt->table_id = oigt->table_id; + } + + error = ofpacts_verify(ofpacts->data, ofpacts->size, + (1u << N_OVS_INSTRUCTIONS) - 1, 0); +exit: + if (error) { + ofpbuf_clear(ofpacts); } + return error; } - -/* Converting ofpacts to OpenFlow 1.0. */ +/* Update the length of the instruction that begins at offset 'ofs' within + * 'openflow' and contains nested actions that extend to the end of 'openflow'. + * If the instruction contains no nested actions, deletes it entirely. */ static void -ofpact_output_to_openflow10(const struct ofpact_output *output, - struct ofpbuf *out) +ofpacts_update_instruction_actions(struct ofpbuf *openflow, size_t ofs) { - struct ofp10_action_output *oao; + struct ofp11_instruction_actions *oia; - oao = ofputil_put_OFPAT10_OUTPUT(out); - oao->port = htons(ofp_to_u16(output->port)); - oao->max_len = htons(output->max_len); + oia = ofpbuf_at_assert(openflow, ofs, sizeof *oia); + if (openflow->size > ofs + sizeof *oia) { + oia->len = htons(openflow->size - ofs); + } else { + openflow->size = ofs; + } } - -static void -ofpact_enqueue_to_openflow10(const struct ofpact_enqueue *enqueue, - struct ofpbuf *out) + +/* Checks that 'port' is a valid output port for OFPACT_OUTPUT, given that the + * switch will never have more than 'max_ports' ports. Returns 0 if 'port' is + * valid, otherwise an OpenFlow error code. */ +enum ofperr +ofpact_check_output_port(ofp_port_t port, ofp_port_t max_ports) { - struct ofp10_action_enqueue *oae; + switch (port) { + case OFPP_IN_PORT: + case OFPP_TABLE: + case OFPP_NORMAL: + case OFPP_FLOOD: + case OFPP_ALL: + case OFPP_CONTROLLER: + case OFPP_LOCAL: + return 0; + + case OFPP_NONE: + return OFPERR_OFPBAC_BAD_OUT_PORT; - oae = ofputil_put_OFPAT10_ENQUEUE(out); - oae->port = htons(ofp_to_u16(enqueue->port)); - oae->queue_id = htonl(enqueue->queue); + default: + if (ofp_to_u16(port) < ofp_to_u16(max_ports)) { + return 0; + } + return OFPERR_OFPBAC_BAD_OUT_PORT; + } } +/* Removes the protocols that require consistency between match and actions + * (that's everything but OpenFlow 1.0) from '*usable_protocols'. + * + * (An example of an inconsistency between match and actions is a flow that + * does not match on an MPLS Ethertype but has an action that pops an MPLS + * label.) */ static void -ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out) +inconsistent_match(enum ofputil_protocol *usable_protocols) +{ + *usable_protocols &= OFPUTIL_P_OF10_ANY; +} + +/* May modify flow->dl_type, flow->nw_proto and flow->vlan_tci, + * caller must restore them. + * + * Modifies some actions, filling in fields that could not be properly set + * without context. */ +static enum ofperr +ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a, + struct flow *flow, ofp_port_t max_ports, + uint8_t table_id, uint8_t n_tables) { + const struct ofpact_enqueue *enqueue; + const struct mf_field *mf; + switch (a->type) { case OFPACT_OUTPUT: - ofpact_output_to_openflow10(ofpact_get_OUTPUT(a), out); - break; + return ofpact_check_output_port(ofpact_get_OUTPUT(a)->port, + max_ports); + + case OFPACT_CONTROLLER: + return 0; case OFPACT_ENQUEUE: - ofpact_enqueue_to_openflow10(ofpact_get_ENQUEUE(a), out); - break; + enqueue = ofpact_get_ENQUEUE(a); + if (ofp_to_u16(enqueue->port) >= ofp_to_u16(max_ports) + && enqueue->port != OFPP_IN_PORT + && enqueue->port != OFPP_LOCAL) { + return OFPERR_OFPBAC_BAD_OUT_PORT; + } + return 0; + + case OFPACT_OUTPUT_REG: + return mf_check_src(&ofpact_get_OUTPUT_REG(a)->src, flow); + + case OFPACT_BUNDLE: + return bundle_check(ofpact_get_BUNDLE(a), max_ports, flow); case OFPACT_SET_VLAN_VID: - ofputil_put_OFPAT10_SET_VLAN_VID(out)->vlan_vid - = htons(ofpact_get_SET_VLAN_VID(a)->vlan_vid); - break; + /* Remember if we saw a vlan tag in the flow to aid translating to + * OpenFlow 1.1+ if need be. */ + ofpact_get_SET_VLAN_VID(a)->flow_has_vlan = + (flow->vlan_tci & htons(VLAN_CFI)) == htons(VLAN_CFI); + if (!(flow->vlan_tci & htons(VLAN_CFI)) && + !ofpact_get_SET_VLAN_VID(a)->push_vlan_if_needed) { + inconsistent_match(usable_protocols); + } + /* Temporary mark that we have a vlan tag. */ + flow->vlan_tci |= htons(VLAN_CFI); + return 0; case OFPACT_SET_VLAN_PCP: - ofputil_put_OFPAT10_SET_VLAN_PCP(out)->vlan_pcp - = ofpact_get_SET_VLAN_PCP(a)->vlan_pcp; - break; + /* Remember if we saw a vlan tag in the flow to aid translating to + * OpenFlow 1.1+ if need be. */ + ofpact_get_SET_VLAN_PCP(a)->flow_has_vlan = + (flow->vlan_tci & htons(VLAN_CFI)) == htons(VLAN_CFI); + if (!(flow->vlan_tci & htons(VLAN_CFI)) && + !ofpact_get_SET_VLAN_PCP(a)->push_vlan_if_needed) { + inconsistent_match(usable_protocols); + } + /* Temporary mark that we have a vlan tag. */ + flow->vlan_tci |= htons(VLAN_CFI); + return 0; case OFPACT_STRIP_VLAN: - ofputil_put_OFPAT10_STRIP_VLAN(out); - break; + if (!(flow->vlan_tci & htons(VLAN_CFI))) { + inconsistent_match(usable_protocols); + } + /* Temporary mark that we have no vlan tag. */ + flow->vlan_tci = htons(0); + return 0; - case OFPACT_SET_ETH_SRC: - memcpy(ofputil_put_OFPAT10_SET_DL_SRC(out)->dl_addr, - ofpact_get_SET_ETH_SRC(a)->mac, ETH_ADDR_LEN); - break; + case OFPACT_PUSH_VLAN: + if (flow->vlan_tci & htons(VLAN_CFI)) { + /* Multiple VLAN headers not supported. */ + return OFPERR_OFPBAC_BAD_TAG; + } + /* Temporary mark that we have a vlan tag. */ + flow->vlan_tci |= htons(VLAN_CFI); + return 0; + case OFPACT_SET_ETH_SRC: case OFPACT_SET_ETH_DST: - memcpy(ofputil_put_OFPAT10_SET_DL_DST(out)->dl_addr, - ofpact_get_SET_ETH_DST(a)->mac, ETH_ADDR_LEN); - break; + return 0; case OFPACT_SET_IPV4_SRC: - ofputil_put_OFPAT10_SET_NW_SRC(out)->nw_addr - = ofpact_get_SET_IPV4_SRC(a)->ipv4; - break; - case OFPACT_SET_IPV4_DST: - ofputil_put_OFPAT10_SET_NW_DST(out)->nw_addr - = ofpact_get_SET_IPV4_DST(a)->ipv4; - break; + if (flow->dl_type != htons(ETH_TYPE_IP)) { + inconsistent_match(usable_protocols); + } + return 0; case OFPACT_SET_IP_DSCP: - ofputil_put_OFPAT10_SET_NW_TOS(out)->nw_tos - = ofpact_get_SET_IP_DSCP(a)->dscp; - break; + case OFPACT_SET_IP_ECN: + case OFPACT_SET_IP_TTL: + case OFPACT_DEC_TTL: + if (!is_ip_any(flow)) { + inconsistent_match(usable_protocols); + } + return 0; case OFPACT_SET_L4_SRC_PORT: - ofputil_put_OFPAT10_SET_TP_SRC(out)->tp_port - = htons(ofpact_get_SET_L4_SRC_PORT(a)->port); - break; - case OFPACT_SET_L4_DST_PORT: - ofputil_put_OFPAT10_SET_TP_DST(out)->tp_port - = htons(ofpact_get_SET_L4_DST_PORT(a)->port); - break; - - case OFPACT_PUSH_VLAN: - /* PUSH is a side effect of a SET_VLAN_VID/PCP, which should - * follow this action. */ - break; - - case OFPACT_CLEAR_ACTIONS: - case OFPACT_WRITE_ACTIONS: - case OFPACT_GOTO_TABLE: - case OFPACT_METER: - /* XXX */ - break; + if (!is_ip_any(flow) || (flow->nw_frag & FLOW_NW_FRAG_LATER) || + (flow->nw_proto != IPPROTO_TCP && flow->nw_proto != IPPROTO_UDP + && flow->nw_proto != IPPROTO_SCTP)) { + inconsistent_match(usable_protocols); + } + /* Note on which transport protocol the port numbers are set. + * This allows this set action to be converted to an OF1.2 set field + * action. */ + if (a->type == OFPACT_SET_L4_SRC_PORT) { + ofpact_get_SET_L4_SRC_PORT(a)->flow_ip_proto = flow->nw_proto; + } else { + ofpact_get_SET_L4_DST_PORT(a)->flow_ip_proto = flow->nw_proto; + } + return 0; - case OFPACT_GROUP: - break; + case OFPACT_REG_MOVE: + return nxm_reg_move_check(ofpact_get_REG_MOVE(a), flow); case OFPACT_SET_FIELD: - set_field_to_openflow(ofpact_get_SET_FIELD(a), out); - break; + mf = ofpact_get_SET_FIELD(a)->field; + /* Require OXM_OF_VLAN_VID to have an existing VLAN header. */ + if (!mf_are_prereqs_ok(mf, flow) || + (mf->id == MFF_VLAN_VID && !(flow->vlan_tci & htons(VLAN_CFI)))) { + VLOG_WARN_RL(&rl, "set_field %s lacks correct prerequisities", + mf->name); + return OFPERR_OFPBAC_MATCH_INCONSISTENT; + } + /* Remember if we saw a vlan tag in the flow to aid translating to + * OpenFlow 1.1 if need be. */ + ofpact_get_SET_FIELD(a)->flow_has_vlan = + (flow->vlan_tci & htons(VLAN_CFI)) == htons(VLAN_CFI); + if (mf->id == MFF_VLAN_TCI) { + /* The set field may add or remove the vlan tag, + * Mark the status temporarily. */ + flow->vlan_tci = ofpact_get_SET_FIELD(a)->value.be16; + } + return 0; - case OFPACT_CONTROLLER: - case OFPACT_OUTPUT_REG: - case OFPACT_BUNDLE: - case OFPACT_REG_MOVE: - case OFPACT_REG_LOAD: case OFPACT_STACK_PUSH: + return nxm_stack_push_check(ofpact_get_STACK_PUSH(a), flow); + case OFPACT_STACK_POP: - case OFPACT_DEC_TTL: - case OFPACT_SET_IP_ECN: - case OFPACT_SET_IP_TTL: + return nxm_stack_pop_check(ofpact_get_STACK_POP(a), flow); + + case OFPACT_SET_MPLS_LABEL: + case OFPACT_SET_MPLS_TC: case OFPACT_SET_MPLS_TTL: case OFPACT_DEC_MPLS_TTL: + if (!eth_type_mpls(flow->dl_type)) { + inconsistent_match(usable_protocols); + } + return 0; + case OFPACT_SET_TUNNEL: - case OFPACT_WRITE_METADATA: case OFPACT_SET_QUEUE: case OFPACT_POP_QUEUE: - case OFPACT_FIN_TIMEOUT: case OFPACT_RESUBMIT: + return 0; + + case OFPACT_FIN_TIMEOUT: + if (flow->nw_proto != IPPROTO_TCP) { + inconsistent_match(usable_protocols); + } + return 0; + case OFPACT_LEARN: + return learn_check(ofpact_get_LEARN(a), flow); + + case OFPACT_CONJUNCTION: + return 0; + case OFPACT_MULTIPATH: + return multipath_check(ofpact_get_MULTIPATH(a), flow); + case OFPACT_NOTE: case OFPACT_EXIT: + return 0; + case OFPACT_PUSH_MPLS: + flow->dl_type = ofpact_get_PUSH_MPLS(a)->ethertype; + /* The packet is now MPLS and the MPLS payload is opaque. + * Thus nothing can be assumed about the network protocol. + * Temporarily mark that we have no nw_proto. */ + flow->nw_proto = 0; + return 0; + case OFPACT_POP_MPLS: + if (!eth_type_mpls(flow->dl_type)) { + inconsistent_match(usable_protocols); + } + flow->dl_type = ofpact_get_POP_MPLS(a)->ethertype; + return 0; + case OFPACT_SAMPLE: - ofpact_to_nxast(a, out); - break; + return 0; + + case OFPACT_CT: { + struct ofpact_conntrack *oc = ofpact_get_CT(a); + enum ofperr err; + + if (!dl_type_is_ip_any(flow->dl_type) + || (flow->ct_state & CS_INVALID && oc->flags & NX_CT_F_COMMIT)) { + inconsistent_match(usable_protocols); + } + + if (oc->zone_src.field) { + return mf_check_src(&oc->zone_src, flow); + } + + err = ofpacts_check(oc->actions, ofpact_ct_get_action_len(oc), + flow, max_ports, table_id, n_tables, + usable_protocols); + return err; } -} - -/* Converting ofpacts to OpenFlow 1.1. */ -static void -ofpact_output_to_openflow11(const struct ofpact_output *output, - struct ofpbuf *out) -{ - struct ofp11_action_output *oao; + case OFPACT_NAT: { + struct ofpact_nat *on = ofpact_get_NAT(a); + + if (!dl_type_is_ip_any(flow->dl_type) || + (on->range_af == AF_INET && flow->dl_type != htons(ETH_TYPE_IP)) || + (on->range_af == AF_INET6 + && flow->dl_type != htons(ETH_TYPE_IPV6))) { + inconsistent_match(usable_protocols); + } + return 0; + } + + case OFPACT_CLEAR_ACTIONS: + return 0; + + case OFPACT_WRITE_ACTIONS: { + /* Use a temporary copy of 'usable_protocols' because we can't check + * consistency of an action set. */ + struct ofpact_nest *on = ofpact_get_WRITE_ACTIONS(a); + enum ofputil_protocol p = *usable_protocols; + return ofpacts_check(on->actions, ofpact_nest_get_action_len(on), + flow, max_ports, table_id, n_tables, &p); + } + + case OFPACT_WRITE_METADATA: + return 0; + + case OFPACT_METER: { + uint32_t mid = ofpact_get_METER(a)->meter_id; + if (mid == 0 || mid > OFPM13_MAX) { + return OFPERR_OFPMMFC_INVALID_METER; + } + return 0; + } + + case OFPACT_GOTO_TABLE: { + uint8_t goto_table = ofpact_get_GOTO_TABLE(a)->table_id; + if ((table_id != 255 && goto_table <= table_id) + || (n_tables != 255 && goto_table >= n_tables)) { + return OFPERR_OFPBIC_BAD_TABLE_ID; + } + return 0; + } - oao = ofputil_put_OFPAT11_OUTPUT(out); - oao->port = ofputil_port_to_ofp11(output->port); - oao->max_len = htons(output->max_len); + case OFPACT_GROUP: + return 0; + + case OFPACT_UNROLL_XLATE: + /* UNROLL is an internal action that should never be seen via + * OpenFlow. */ + return OFPERR_OFPBAC_BAD_TYPE; + + case OFPACT_DEBUG_RECIRC: + return 0; + + default: + OVS_NOT_REACHED(); + } } -static void -ofpact_dec_ttl_to_openflow11(const struct ofpact_cnt_ids *dec_ttl, - struct ofpbuf *out) -{ - if (dec_ttl->n_controllers == 1 && dec_ttl->cnt_ids[0] == 0 - && (!dec_ttl->ofpact.compat || - dec_ttl->ofpact.compat == OFPUTIL_OFPAT11_DEC_NW_TTL)) { - ofputil_put_OFPAT11_DEC_NW_TTL(out); - } else { - ofpact_dec_ttl_to_nxast(dec_ttl, out); +/* Checks that the 'ofpacts_len' bytes of actions in 'ofpacts' are + * appropriate for a packet with the prerequisites satisfied by 'flow' in a + * switch with no more than 'max_ports' ports. + * + * If 'ofpacts' and 'flow' are inconsistent with one another, un-sets in + * '*usable_protocols' the protocols that forbid the inconsistency. (An + * example of an inconsistency between match and actions is a flow that does + * not match on an MPLS Ethertype but has an action that pops an MPLS label.) + * + * May annotate ofpacts with information gathered from the 'flow'. + * + * May temporarily modify 'flow', but restores the changes before returning. */ +enum ofperr +ofpacts_check(struct ofpact ofpacts[], size_t ofpacts_len, + struct flow *flow, ofp_port_t max_ports, + uint8_t table_id, uint8_t n_tables, + enum ofputil_protocol *usable_protocols) +{ + struct ofpact *a; + ovs_be16 dl_type = flow->dl_type; + ovs_be16 vlan_tci = flow->vlan_tci; + uint8_t nw_proto = flow->nw_proto; + enum ofperr error = 0; + + OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + error = ofpact_check__(usable_protocols, a, flow, + max_ports, table_id, n_tables); + if (error) { + break; + } } + /* Restore fields that may have been modified. */ + flow->dl_type = dl_type; + flow->vlan_tci = vlan_tci; + flow->nw_proto = nw_proto; + return error; } -static void -ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out) +/* Like ofpacts_check(), but reports inconsistencies as + * OFPERR_OFPBAC_MATCH_INCONSISTENT rather than clearing bits. */ +enum ofperr +ofpacts_check_consistency(struct ofpact ofpacts[], size_t ofpacts_len, + struct flow *flow, ofp_port_t max_ports, + uint8_t table_id, uint8_t n_tables, + enum ofputil_protocol usable_protocols) { - switch (a->type) { - case OFPACT_OUTPUT: - return ofpact_output_to_openflow11(ofpact_get_OUTPUT(a), out); - - case OFPACT_ENQUEUE: - /* XXX */ - break; - - case OFPACT_SET_VLAN_VID: - /* Push a VLAN tag, if one was not seen at action validation time. */ - if (!ofpact_get_SET_VLAN_VID(a)->flow_has_vlan - && ofpact_get_SET_VLAN_VID(a)->push_vlan_if_needed) { - ofputil_put_OFPAT11_PUSH_VLAN(out)->ethertype - = htons(ETH_TYPE_VLAN_8021Q); - } - ofputil_put_OFPAT11_SET_VLAN_VID(out)->vlan_vid - = htons(ofpact_get_SET_VLAN_VID(a)->vlan_vid); - break; - - case OFPACT_SET_VLAN_PCP: - /* Push a VLAN tag, if one was not seen at action validation time. */ - if (!ofpact_get_SET_VLAN_PCP(a)->flow_has_vlan - && ofpact_get_SET_VLAN_PCP(a)->push_vlan_if_needed) { - ofputil_put_OFPAT11_PUSH_VLAN(out)->ethertype - = htons(ETH_TYPE_VLAN_8021Q); - } - ofputil_put_OFPAT11_SET_VLAN_PCP(out)->vlan_pcp - = ofpact_get_SET_VLAN_PCP(a)->vlan_pcp; - break; - - case OFPACT_STRIP_VLAN: - ofputil_put_OFPAT11_POP_VLAN(out); - break; - - case OFPACT_PUSH_VLAN: - /* XXX ETH_TYPE_VLAN_8021AD case */ - ofputil_put_OFPAT11_PUSH_VLAN(out)->ethertype = - htons(ETH_TYPE_VLAN_8021Q); - break; - - case OFPACT_SET_QUEUE: - ofputil_put_OFPAT11_SET_QUEUE(out)->queue_id - = htonl(ofpact_get_SET_QUEUE(a)->queue_id); - break; - - case OFPACT_SET_ETH_SRC: - memcpy(ofputil_put_OFPAT11_SET_DL_SRC(out)->dl_addr, - ofpact_get_SET_ETH_SRC(a)->mac, ETH_ADDR_LEN); - break; + enum ofputil_protocol p = usable_protocols; + enum ofperr error; - case OFPACT_SET_ETH_DST: - memcpy(ofputil_put_OFPAT11_SET_DL_DST(out)->dl_addr, - ofpact_get_SET_ETH_DST(a)->mac, ETH_ADDR_LEN); - break; + error = ofpacts_check(ofpacts, ofpacts_len, flow, max_ports, + table_id, n_tables, &p); + return (error ? error + : p != usable_protocols ? OFPERR_OFPBAC_MATCH_INCONSISTENT + : 0); +} - case OFPACT_SET_IPV4_SRC: - ofputil_put_OFPAT11_SET_NW_SRC(out)->nw_addr - = ofpact_get_SET_IPV4_SRC(a)->ipv4; - break; +/* Returns the destination field that 'ofpact' would write to, or NULL + * if the action would not write to an mf_field. */ +const struct mf_field * +ofpact_get_mf_dst(const struct ofpact *ofpact) +{ + if (ofpact->type == OFPACT_SET_FIELD) { + const struct ofpact_set_field *orl; - case OFPACT_SET_IPV4_DST: - ofputil_put_OFPAT11_SET_NW_DST(out)->nw_addr - = ofpact_get_SET_IPV4_DST(a)->ipv4; - break; + orl = CONTAINER_OF(ofpact, struct ofpact_set_field, ofpact); + return orl->field; + } else if (ofpact->type == OFPACT_REG_MOVE) { + const struct ofpact_reg_move *orm; - case OFPACT_SET_IP_DSCP: - ofputil_put_OFPAT11_SET_NW_TOS(out)->nw_tos - = ofpact_get_SET_IP_DSCP(a)->dscp; - break; + orm = CONTAINER_OF(ofpact, struct ofpact_reg_move, ofpact); + return orm->dst.field; + } - case OFPACT_SET_IP_ECN: - ofputil_put_OFPAT11_SET_NW_ECN(out)->nw_ecn - = ofpact_get_SET_IP_ECN(a)->ecn; - break; + return NULL; +} - case OFPACT_SET_IP_TTL: - ofputil_put_OFPAT11_SET_NW_TTL(out)->nw_ttl - = ofpact_get_SET_IP_TTL(a)->ttl; - break; +static enum ofperr +unsupported_nesting(enum ofpact_type action, enum ofpact_type outer_action) +{ + VLOG_WARN("%s action doesn't support nested action %s", + ofpact_name(outer_action), ofpact_name(action)); + return OFPERR_OFPBAC_BAD_ARGUMENT; +} - case OFPACT_SET_L4_SRC_PORT: - ofputil_put_OFPAT11_SET_TP_SRC(out)->tp_port - = htons(ofpact_get_SET_L4_SRC_PORT(a)->port); - break; +static bool +field_requires_ct(enum mf_field_id field) +{ + return field == MFF_CT_MARK || field == MFF_CT_LABEL; +} - case OFPACT_SET_L4_DST_PORT: - ofputil_put_OFPAT11_SET_TP_DST(out)->tp_port - = htons(ofpact_get_SET_L4_DST_PORT(a)->port); - break; +/* Apply nesting constraints for actions */ +static enum ofperr +ofpacts_verify_nested(const struct ofpact *a, enum ofpact_type outer_action) +{ + const struct mf_field *field = ofpact_get_mf_dst(a); - case OFPACT_DEC_TTL: - ofpact_dec_ttl_to_openflow11(ofpact_get_DEC_TTL(a), out); - break; + if (field && field_requires_ct(field->id) && outer_action != OFPACT_CT) { + VLOG_WARN("cannot set CT fields outside of ct action"); + return OFPERR_OFPBAC_BAD_SET_ARGUMENT; + } + if (a->type == OFPACT_NAT) { + if (outer_action != OFPACT_CT) { + VLOG_WARN("Cannot have NAT action outside of \"ct\" action"); + return OFPERR_OFPBAC_BAD_SET_ARGUMENT; + } + return 0; + } - case OFPACT_SET_MPLS_TTL: - ofputil_put_OFPAT11_SET_MPLS_TTL(out)->mpls_ttl - = ofpact_get_SET_MPLS_TTL(a)->ttl; - break; + if (outer_action) { + ovs_assert(outer_action == OFPACT_WRITE_ACTIONS + || outer_action == OFPACT_CT); + + if (outer_action == OFPACT_CT) { + if (!field) { + return unsupported_nesting(a->type, outer_action); + } else if (!field_requires_ct(field->id)) { + VLOG_WARN("%s action doesn't support nested modification " + "of %s", ofpact_name(outer_action), field->name); + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + } + } - case OFPACT_DEC_MPLS_TTL: - ofputil_put_OFPAT11_DEC_MPLS_TTL(out); - break; + return 0; +} - case OFPACT_WRITE_METADATA: - /* OpenFlow 1.1 uses OFPIT_WRITE_METADATA to express this action. */ - break; +/* Verifies that the 'ofpacts_len' bytes of actions in 'ofpacts' are in the + * appropriate order as defined by the OpenFlow spec and as required by Open + * vSwitch. + * + * 'allowed_ovsinsts' is a bitmap of OVSINST_* values, in which 1-bits indicate + * instructions that are allowed within 'ofpacts[]'. + * + * If 'outer_action' is not zero, it specifies that the actions are nested + * within another action of type 'outer_action'. */ +static enum ofperr +ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len, + uint32_t allowed_ovsinsts, enum ofpact_type outer_action) +{ + const struct ofpact *a; + enum ovs_instruction_type inst; - case OFPACT_PUSH_MPLS: - ofputil_put_OFPAT11_PUSH_MPLS(out)->ethertype = - ofpact_get_PUSH_MPLS(a)->ethertype; - break; + inst = OVSINST_OFPIT13_METER; + OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + enum ovs_instruction_type next; + enum ofperr error; - case OFPACT_POP_MPLS: - ofputil_put_OFPAT11_POP_MPLS(out)->ethertype = - ofpact_get_POP_MPLS(a)->ethertype; + if (a->type == OFPACT_CONJUNCTION) { + OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + if (a->type != OFPACT_CONJUNCTION && a->type != OFPACT_NOTE) { + VLOG_WARN("\"conjunction\" actions may be used along with " + "\"note\" but not any other kind of action " + "(such as the \"%s\" action used here)", + ofpact_name(a->type)); + return OFPERR_NXBAC_BAD_CONJUNCTION; + } + } + return 0; + } - break; + error = ofpacts_verify_nested(a, outer_action); + if (error) { + return error; + } - case OFPACT_CLEAR_ACTIONS: - case OFPACT_WRITE_ACTIONS: - case OFPACT_GOTO_TABLE: - case OFPACT_METER: - NOT_REACHED(); + next = ovs_instruction_type_from_ofpact_type(a->type); + if (a > ofpacts + && (inst == OVSINST_OFPIT11_APPLY_ACTIONS + ? next < inst + : next <= inst)) { + const char *name = ovs_instruction_name_from_type(inst); + const char *next_name = ovs_instruction_name_from_type(next); - case OFPACT_GROUP: - ofputil_put_OFPAT11_GROUP(out)->group_id = - htonl(ofpact_get_GROUP(a)->group_id); - break; + if (next == inst) { + VLOG_WARN("duplicate %s instruction not allowed, for OpenFlow " + "1.1+ compatibility", name); + } else { + VLOG_WARN("invalid instruction ordering: %s must appear " + "before %s, for OpenFlow 1.1+ compatibility", + next_name, name); + } + return OFPERR_OFPBAC_UNSUPPORTED_ORDER; + } + if (!((1u << next) & allowed_ovsinsts)) { + const char *name = ovs_instruction_name_from_type(next); - case OFPACT_SET_FIELD: - set_field_to_openflow(ofpact_get_SET_FIELD(a), out); - break; + VLOG_WARN("%s instruction not allowed here", name); + return OFPERR_OFPBIC_UNSUP_INST; + } - case OFPACT_CONTROLLER: - case OFPACT_OUTPUT_REG: - case OFPACT_BUNDLE: - case OFPACT_REG_MOVE: - case OFPACT_REG_LOAD: - case OFPACT_STACK_PUSH: - case OFPACT_STACK_POP: - case OFPACT_SET_TUNNEL: - case OFPACT_POP_QUEUE: - case OFPACT_FIN_TIMEOUT: - case OFPACT_RESUBMIT: - case OFPACT_LEARN: - case OFPACT_MULTIPATH: - case OFPACT_NOTE: - case OFPACT_EXIT: - case OFPACT_SAMPLE: - ofpact_to_nxast(a, out); - break; + inst = next; } + + return 0; } + +/* Converting ofpacts to OpenFlow. */ static void -ofpact_to_openflow12(const struct ofpact *a, struct ofpbuf *out) +encode_ofpact(const struct ofpact *a, enum ofp_version ofp_version, + struct ofpbuf *out) { - ofpact_to_openflow11(a, out); + switch (a->type) { +#define OFPACT(ENUM, STRUCT, MEMBER, NAME) \ + case OFPACT_##ENUM: \ + encode_##ENUM(ofpact_get_##ENUM(a), ofp_version, out); \ + return; + OFPACTS +#undef OFPACT + default: + OVS_NOT_REACHED(); + } } /* Converts the 'ofpacts_len' bytes of ofpacts in 'ofpacts' into OpenFlow @@ -2673,29 +7008,17 @@ ofpacts_put_openflow_actions(const struct ofpact ofpacts[], size_t ofpacts_len, const struct ofpact *a; size_t start_size = openflow->size; - void (*translate)(const struct ofpact *a, struct ofpbuf *out) = - (ofp_version == OFP10_VERSION) ? ofpact_to_openflow10 : - (ofp_version == OFP11_VERSION) ? ofpact_to_openflow11 : - ofpact_to_openflow12; - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { - translate(a, openflow); + encode_ofpact(a, ofp_version, openflow); } return openflow->size - start_size; } -static void -ofpacts_update_instruction_actions(struct ofpbuf *openflow, size_t ofs) +static enum ovs_instruction_type +ofpact_is_apply_actions(const struct ofpact *a) { - struct ofp11_instruction_actions *oia; - - /* Update the instruction's length (or, if it's empty, delete it). */ - oia = ofpbuf_at_assert(openflow, ofs, sizeof *oia); - if (openflow->size > ofs + sizeof *oia) { - oia->len = htons(openflow->size - ofs); - } else { - openflow->size = ofs; - } + return (ovs_instruction_type_from_ofpact_type(a->type) + == OVSINST_OFPIT11_APPLY_ACTIONS); } void @@ -2704,85 +7027,179 @@ ofpacts_put_openflow_instructions(const struct ofpact ofpacts[], struct ofpbuf *openflow, enum ofp_version ofp_version) { + const struct ofpact *end = ofpact_end(ofpacts, ofpacts_len); const struct ofpact *a; - ovs_assert(ofp_version >= OFP11_VERSION); - - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { - switch (ovs_instruction_type_from_ofpact_type(a->type)) { - case OVSINST_OFPIT11_CLEAR_ACTIONS: - instruction_put_OFPIT11_CLEAR_ACTIONS(openflow); - break; - - case OVSINST_OFPIT11_GOTO_TABLE: { - struct ofp11_instruction_goto_table *oigt; - oigt = instruction_put_OFPIT11_GOTO_TABLE(openflow); - oigt->table_id = ofpact_get_GOTO_TABLE(a)->table_id; - memset(oigt->pad, 0, sizeof oigt->pad); - break; - } + if (ofp_version == OFP10_VERSION) { + ofpacts_put_openflow_actions(ofpacts, ofpacts_len, openflow, + ofp_version); + return; + } - case OVSINST_OFPIT11_WRITE_METADATA: { - const struct ofpact_metadata *om; - struct ofp11_instruction_write_metadata *oiwm; + a = ofpacts; + while (a < end) { + if (ofpact_is_apply_actions(a)) { + size_t ofs = openflow->size; - om = ofpact_get_WRITE_METADATA(a); - oiwm = instruction_put_OFPIT11_WRITE_METADATA(openflow); - oiwm->metadata = om->metadata; - oiwm->metadata_mask = om->mask; - break; + instruction_put_OFPIT11_APPLY_ACTIONS(openflow); + do { + encode_ofpact(a, ofp_version, openflow); + a = ofpact_next(a); + } while (a < end && ofpact_is_apply_actions(a)); + ofpacts_update_instruction_actions(openflow, ofs); + } else { + encode_ofpact(a, ofp_version, openflow); + a = ofpact_next(a); } + } +} + +/* Sets of supported actions. */ + +/* Two-way translation between OVS's internal "OFPACT_*" representation of + * actions and the "OFPAT_*" representation used in some OpenFlow version. + * (OFPAT_* numbering varies from one OpenFlow version to another, so a given + * instance is specific to one OpenFlow version.) */ +struct ofpact_map { + enum ofpact_type ofpact; /* Internal name for action type. */ + int ofpat; /* OFPAT_* number from OpenFlow spec. */ +}; - case OVSINST_OFPIT13_METER: - if (ofp_version >= OFP13_VERSION) { - const struct ofpact_meter *om; - struct ofp13_instruction_meter *oim; - - om = ofpact_get_METER(a); - oim = instruction_put_OFPIT13_METER(openflow); - oim->meter_id = htonl(om->meter_id); - } - break; +static const struct ofpact_map * +get_ofpact_map(enum ofp_version version) +{ + /* OpenFlow 1.0 actions. */ + static const struct ofpact_map of10[] = { + { OFPACT_OUTPUT, 0 }, + { OFPACT_SET_VLAN_VID, 1 }, + { OFPACT_SET_VLAN_PCP, 2 }, + { OFPACT_STRIP_VLAN, 3 }, + { OFPACT_SET_ETH_SRC, 4 }, + { OFPACT_SET_ETH_DST, 5 }, + { OFPACT_SET_IPV4_SRC, 6 }, + { OFPACT_SET_IPV4_DST, 7 }, + { OFPACT_SET_IP_DSCP, 8 }, + { OFPACT_SET_L4_SRC_PORT, 9 }, + { OFPACT_SET_L4_DST_PORT, 10 }, + { OFPACT_ENQUEUE, 11 }, + { 0, -1 }, + }; + + /* OpenFlow 1.1 actions. */ + static const struct ofpact_map of11[] = { + { OFPACT_OUTPUT, 0 }, + { OFPACT_SET_VLAN_VID, 1 }, + { OFPACT_SET_VLAN_PCP, 2 }, + { OFPACT_SET_ETH_SRC, 3 }, + { OFPACT_SET_ETH_DST, 4 }, + { OFPACT_SET_IPV4_SRC, 5 }, + { OFPACT_SET_IPV4_DST, 6 }, + { OFPACT_SET_IP_DSCP, 7 }, + { OFPACT_SET_IP_ECN, 8 }, + { OFPACT_SET_L4_SRC_PORT, 9 }, + { OFPACT_SET_L4_DST_PORT, 10 }, + /* OFPAT_COPY_TTL_OUT (11) not supported. */ + /* OFPAT_COPY_TTL_IN (12) not supported. */ + { OFPACT_SET_MPLS_LABEL, 13 }, + { OFPACT_SET_MPLS_TC, 14 }, + { OFPACT_SET_MPLS_TTL, 15 }, + { OFPACT_DEC_MPLS_TTL, 16 }, + { OFPACT_PUSH_VLAN, 17 }, + { OFPACT_STRIP_VLAN, 18 }, + { OFPACT_PUSH_MPLS, 19 }, + { OFPACT_POP_MPLS, 20 }, + { OFPACT_SET_QUEUE, 21 }, + { OFPACT_GROUP, 22 }, + { OFPACT_SET_IP_TTL, 23 }, + { OFPACT_DEC_TTL, 24 }, + { 0, -1 }, + }; + + /* OpenFlow 1.2, 1.3, and 1.4 actions. */ + static const struct ofpact_map of12[] = { + { OFPACT_OUTPUT, 0 }, + /* OFPAT_COPY_TTL_OUT (11) not supported. */ + /* OFPAT_COPY_TTL_IN (12) not supported. */ + { OFPACT_SET_MPLS_TTL, 15 }, + { OFPACT_DEC_MPLS_TTL, 16 }, + { OFPACT_PUSH_VLAN, 17 }, + { OFPACT_STRIP_VLAN, 18 }, + { OFPACT_PUSH_MPLS, 19 }, + { OFPACT_POP_MPLS, 20 }, + { OFPACT_SET_QUEUE, 21 }, + { OFPACT_GROUP, 22 }, + { OFPACT_SET_IP_TTL, 23 }, + { OFPACT_DEC_TTL, 24 }, + { OFPACT_SET_FIELD, 25 }, + /* OF1.3+ OFPAT_PUSH_PBB (26) not supported. */ + /* OF1.3+ OFPAT_POP_PBB (27) not supported. */ + { 0, -1 }, + }; + + switch (version) { + case OFP10_VERSION: + return of10; + + case OFP11_VERSION: + return of11; + + case OFP12_VERSION: + case OFP13_VERSION: + case OFP14_VERSION: + case OFP15_VERSION: + default: + return of12; + } +} - case OVSINST_OFPIT11_APPLY_ACTIONS: { - const size_t ofs = openflow->size; - const size_t ofpacts_len_left = - (uint8_t*)ofpact_end(ofpacts, ofpacts_len) - (uint8_t*)a; - const struct ofpact *action; - const struct ofpact *processed = a; +/* Converts 'ofpacts_bitmap', a bitmap whose bits correspond to OFPACT_* + * values, into a bitmap of actions suitable for OpenFlow 'version', and + * returns the result. */ +ovs_be32 +ofpact_bitmap_to_openflow(uint64_t ofpacts_bitmap, enum ofp_version version) +{ + uint32_t openflow_bitmap = 0; + const struct ofpact_map *x; - instruction_put_OFPIT11_APPLY_ACTIONS(openflow); - OFPACT_FOR_EACH(action, a, ofpacts_len_left) { - if (ovs_instruction_type_from_ofpact_type(action->type) - != OVSINST_OFPIT11_APPLY_ACTIONS) { - break; - } - if (ofp_version == OFP11_VERSION) { - ofpact_to_openflow11(action, openflow); - } else { - ofpact_to_openflow12(action, openflow); - } - processed = action; - } - ofpacts_update_instruction_actions(openflow, ofs); - a = processed; - break; + for (x = get_ofpact_map(version); x->ofpat >= 0; x++) { + if (ofpacts_bitmap & (UINT64_C(1) << x->ofpact)) { + openflow_bitmap |= 1u << x->ofpat; } + } + return htonl(openflow_bitmap); +} - case OVSINST_OFPIT11_WRITE_ACTIONS: { - const size_t ofs = openflow->size; - const struct ofpact_nest *on; - - on = ofpact_get_WRITE_ACTIONS(a); - instruction_put_OFPIT11_WRITE_ACTIONS(openflow); - ofpacts_put_openflow_actions(on->actions, - ofpact_nest_get_action_len(on), - openflow, ofp_version); - ofpacts_update_instruction_actions(openflow, ofs); +/* Converts 'ofpat_bitmap', a bitmap of actions from an OpenFlow message with + * the given 'version' into a bitmap whose bits correspond to OFPACT_* values, + * and returns the result. */ +uint64_t +ofpact_bitmap_from_openflow(ovs_be32 ofpat_bitmap, enum ofp_version version) +{ + uint64_t ofpact_bitmap = 0; + const struct ofpact_map *x; - break; + for (x = get_ofpact_map(version); x->ofpat >= 0; x++) { + if (ofpat_bitmap & htonl(1u << x->ofpat)) { + ofpact_bitmap |= UINT64_C(1) << x->ofpact; } + } + return ofpact_bitmap; +} + +/* Appends to 's' a string representation of the set of OFPACT_* represented + * by 'ofpacts_bitmap'. */ +void +ofpact_bitmap_format(uint64_t ofpacts_bitmap, struct ds *s) +{ + if (!ofpacts_bitmap) { + ds_put_cstr(s, ""); + } else { + while (ofpacts_bitmap) { + ds_put_format(s, "%s ", + ofpact_name(rightmost_1bit_idx(ofpacts_bitmap))); + ofpacts_bitmap = zero_rightmost_1bit(ofpacts_bitmap); } + ds_chomp(s, ' '); } } @@ -2814,11 +7231,12 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) case OFPACT_SET_L4_SRC_PORT: case OFPACT_SET_L4_DST_PORT: case OFPACT_REG_MOVE: - case OFPACT_REG_LOAD: case OFPACT_SET_FIELD: case OFPACT_STACK_PUSH: case OFPACT_STACK_POP: case OFPACT_DEC_TTL: + case OFPACT_SET_MPLS_LABEL: + case OFPACT_SET_MPLS_TC: case OFPACT_SET_MPLS_TTL: case OFPACT_DEC_MPLS_TTL: case OFPACT_SET_TUNNEL: @@ -2828,9 +7246,11 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) case OFPACT_FIN_TIMEOUT: case OFPACT_RESUBMIT: case OFPACT_LEARN: + case OFPACT_CONJUNCTION: case OFPACT_MULTIPATH: case OFPACT_NOTE: case OFPACT_EXIT: + case OFPACT_UNROLL_XLATE: case OFPACT_PUSH_MPLS: case OFPACT_POP_MPLS: case OFPACT_SAMPLE: @@ -2839,6 +7259,9 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) case OFPACT_GOTO_TABLE: case OFPACT_METER: case OFPACT_GROUP: + case OFPACT_DEBUG_RECIRC: + case OFPACT_CT: + case OFPACT_NAT: default: return false; } @@ -2852,7 +7275,7 @@ ofpacts_output_to_port(const struct ofpact *ofpacts, size_t ofpacts_len, { const struct ofpact *a; - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + OFPACT_FOR_EACH_FLATTENED (a, ofpacts, ofpacts_len) { if (ofpact_outputs_to_port(a, port)) { return true; } @@ -2869,7 +7292,7 @@ ofpacts_output_to_group(const struct ofpact *ofpacts, size_t ofpacts_len, { const struct ofpact *a; - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + OFPACT_FOR_EACH_FLATTENED (a, ofpacts, ofpacts_len) { if (a->type == OFPACT_GROUP && ofpact_get_GROUP(a)->group_id == group_id) { return true; @@ -2913,421 +7336,581 @@ ofpacts_get_meter(const struct ofpact ofpacts[], size_t ofpacts_len) /* Formatting ofpacts. */ static void -print_note(const struct ofpact_note *note, struct ds *string) +ofpact_format(const struct ofpact *a, struct ds *s) { - size_t i; + switch (a->type) { +#define OFPACT(ENUM, STRUCT, MEMBER, NAME) \ + case OFPACT_##ENUM: \ + format_##ENUM(ALIGNED_CAST(const struct STRUCT *, a), s); \ + break; + OFPACTS +#undef OFPACT + default: + OVS_NOT_REACHED(); + } +} - ds_put_cstr(string, "note:"); - for (i = 0; i < note->length; i++) { - if (i) { - ds_put_char(string, '.'); +/* Appends a string representing the 'ofpacts_len' bytes of ofpacts in + * 'ofpacts' to 'string'. */ +void +ofpacts_format(const struct ofpact *ofpacts, size_t ofpacts_len, + struct ds *string) +{ + if (!ofpacts_len) { + ds_put_cstr(string, "drop"); + } else { + const struct ofpact *a; + + OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + if (a != ofpacts) { + ds_put_cstr(string, ","); + } + + /* XXX write-actions */ + ofpact_format(a, string); } - ds_put_format(string, "%02"PRIx8, note->data[i]); } } + +/* Internal use by helpers. */ -static void -print_dec_ttl(const struct ofpact_cnt_ids *ids, - struct ds *s) +void * +ofpact_put(struct ofpbuf *ofpacts, enum ofpact_type type, size_t len) +{ + struct ofpact *ofpact; + + ofpacts->header = ofpbuf_put_uninit(ofpacts, len); + ofpact = ofpacts->header; + ofpact_init(ofpact, type, len); + return ofpact; +} + +void +ofpact_init(struct ofpact *ofpact, enum ofpact_type type, size_t len) +{ + memset(ofpact, 0, len); + ofpact->type = type; + ofpact->raw = -1; + ofpact->len = len; +} + +/* Finishes composing a variable-length action (begun using + * ofpact_put_()), by padding the action to a multiple of OFPACT_ALIGNTO + * bytes and updating its embedded length field. See the large comment near + * the end of ofp-actions.h for more information. */ +void +ofpact_finish(struct ofpbuf *ofpacts, struct ofpact *ofpact) +{ + ptrdiff_t len; + + ovs_assert(ofpact == ofpacts->header); + len = (char *) ofpbuf_tail(ofpacts) - (char *) ofpact; + ovs_assert(len > 0 && len <= UINT16_MAX); + ofpact->len = len; + ofpbuf_padto(ofpacts, OFPACT_ALIGN(ofpacts->size)); +} + +static char * OVS_WARN_UNUSED_RESULT +ofpact_parse(enum ofpact_type type, char *value, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols) { - size_t i; - - ds_put_cstr(s, "dec_ttl"); - if (ids->ofpact.compat == OFPUTIL_NXAST_DEC_TTL_CNT_IDS) { - ds_put_cstr(s, "("); - for (i = 0; i < ids->n_controllers; i++) { - if (i) { - ds_put_cstr(s, ","); - } - ds_put_format(s, "%"PRIu16, ids->cnt_ids[i]); - } - ds_put_cstr(s, ")"); + switch (type) { +#define OFPACT(ENUM, STRUCT, MEMBER, NAME) \ + case OFPACT_##ENUM: \ + return parse_##ENUM(value, ofpacts, usable_protocols); + OFPACTS +#undef OFPACT + default: + OVS_NOT_REACHED(); } } -static void -print_fin_timeout(const struct ofpact_fin_timeout *fin_timeout, - struct ds *s) +static bool +ofpact_type_from_name(const char *name, enum ofpact_type *type) { - ds_put_cstr(s, "fin_timeout("); - if (fin_timeout->fin_idle_timeout) { - ds_put_format(s, "idle_timeout=%"PRIu16",", - fin_timeout->fin_idle_timeout); - } - if (fin_timeout->fin_hard_timeout) { - ds_put_format(s, "hard_timeout=%"PRIu16",", - fin_timeout->fin_hard_timeout); +#define OFPACT(ENUM, STRUCT, MEMBER, NAME) \ + if (!strcasecmp(name, NAME)) { \ + *type = OFPACT_##ENUM; \ + return true; \ } - ds_chomp(s, ','); - ds_put_char(s, ')'); + OFPACTS +#undef OFPACT + + return false; } -static void -ofpact_format(const struct ofpact *a, struct ds *s) +/* Parses 'str' as a series of instructions, and appends them to 'ofpacts'. + * + * Returns NULL if successful, otherwise a malloc()'d string describing the + * error. The caller is responsible for freeing the returned string. + * + * If 'outer_action' is specified, indicates that the actions being parsed + * are nested within another action of the type specified in 'outer_action'. */ +static char * OVS_WARN_UNUSED_RESULT +ofpacts_parse__(char *str, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols, + bool allow_instructions, enum ofpact_type outer_action) { - const struct ofpact_enqueue *enqueue; - const struct ofpact_resubmit *resubmit; - const struct ofpact_controller *controller; - const struct ofpact_metadata *metadata; - const struct ofpact_tunnel *tunnel; - const struct ofpact_sample *sample; - const struct ofpact_set_field *set_field; - const struct mf_field *mf; - ofp_port_t port; - - switch (a->type) { - case OFPACT_OUTPUT: - port = ofpact_get_OUTPUT(a)->port; - if (ofp_to_u16(port) < ofp_to_u16(OFPP_MAX)) { - ds_put_format(s, "output:%"PRIu16, port); + int prev_inst = -1; + enum ofperr retval; + char *key, *value; + bool drop = false; + char *pos; + + pos = str; + while (ofputil_parse_key_value(&pos, &key, &value)) { + enum ovs_instruction_type inst = OVSINST_OFPIT11_APPLY_ACTIONS; + enum ofpact_type type; + char *error = NULL; + ofp_port_t port; + + if (ofpact_type_from_name(key, &type)) { + error = ofpact_parse(type, value, ofpacts, usable_protocols); + inst = ovs_instruction_type_from_ofpact_type(type); + } else if (!strcasecmp(key, "mod_vlan_vid")) { + error = parse_set_vlan_vid(value, ofpacts, true); + } else if (!strcasecmp(key, "mod_vlan_pcp")) { + error = parse_set_vlan_pcp(value, ofpacts, true); + } else if (!strcasecmp(key, "set_nw_ttl")) { + error = parse_SET_IP_TTL(value, ofpacts, usable_protocols); + } else if (!strcasecmp(key, "pop_vlan")) { + error = parse_pop_vlan(ofpacts); + } else if (!strcasecmp(key, "set_tunnel64")) { + error = parse_set_tunnel(value, ofpacts, + NXAST_RAW_SET_TUNNEL64); + } else if (!strcasecmp(key, "load")) { + error = parse_reg_load(value, ofpacts); + } else if (!strcasecmp(key, "bundle_load")) { + error = parse_bundle_load(value, ofpacts); + } else if (!strcasecmp(key, "drop")) { + drop = true; + } else if (!strcasecmp(key, "apply_actions")) { + return xstrdup("apply_actions is the default instruction"); + } else if (ofputil_port_from_string(key, &port)) { + ofpact_put_OUTPUT(ofpacts)->port = port; } else { - ofputil_format_port(port, s); - if (port == OFPP_CONTROLLER) { - ds_put_format(s, ":%"PRIu16, ofpact_get_OUTPUT(a)->max_len); - } + return xasprintf("unknown action %s", key); + } + if (error) { + return error; } - break; - - case OFPACT_CONTROLLER: - controller = ofpact_get_CONTROLLER(a); - if (controller->reason == OFPR_ACTION && - controller->controller_id == 0) { - ds_put_format(s, "CONTROLLER:%"PRIu16, - ofpact_get_CONTROLLER(a)->max_len); - } else { - enum ofp_packet_in_reason reason = controller->reason; - - ds_put_cstr(s, "controller("); - if (reason != OFPR_ACTION) { - char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE]; - ds_put_format(s, "reason=%s,", - ofputil_packet_in_reason_to_string( - reason, reasonbuf, sizeof reasonbuf)); + if (inst != OVSINST_OFPIT11_APPLY_ACTIONS) { + if (!allow_instructions) { + return xasprintf("only actions are allowed here (not " + "instruction %s)", + ovs_instruction_name_from_type(inst)); } - if (controller->max_len != UINT16_MAX) { - ds_put_format(s, "max_len=%"PRIu16",", controller->max_len); + if (inst == prev_inst) { + return xasprintf("instruction %s may be specified only once", + ovs_instruction_name_from_type(inst)); } - if (controller->controller_id != 0) { - ds_put_format(s, "id=%"PRIu16",", controller->controller_id); - } - ds_chomp(s, ','); - ds_put_char(s, ')'); } - break; + if (prev_inst != -1 && inst < prev_inst) { + return xasprintf("instruction %s must be specified before %s", + ovs_instruction_name_from_type(inst), + ovs_instruction_name_from_type(prev_inst)); + } + prev_inst = inst; + } - case OFPACT_ENQUEUE: - enqueue = ofpact_get_ENQUEUE(a); - ds_put_format(s, "enqueue:"); - ofputil_format_port(enqueue->port, s); - ds_put_format(s, "q%"PRIu32, enqueue->queue); - break; + if (drop && ofpacts->size) { + return xstrdup("\"drop\" must not be accompanied by any other action " + "or instruction"); + } - case OFPACT_OUTPUT_REG: - ds_put_cstr(s, "output:"); - mf_format_subfield(&ofpact_get_OUTPUT_REG(a)->src, s); - break; + retval = ofpacts_verify(ofpacts->data, ofpacts->size, + (allow_instructions + ? (1u << N_OVS_INSTRUCTIONS) - 1 + : 1u << OVSINST_OFPIT11_APPLY_ACTIONS), + outer_action); + if (retval) { + return xstrdup("Incorrect instruction ordering"); + } - case OFPACT_BUNDLE: - bundle_format(ofpact_get_BUNDLE(a), s); - break; + return NULL; +} - case OFPACT_SET_VLAN_VID: - ds_put_format(s, "%s:%"PRIu16, - (a->compat == OFPUTIL_OFPAT11_SET_VLAN_VID - ? "set_vlan_vid" - : "mod_vlan_vid"), - ofpact_get_SET_VLAN_VID(a)->vlan_vid); - break; +static char * OVS_WARN_UNUSED_RESULT +ofpacts_parse(char *str, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols, bool allow_instructions, + enum ofpact_type outer_action) +{ + uint32_t orig_size = ofpacts->size; + char *error = ofpacts_parse__(str, ofpacts, usable_protocols, + allow_instructions, outer_action); + if (error) { + ofpacts->size = orig_size; + } + return error; +} - case OFPACT_SET_VLAN_PCP: - ds_put_format(s, "%s:%"PRIu8, - (a->compat == OFPUTIL_OFPAT11_SET_VLAN_PCP - ? "set_vlan_pcp" - : "mod_vlan_pcp"), - ofpact_get_SET_VLAN_PCP(a)->vlan_pcp); - break; +static char * OVS_WARN_UNUSED_RESULT +ofpacts_parse_copy(const char *s_, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols, + bool allow_instructions, enum ofpact_type outer_action) +{ + char *error, *s; - case OFPACT_STRIP_VLAN: - ds_put_cstr(s, a->compat == OFPUTIL_OFPAT11_POP_VLAN - ? "pop_vlan" : "strip_vlan"); - break; + *usable_protocols = OFPUTIL_P_ANY; - case OFPACT_PUSH_VLAN: - /* XXX 802.1AD case*/ - ds_put_format(s, "push_vlan:%#"PRIx16, ETH_TYPE_VLAN_8021Q); - break; + s = xstrdup(s_); + error = ofpacts_parse(s, ofpacts, usable_protocols, allow_instructions, + outer_action); + free(s); - case OFPACT_SET_ETH_SRC: - ds_put_format(s, "mod_dl_src:"ETH_ADDR_FMT, - ETH_ADDR_ARGS(ofpact_get_SET_ETH_SRC(a)->mac)); - break; + return error; +} - case OFPACT_SET_ETH_DST: - ds_put_format(s, "mod_dl_dst:"ETH_ADDR_FMT, - ETH_ADDR_ARGS(ofpact_get_SET_ETH_DST(a)->mac)); - break; +/* Parses 's' as a set of OpenFlow actions and appends the actions to + * 'ofpacts'. 'outer_action', if nonzero, specifies that 's' contains actions + * that are nested within the action of type 'outer_action'. + * + * Returns NULL if successful, otherwise a malloc()'d string describing the + * error. The caller is responsible for freeing the returned string. */ +char * OVS_WARN_UNUSED_RESULT +ofpacts_parse_actions(const char *s, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols) +{ + return ofpacts_parse_copy(s, ofpacts, usable_protocols, false, 0); +} - case OFPACT_SET_IPV4_SRC: - ds_put_format(s, "mod_nw_src:"IP_FMT, - IP_ARGS(ofpact_get_SET_IPV4_SRC(a)->ipv4)); - break; +/* Parses 's' as a set of OpenFlow instructions and appends the instructions to + * 'ofpacts'. + * + * Returns NULL if successful, otherwise a malloc()'d string describing the + * error. The caller is responsible for freeing the returned string. */ +char * OVS_WARN_UNUSED_RESULT +ofpacts_parse_instructions(const char *s, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols) +{ + return ofpacts_parse_copy(s, ofpacts, usable_protocols, true, 0); +} - case OFPACT_SET_IPV4_DST: - ds_put_format(s, "mod_nw_dst:"IP_FMT, - IP_ARGS(ofpact_get_SET_IPV4_DST(a)->ipv4)); - break; +const char * +ofpact_name(enum ofpact_type type) +{ + switch (type) { +#define OFPACT(ENUM, STRUCT, MEMBER, NAME) case OFPACT_##ENUM: return NAME; + OFPACTS +#undef OFPACT + } + return ""; +} + +/* Low-level action decoding and encoding functions. */ - case OFPACT_SET_IP_DSCP: - ds_put_format(s, "mod_nw_tos:%d", ofpact_get_SET_IP_DSCP(a)->dscp); - break; +/* Everything needed to identify a particular OpenFlow action. */ +struct ofpact_hdrs { + uint32_t vendor; /* 0 if standard, otherwise a vendor code. */ + uint16_t type; /* Type if standard, otherwise subtype. */ + uint8_t ofp_version; /* From ofp_header. */ +}; - case OFPACT_SET_IP_ECN: - ds_put_format(s, "mod_nw_ecn:%d", ofpact_get_SET_IP_ECN(a)->ecn); - break; +/* Information about a particular OpenFlow action. */ +struct ofpact_raw_instance { + /* The action's identity. */ + struct ofpact_hdrs hdrs; + enum ofp_raw_action_type raw; - case OFPACT_SET_IP_TTL: - ds_put_format(s, "mod_nw_ttl:%d", ofpact_get_SET_IP_TTL(a)->ttl); - break; + /* Looking up the action. */ + struct hmap_node decode_node; /* Based on 'hdrs'. */ + struct hmap_node encode_node; /* Based on 'raw' + 'hdrs.ofp_version'. */ - case OFPACT_SET_L4_SRC_PORT: - ds_put_format(s, "mod_tp_src:%d", ofpact_get_SET_L4_SRC_PORT(a)->port); - break; + /* The action's encoded size. + * + * If this action is fixed-length, 'min_length' == 'max_length'. + * If it is variable length, then 'max_length' is ROUND_DOWN(UINT16_MAX, + * OFP_ACTION_ALIGN) == 65528. */ + unsigned short int min_length; + unsigned short int max_length; + + /* For actions with a simple integer numeric argument, 'arg_ofs' is the + * offset of that argument from the beginning of the action and 'arg_len' + * its length, both in bytes. + * + * For actions that take other forms, these are both zero. */ + unsigned short int arg_ofs; + unsigned short int arg_len; - case OFPACT_SET_L4_DST_PORT: - ds_put_format(s, "mod_tp_dst:%d", ofpact_get_SET_L4_DST_PORT(a)->port); - break; + /* The name of the action, e.g. "OFPAT_OUTPUT" or "NXAST_RESUBMIT". */ + const char *name; - case OFPACT_REG_MOVE: - nxm_format_reg_move(ofpact_get_REG_MOVE(a), s); - break; + /* If this action is deprecated, a human-readable string with a brief + * explanation. */ + const char *deprecation; +}; - case OFPACT_REG_LOAD: - nxm_format_reg_load(ofpact_get_REG_LOAD(a), s); - break; +/* Action header. */ +struct ofp_action_header { + /* The meaning of other values of 'type' generally depends on the OpenFlow + * version (see enum ofp_raw_action_type). + * + * Across all OpenFlow versions, OFPAT_VENDOR indicates that 'vendor' + * designates an OpenFlow vendor ID and that the remainder of the action + * structure has a vendor-defined meaning. + */ +#define OFPAT_VENDOR 0xffff + ovs_be16 type; - case OFPACT_SET_FIELD: - set_field = ofpact_get_SET_FIELD(a); - mf = set_field->field; - ds_put_format(s, "set_field:"); - mf_format(mf, &set_field->value, NULL, s); - ds_put_format(s, "->%s", mf->name); - break; + /* Always a multiple of 8. */ + ovs_be16 len; - case OFPACT_STACK_PUSH: - nxm_format_stack_push(ofpact_get_STACK_PUSH(a), s); - break; + /* For type == OFPAT_VENDOR only, this is a vendor ID, e.g. NX_VENDOR_ID or + * ONF_VENDOR_ID. Other 'type's use this space for some other purpose. */ + ovs_be32 vendor; +}; +OFP_ASSERT(sizeof(struct ofp_action_header) == 8); - case OFPACT_STACK_POP: - nxm_format_stack_pop(ofpact_get_STACK_POP(a), s); - break; +/* Header for Nicira-defined actions and for ONF vendor extensions. + * + * This cannot be used as an entirely generic vendor extension action header, + * because OpenFlow does not specify the location or size of the action + * subtype; it just happens that ONF extensions and Nicira extensions share + * this format. */ +struct ext_action_header { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* At least 16. */ + ovs_be32 vendor; /* NX_VENDOR_ID or ONF_VENDOR_ID. */ + ovs_be16 subtype; /* See enum ofp_raw_action_type. */ + uint8_t pad[6]; +}; +OFP_ASSERT(sizeof(struct ext_action_header) == 16); - case OFPACT_DEC_TTL: - print_dec_ttl(ofpact_get_DEC_TTL(a), s); - break; +static bool +ofpact_hdrs_equal(const struct ofpact_hdrs *a, + const struct ofpact_hdrs *b) +{ + return (a->vendor == b->vendor + && a->type == b->type + && a->ofp_version == b->ofp_version); +} - case OFPACT_SET_MPLS_TTL: - ds_put_format(s, "set_mpls_ttl(%"PRIu8")", - ofpact_get_SET_MPLS_TTL(a)->ttl); - break; +static uint32_t +ofpact_hdrs_hash(const struct ofpact_hdrs *hdrs) +{ + return hash_2words(hdrs->vendor, (hdrs->type << 16) | hdrs->ofp_version); +} - case OFPACT_DEC_MPLS_TTL: - ds_put_cstr(s, "dec_mpls_ttl"); - break; +#include "ofp-actions.inc2" - case OFPACT_SET_TUNNEL: - tunnel = ofpact_get_SET_TUNNEL(a); - ds_put_format(s, "set_tunnel%s:%#"PRIx64, - (tunnel->tun_id > UINT32_MAX - || a->compat == OFPUTIL_NXAST_SET_TUNNEL64 ? "64" : ""), - tunnel->tun_id); - break; +static struct hmap * +ofpact_decode_hmap(void) +{ + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; + static struct hmap hmap; + + if (ovsthread_once_start(&once)) { + struct ofpact_raw_instance *inst; + + hmap_init(&hmap); + for (inst = all_raw_instances; + inst < &all_raw_instances[ARRAY_SIZE(all_raw_instances)]; + inst++) { + hmap_insert(&hmap, &inst->decode_node, + ofpact_hdrs_hash(&inst->hdrs)); + } + ovsthread_once_done(&once); + } + return &hmap; +} - case OFPACT_SET_QUEUE: - ds_put_format(s, "set_queue:%"PRIu32, - ofpact_get_SET_QUEUE(a)->queue_id); - break; +static struct hmap * +ofpact_encode_hmap(void) +{ + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; + static struct hmap hmap; + + if (ovsthread_once_start(&once)) { + struct ofpact_raw_instance *inst; + + hmap_init(&hmap); + for (inst = all_raw_instances; + inst < &all_raw_instances[ARRAY_SIZE(all_raw_instances)]; + inst++) { + hmap_insert(&hmap, &inst->encode_node, + hash_2words(inst->raw, inst->hdrs.ofp_version)); + } + ovsthread_once_done(&once); + } + return &hmap; +} - case OFPACT_POP_QUEUE: - ds_put_cstr(s, "pop_queue"); - break; +static enum ofperr +ofpact_decode_raw(enum ofp_version ofp_version, + const struct ofp_action_header *oah, size_t length, + const struct ofpact_raw_instance **instp) +{ + const struct ofpact_raw_instance *inst; + struct ofpact_hdrs hdrs; - case OFPACT_FIN_TIMEOUT: - print_fin_timeout(ofpact_get_FIN_TIMEOUT(a), s); - break; + *instp = NULL; + if (length < sizeof *oah) { + return OFPERR_OFPBAC_BAD_LEN; + } - case OFPACT_RESUBMIT: - resubmit = ofpact_get_RESUBMIT(a); - if (resubmit->in_port != OFPP_IN_PORT && resubmit->table_id == 255) { - ds_put_cstr(s, "resubmit:"); - ofputil_format_port(resubmit->in_port, s); - } else { - ds_put_format(s, "resubmit("); - if (resubmit->in_port != OFPP_IN_PORT) { - ofputil_format_port(resubmit->in_port, s); - } - ds_put_char(s, ','); - if (resubmit->table_id != 255) { - ds_put_format(s, "%"PRIu8, resubmit->table_id); + /* Get base action type. */ + if (oah->type == htons(OFPAT_VENDOR)) { + /* Get vendor. */ + hdrs.vendor = ntohl(oah->vendor); + if (hdrs.vendor == NX_VENDOR_ID || hdrs.vendor == ONF_VENDOR_ID) { + /* Get extension subtype. */ + const struct ext_action_header *nah; + + nah = ALIGNED_CAST(const struct ext_action_header *, oah); + if (length < sizeof *nah) { + return OFPERR_OFPBAC_BAD_LEN; } - ds_put_char(s, ')'); + hdrs.type = ntohs(nah->subtype); + } else { + VLOG_WARN_RL(&rl, "OpenFlow action has unknown vendor %#"PRIx32, + hdrs.vendor); + return OFPERR_OFPBAC_BAD_VENDOR; } - break; + } else { + hdrs.vendor = 0; + hdrs.type = ntohs(oah->type); + } - case OFPACT_LEARN: - learn_format(ofpact_get_LEARN(a), s); - break; + hdrs.ofp_version = ofp_version; + HMAP_FOR_EACH_WITH_HASH (inst, decode_node, ofpact_hdrs_hash(&hdrs), + ofpact_decode_hmap()) { + if (ofpact_hdrs_equal(&hdrs, &inst->hdrs)) { + *instp = inst; + return 0; + } + } - case OFPACT_MULTIPATH: - multipath_format(ofpact_get_MULTIPATH(a), s); - break; + return (hdrs.vendor + ? OFPERR_OFPBAC_BAD_VENDOR_TYPE + : OFPERR_OFPBAC_BAD_TYPE); +} - case OFPACT_NOTE: - print_note(ofpact_get_NOTE(a), s); - break; +static enum ofperr +ofpact_pull_raw(struct ofpbuf *buf, enum ofp_version ofp_version, + enum ofp_raw_action_type *raw, uint64_t *arg) +{ + const struct ofp_action_header *oah = buf->data; + const struct ofpact_raw_instance *action; + unsigned int length; + enum ofperr error; - case OFPACT_PUSH_MPLS: - ds_put_format(s, "push_mpls:0x%04"PRIx16, - ntohs(ofpact_get_PUSH_MPLS(a)->ethertype)); - break; + *raw = *arg = 0; + error = ofpact_decode_raw(ofp_version, oah, buf->size, &action); + if (error) { + return error; + } - case OFPACT_POP_MPLS: - ds_put_format(s, "pop_mpls:0x%04"PRIx16, - ntohs(ofpact_get_POP_MPLS(a)->ethertype)); - break; + if (action->deprecation) { + VLOG_INFO_RL(&rl, "%s is deprecated in %s (%s)", + action->name, ofputil_version_to_string(ofp_version), + action->deprecation); + } - case OFPACT_EXIT: - ds_put_cstr(s, "exit"); - break; + length = ntohs(oah->len); + if (length > buf->size) { + VLOG_WARN_RL(&rl, "OpenFlow action %s length %u exceeds action buffer " + "length %"PRIu32, action->name, length, buf->size); + return OFPERR_OFPBAC_BAD_LEN; + } + if (length < action->min_length || length > action->max_length) { + VLOG_WARN_RL(&rl, "OpenFlow action %s length %u not in valid range " + "[%hu,%hu]", action->name, length, + action->min_length, action->max_length); + return OFPERR_OFPBAC_BAD_LEN; + } + if (length % 8) { + VLOG_WARN_RL(&rl, "OpenFlow action %s length %u is not a multiple " + "of 8", action->name, length); + return OFPERR_OFPBAC_BAD_LEN; + } - case OFPACT_SAMPLE: - sample = ofpact_get_SAMPLE(a); - ds_put_format( - s, "sample(probability=%"PRIu16",collector_set_id=%"PRIu32 - ",obs_domain_id=%"PRIu32",obs_point_id=%"PRIu32")", - sample->probability, sample->collector_set_id, - sample->obs_domain_id, sample->obs_point_id); - break; + *raw = action->raw; + *arg = 0; + if (action->arg_len) { + const uint8_t *p; + int i; - case OFPACT_WRITE_ACTIONS: { - struct ofpact_nest *on = ofpact_get_WRITE_ACTIONS(a); - ds_put_format(s, "%s(", - ovs_instruction_name_from_type( - OVSINST_OFPIT11_WRITE_ACTIONS)); - ofpacts_format(on->actions, ofpact_nest_get_action_len(on), s); - ds_put_char(s, ')'); - break; + p = ofpbuf_at_assert(buf, action->arg_ofs, action->arg_len); + for (i = 0; i < action->arg_len; i++) { + *arg = (*arg << 8) | p[i]; + } } - case OFPACT_CLEAR_ACTIONS: - ds_put_format(s, "%s", - ovs_instruction_name_from_type( - OVSINST_OFPIT11_CLEAR_ACTIONS)); - break; + ofpbuf_pull(buf, length); - case OFPACT_WRITE_METADATA: - metadata = ofpact_get_WRITE_METADATA(a); - ds_put_format(s, "%s:%#"PRIx64, - ovs_instruction_name_from_type( - OVSINST_OFPIT11_WRITE_METADATA), - ntohll(metadata->metadata)); - if (metadata->mask != OVS_BE64_MAX) { - ds_put_format(s, "/%#"PRIx64, ntohll(metadata->mask)); + return 0; +} + +static const struct ofpact_raw_instance * +ofpact_raw_lookup(enum ofp_version ofp_version, enum ofp_raw_action_type raw) +{ + const struct ofpact_raw_instance *inst; + + HMAP_FOR_EACH_WITH_HASH (inst, encode_node, hash_2words(raw, ofp_version), + ofpact_encode_hmap()) { + if (inst->raw == raw && inst->hdrs.ofp_version == ofp_version) { + return inst; } - break; + } + OVS_NOT_REACHED(); +} - case OFPACT_GOTO_TABLE: - ds_put_format(s, "%s:%"PRIu8, - ovs_instruction_name_from_type( - OVSINST_OFPIT11_GOTO_TABLE), - ofpact_get_GOTO_TABLE(a)->table_id); - break; +static void * +ofpact_put_raw(struct ofpbuf *buf, enum ofp_version ofp_version, + enum ofp_raw_action_type raw, uint64_t arg) +{ + const struct ofpact_raw_instance *inst; + struct ofp_action_header *oah; + const struct ofpact_hdrs *hdrs; - case OFPACT_METER: - ds_put_format(s, "%s:%"PRIu32, - ovs_instruction_name_from_type(OVSINST_OFPIT13_METER), - ofpact_get_METER(a)->meter_id); + inst = ofpact_raw_lookup(ofp_version, raw); + hdrs = &inst->hdrs; + + oah = ofpbuf_put_zeros(buf, inst->min_length); + oah->type = htons(hdrs->vendor ? OFPAT_VENDOR : hdrs->type); + oah->len = htons(inst->min_length); + oah->vendor = htonl(hdrs->vendor); + + switch (hdrs->vendor) { + case 0: break; - case OFPACT_GROUP: - ds_put_format(s, "group:%"PRIu32, - ofpact_get_GROUP(a)->group_id); + case NX_VENDOR_ID: + case ONF_VENDOR_ID: { + struct ext_action_header *nah = (struct ext_action_header *) oah; + nah->subtype = htons(hdrs->type); break; } -} -/* Appends a string representing the 'ofpacts_len' bytes of ofpacts in - * 'ofpacts' to 'string'. */ -void -ofpacts_format(const struct ofpact *ofpacts, size_t ofpacts_len, - struct ds *string) -{ - if (!ofpacts_len) { - ds_put_cstr(string, "drop"); - } else { - const struct ofpact *a; + default: + OVS_NOT_REACHED(); + } - OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { - if (a != ofpacts) { - ds_put_cstr(string, ","); - } + if (inst->arg_len) { + uint8_t *p = (uint8_t *) oah + inst->arg_ofs + inst->arg_len; + int i; - /* XXX write-actions */ - ofpact_format(a, string); + for (i = 0; i < inst->arg_len; i++) { + *--p = arg; + arg >>= 8; } + } else { + ovs_assert(!arg); } + + return oah; } - -/* Internal use by helpers. */ -void * -ofpact_put(struct ofpbuf *ofpacts, enum ofpact_type type, size_t len) +static void +pad_ofpat(struct ofpbuf *openflow, size_t start_ofs) { - struct ofpact *ofpact; + struct ofp_action_header *oah; - ofpact_pad(ofpacts); - ofpact = ofpacts->l2 = ofpbuf_put_uninit(ofpacts, len); - ofpact_init(ofpact, type, len); - return ofpact; -} + ofpbuf_put_zeros(openflow, PAD_SIZE(openflow->size - start_ofs, + OFP_ACTION_ALIGN)); -void -ofpact_init(struct ofpact *ofpact, enum ofpact_type type, size_t len) -{ - memset(ofpact, 0, len); - ofpact->type = type; - ofpact->compat = OFPUTIL_ACTION_INVALID; - ofpact->len = len; -} - -/* Updates 'ofpact->len' to the number of bytes in the tail of 'ofpacts' - * starting at 'ofpact'. - * - * This is the correct way to update a variable-length ofpact's length after - * adding the variable-length part of the payload. (See the large comment - * near the end of ofp-actions.h for more information.) */ -void -ofpact_update_len(struct ofpbuf *ofpacts, struct ofpact *ofpact) -{ - ovs_assert(ofpact == ofpacts->l2); - ofpact->len = (char *) ofpbuf_tail(ofpacts) - (char *) ofpact; + oah = ofpbuf_at_assert(openflow, start_ofs, sizeof *oah); + oah->len = htons(openflow->size - start_ofs); } -/* Pads out 'ofpacts' to a multiple of OFPACT_ALIGNTO bytes in length. Each - * ofpact_put_() calls this function automatically beforehand, but the - * client must call this itself after adding the final ofpact to an array of - * them. - * - * (The consequences of failing to call this function are probably not dire. - * OFPACT_FOR_EACH will calculate a pointer beyond the end of the ofpacts, but - * not dereference it. That's undefined behavior, technically, but it will not - * cause a real problem on common systems. Still, it seems better to call - * it.) */ -void -ofpact_pad(struct ofpbuf *ofpacts) -{ - unsigned int rem = ofpacts->size % OFPACT_ALIGNTO; - if (rem) { - ofpbuf_put_zeros(ofpacts, OFPACT_ALIGNTO - rem); - } -}