From: Ben Pfaff Date: Wed, 4 Jul 2012 05:14:29 +0000 (-0700) Subject: ofp-actions: Add decoding and encoding OF1.1 instructions and actions. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d01c980ffe0d61497298a9f907ff93fe1751e2bd;p=openvswitch ofp-actions: Add decoding and encoding OF1.1 instructions and actions. So far, only the Apply-Actions instruction is supported, and only actions that have identical semantics to OpenFlow 1.0 actions. Co-authored-by: Simon Horman Co-authored-by: Isaku Yamahata Signed-off-by: Ben Pfaff --- diff --git a/DESIGN b/DESIGN index 7dd6efa2..b06751f1 100644 --- a/DESIGN +++ b/DESIGN @@ -628,6 +628,19 @@ against desired actions in a bytewise fashion: - Open vSwitch zeros padding bytes in action structures, regardless of their values when the flows were added. + - Open vSwitch "normalizes" the instructions in OpenFlow 1.1 + (and later) in the following way: + + * OVS sorts the instructions into the following order: + Apply-Actions, Clear-Actions, Write-Actions, + Write-Metadata, Goto-Table. + + * OVS drops Apply-Actions instructions that have empty + action lists. + + * OVS drops Write-Actions instructions that have empty + action sets. + Please report other discrepancies, if you notice any, so that we can fix or document them. diff --git a/build-aux/extract-ofp-errors b/build-aux/extract-ofp-errors index efaf103b..1ec5ba4d 100755 --- a/build-aux/extract-ofp-errors +++ b/build-aux/extract-ofp-errors @@ -234,6 +234,7 @@ def extract_ofp_errors(filenames): "NX1.0+": ("OF1.0", "OF1.1", "OF1.2"), "NX1.0": ("OF1.0",), "NX1.1": ("OF1.1",), + "NX1.1+": ("OF1.1",), "NX1.2": ("OF1.2",)} if targets not in target_map: fatal("%s: unknown error domain" % targets) diff --git a/include/openflow/openflow-1.0.h b/include/openflow/openflow-1.0.h index 2ee356df..039eb6be 100644 --- a/include/openflow/openflow-1.0.h +++ b/include/openflow/openflow-1.0.h @@ -232,72 +232,19 @@ enum ofp10_action_type { * 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 ofp_action_output { +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 ofp_action_output) == 8); +OFP_ASSERT(sizeof(struct ofp10_action_output) == 8); /* The VLAN id is 12 bits, so we can use the entire 16 bits to indicate * special conditions. All ones is used to match that no VLAN id was * set. */ #define OFP_VLAN_NONE 0xffff -/* Action structure for OFPAT10_SET_VLAN_VID. */ -struct ofp_action_vlan_vid { - ovs_be16 type; /* OFPAT10_SET_VLAN_VID. */ - ovs_be16 len; /* Length is 8. */ - ovs_be16 vlan_vid; /* VLAN id. */ - uint8_t pad[2]; -}; -OFP_ASSERT(sizeof(struct ofp_action_vlan_vid) == 8); - -/* Action structure for OFPAT10_SET_VLAN_PCP. */ -struct ofp_action_vlan_pcp { - ovs_be16 type; /* OFPAT10_SET_VLAN_PCP. */ - ovs_be16 len; /* Length is 8. */ - uint8_t vlan_pcp; /* VLAN priority. */ - uint8_t pad[3]; -}; -OFP_ASSERT(sizeof(struct ofp_action_vlan_pcp) == 8); - -/* Action structure for OFPAT10_SET_DL_SRC/DST. */ -struct ofp_action_dl_addr { - ovs_be16 type; /* OFPAT10_SET_DL_SRC/DST. */ - ovs_be16 len; /* Length is 16. */ - uint8_t dl_addr[OFP_ETH_ALEN]; /* Ethernet address. */ - uint8_t pad[6]; -}; -OFP_ASSERT(sizeof(struct ofp_action_dl_addr) == 16); - -/* Action structure for OFPAT10_SET_NW_SRC/DST. */ -struct ofp_action_nw_addr { - ovs_be16 type; /* OFPAT10_SET_TW_SRC/DST. */ - ovs_be16 len; /* Length is 8. */ - ovs_be32 nw_addr; /* IP address. */ -}; -OFP_ASSERT(sizeof(struct ofp_action_nw_addr) == 8); - -/* Action structure for OFPAT10_SET_NW_TOS. */ -struct ofp_action_nw_tos { - ovs_be16 type; /* OFPAT10_SET_TW_TOS. */ - ovs_be16 len; /* Length is 8. */ - uint8_t nw_tos; /* DSCP in high 6 bits, rest ignored. */ - uint8_t pad[3]; -}; -OFP_ASSERT(sizeof(struct ofp_action_nw_tos) == 8); - -/* Action structure for OFPAT10_SET_TP_SRC/DST. */ -struct ofp_action_tp_port { - ovs_be16 type; /* OFPAT10_SET_TP_SRC/DST. */ - ovs_be16 len; /* Length is 8. */ - ovs_be16 tp_port; /* TCP/UDP port. */ - uint8_t pad[2]; -}; -OFP_ASSERT(sizeof(struct ofp_action_tp_port) == 8); - /* Action header for OFPAT10_VENDOR. The rest of the body is vendor-defined. */ struct ofp_action_vendor_header { ovs_be16 type; /* OFPAT10_VENDOR. */ @@ -336,7 +283,7 @@ union ofp_action { ovs_be16 type; struct ofp_action_header header; struct ofp_action_vendor_header vendor; - struct ofp_action_output output; + 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; diff --git a/include/openflow/openflow-1.1.h b/include/openflow/openflow-1.1.h index f0c063bc..f0f37938 100644 --- a/include/openflow/openflow-1.1.h +++ b/include/openflow/openflow-1.1.h @@ -305,6 +305,16 @@ enum ofp11_instruction_type { OFPIT11_EXPERIMENTER = 0xFFFF /* Experimenter instruction */ }; +#define OFP11_INSTRUCTION_ALIGN 8 + +/* Generic ofp_instruction structure. */ +struct ofp11_instruction { + ovs_be16 type; /* Instruction type */ + ovs_be16 len; /* Length of this struct in bytes. */ + uint8_t pad[4]; /* Align to 64-bits */ +}; +OFP_ASSERT(sizeof(struct ofp11_instruction) == 8); + /* Instruction structure for OFPIT_GOTO_TABLE */ struct ofp11_instruction_goto_table { ovs_be16 type; /* OFPIT_GOTO_TABLE */ @@ -335,6 +345,16 @@ struct ofp11_instruction_actions { }; OFP_ASSERT(sizeof(struct ofp11_instruction_actions) == 8); +/* Instruction structure for experimental instructions */ +struct ofp11_instruction_experimenter { + ovs_be16 type; /* OFPIT11_EXPERIMENTER */ + ovs_be16 len; /* Length of this struct in bytes */ + ovs_be32 experimenter; /* Experimenter ID which takes the same form + as in struct ofp_vendor_header. */ + /* Experimenter-defined arbitrary additional data. */ +}; +OFP_ASSERT(sizeof(struct ofp11_instruction_experimenter) == 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 diff --git a/include/openflow/openflow-common.h b/include/openflow/openflow-common.h index 9788af10..bb4de0b0 100644 --- a/include/openflow/openflow-common.h +++ b/include/openflow/openflow-common.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008, 2011 The Board of Trustees of The Leland Stanford +/* Copyright (c) 2008, 2011, 2012 The Board of Trustees of The Leland Stanford * Junior University * * We are making the OpenFlow specification and associated documentation @@ -222,6 +222,59 @@ enum ofp_packet_in_reason { OFPR_N_REASONS }; +/* Action structure for OFPAT10_SET_VLAN_VID and OFPAT11_SET_VLAN_VID. */ +struct ofp_action_vlan_vid { + ovs_be16 type; /* Type. */ + ovs_be16 len; /* Length is 8. */ + ovs_be16 vlan_vid; /* VLAN id. */ + uint8_t pad[2]; +}; +OFP_ASSERT(sizeof(struct ofp_action_vlan_vid) == 8); + +/* Action structure for OFPAT10_SET_VLAN_PCP and OFPAT11_SET_VLAN_PCP. */ +struct ofp_action_vlan_pcp { + ovs_be16 type; /* Type. */ + ovs_be16 len; /* Length is 8. */ + uint8_t vlan_pcp; /* VLAN priority. */ + uint8_t pad[3]; +}; +OFP_ASSERT(sizeof(struct ofp_action_vlan_pcp) == 8); + +/* 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. */ + uint8_t dl_addr[OFP_ETH_ALEN]; /* Ethernet address. */ + uint8_t pad[6]; +}; +OFP_ASSERT(sizeof(struct ofp_action_dl_addr) == 16); + +/* Action structure for OFPAT10_SET_NW_SRC/DST and OFPAT11_SET_NW_SRC/DST. */ +struct ofp_action_nw_addr { + ovs_be16 type; /* Type. */ + ovs_be16 len; /* Length is 8. */ + ovs_be32 nw_addr; /* IP address. */ +}; +OFP_ASSERT(sizeof(struct ofp_action_nw_addr) == 8); + +/* Action structure for OFPAT10_SET_NW_TOS and OFPAT11_SET_NW_TOS. */ +struct ofp_action_nw_tos { + ovs_be16 type; /* Type.. */ + ovs_be16 len; /* Length is 8. */ + uint8_t nw_tos; /* DSCP in high 6 bits, rest ignored. */ + uint8_t pad[3]; +}; +OFP_ASSERT(sizeof(struct ofp_action_nw_tos) == 8); + +/* Action structure for OFPAT10_SET_TP_SRC/DST and OFPAT11_SET_TP_SRC/DST. */ +struct ofp_action_tp_port { + ovs_be16 type; /* Type. */ + ovs_be16 len; /* Length is 8. */ + ovs_be16 tp_port; /* TCP/UDP port. */ + uint8_t pad[2]; +}; +OFP_ASSERT(sizeof(struct ofp_action_tp_port) == 8); + /* Why was this flow removed? */ enum ofp_flow_removed_reason { OFPRR_IDLE_TIMEOUT, /* Flow idle time exceeded idle_timeout. */ diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 12cc0b0b..2254f532 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -36,7 +36,7 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); /* Converting OpenFlow 1.0 to ofpacts. */ static enum ofperr -output_from_openflow10(const struct ofp_action_output *oao, +output_from_openflow10(const struct ofp10_action_output *oao, struct ofpbuf *out) { struct ofpact_output *output; @@ -216,7 +216,8 @@ decode_openflow10_action(const union ofp_action *a, } static enum ofperr -ofpact_from_openflow10__(const union ofp_action *a, struct ofpbuf *out) +ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code, + struct ofpbuf *out) { const struct nx_action_resubmit *nar; const struct nx_action_set_tunnel *nast; @@ -224,79 +225,15 @@ ofpact_from_openflow10__(const union ofp_action *a, struct ofpbuf *out) const struct nx_action_note *nan; const struct nx_action_set_tunnel64 *nast64; struct ofpact_tunnel *tunnel; - enum ofputil_action_code code; - enum ofperr error; - - error = decode_openflow10_action(a, &code); - if (error) { - return error; - } + enum ofperr error = 0; switch (code) { case OFPUTIL_ACTION_INVALID: +#define OFPAT10_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM: +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM: +#include "ofp-util.def" NOT_REACHED(); - case OFPUTIL_OFPAT10_OUTPUT: - return output_from_openflow10((const struct ofp_action_output *) a, - out); - - case OFPUTIL_OFPAT10_SET_VLAN_VID: - if (a->vlan_vid.vlan_vid & ~htons(0xfff)) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_SET_VLAN_VID(out)->vlan_vid = ntohs(a->vlan_vid.vlan_vid); - break; - - case OFPUTIL_OFPAT10_SET_VLAN_PCP: - if (a->vlan_pcp.vlan_pcp & ~7) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_SET_VLAN_PCP(out)->vlan_pcp = a->vlan_pcp.vlan_pcp; - break; - - case OFPUTIL_OFPAT10_STRIP_VLAN: - ofpact_put_STRIP_VLAN(out); - break; - - case OFPUTIL_OFPAT10_SET_DL_SRC: - memcpy(ofpact_put_SET_ETH_SRC(out)->mac, - ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN); - break; - - case OFPUTIL_OFPAT10_SET_DL_DST: - memcpy(ofpact_put_SET_ETH_DST(out)->mac, - ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN); - break; - - case OFPUTIL_OFPAT10_SET_NW_SRC: - ofpact_put_SET_IPV4_SRC(out)->ipv4 = a->nw_addr.nw_addr; - break; - - case OFPUTIL_OFPAT10_SET_NW_DST: - ofpact_put_SET_IPV4_DST(out)->ipv4 = a->nw_addr.nw_addr; - break; - - case OFPUTIL_OFPAT10_SET_NW_TOS: - if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) { - return OFPERR_OFPBAC_BAD_ARGUMENT; - } - ofpact_put_SET_IPV4_DSCP(out)->dscp = a->nw_tos.nw_tos; - break; - - case OFPUTIL_OFPAT10_SET_TP_SRC: - ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(a->tp_port.tp_port); - break; - - case OFPUTIL_OFPAT10_SET_TP_DST: - ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(a->tp_port.tp_port); - - break; - - case OFPUTIL_OFPAT10_ENQUEUE: - error = enqueue_from_openflow10((const struct ofp_action_enqueue *) a, - out); - break; - case OFPUTIL_NXAST_RESUBMIT: resubmit_from_openflow((const struct nx_action_resubmit *) a, out); break; @@ -389,6 +326,91 @@ ofpact_from_openflow10__(const union ofp_action *a, struct ofpbuf *out) return error; } +static enum ofperr +ofpact_from_openflow10(const union ofp_action *a, struct ofpbuf *out) +{ + enum ofputil_action_code code; + enum ofperr error; + + error = decode_openflow10_action(a, &code); + if (error) { + return error; + } + + switch (code) { + case OFPUTIL_ACTION_INVALID: +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM: +#include "ofp-util.def" + NOT_REACHED(); + + case OFPUTIL_OFPAT10_OUTPUT: + return output_from_openflow10(&a->output10, out); + + case OFPUTIL_OFPAT10_SET_VLAN_VID: + if (a->vlan_vid.vlan_vid & ~htons(0xfff)) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + ofpact_put_SET_VLAN_VID(out)->vlan_vid = ntohs(a->vlan_vid.vlan_vid); + break; + + case OFPUTIL_OFPAT10_SET_VLAN_PCP: + if (a->vlan_pcp.vlan_pcp & ~7) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + ofpact_put_SET_VLAN_PCP(out)->vlan_pcp = a->vlan_pcp.vlan_pcp; + break; + + case OFPUTIL_OFPAT10_STRIP_VLAN: + ofpact_put_STRIP_VLAN(out); + break; + + case OFPUTIL_OFPAT10_SET_DL_SRC: + memcpy(ofpact_put_SET_ETH_SRC(out)->mac, + ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN); + break; + + case OFPUTIL_OFPAT10_SET_DL_DST: + memcpy(ofpact_put_SET_ETH_DST(out)->mac, + ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN); + break; + + case OFPUTIL_OFPAT10_SET_NW_SRC: + ofpact_put_SET_IPV4_SRC(out)->ipv4 = a->nw_addr.nw_addr; + break; + + case OFPUTIL_OFPAT10_SET_NW_DST: + ofpact_put_SET_IPV4_DST(out)->ipv4 = a->nw_addr.nw_addr; + break; + + case OFPUTIL_OFPAT10_SET_NW_TOS: + if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + ofpact_put_SET_IPV4_DSCP(out)->dscp = a->nw_tos.nw_tos; + break; + + case OFPUTIL_OFPAT10_SET_TP_SRC: + ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(a->tp_port.tp_port); + break; + + case OFPUTIL_OFPAT10_SET_TP_DST: + ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(a->tp_port.tp_port); + + break; + + case OFPUTIL_OFPAT10_ENQUEUE: + error = enqueue_from_openflow10((const struct ofp_action_enqueue *) a, + out); + break; + +#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: +#include "ofp-util.def" + return ofpact_from_nxast(a, code, out); + } + + return error; +} + static inline union ofp_action * action_next(const union ofp_action *a) { @@ -413,14 +435,14 @@ action_is_valid(const union ofp_action *a, size_t n_actions) (ITER) = action_next(ITER))) static enum ofperr -ofpact_from_openflow10(const union ofp_action *in, size_t n_in, - struct ofpbuf *out) +ofpacts_from_openflow10(const union ofp_action *in, size_t n_in, + struct ofpbuf *out) { const union ofp_action *a; size_t left; ACTION_FOR_EACH (a, left, in, n_in) { - enum ofperr error = ofpact_from_openflow10__(a, out); + enum ofperr error = ofpact_from_openflow10(a, out); if (error) { VLOG_WARN_RL(&rl, "bad action at offset %td (%s)", (a - in) * sizeof *a, ofperr_get_name(error)); @@ -437,16 +459,12 @@ ofpact_from_openflow10(const union ofp_action *in, size_t n_in, return 0; } -/* 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. - * - * This function does not check that the actions are valid in a given context. - * The caller should do so, with ofpacts_check(). */ -enum ofperr -ofpacts_pull_openflow(struct ofpbuf *openflow, unsigned int actions_len, - struct ofpbuf *ofpacts) +static enum ofperr +ofpacts_pull_actions(struct ofpbuf *openflow, unsigned int actions_len, + struct ofpbuf *ofpacts, + enum ofperr (*translate)(const union ofp_action *actions, + size_t n_actions, + struct ofpbuf *ofpacts)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); const union ofp_action *actions; @@ -468,13 +486,420 @@ ofpacts_pull_openflow(struct ofpbuf *openflow, unsigned int actions_len, return OFPERR_OFPBRC_BAD_LEN; } - error = ofpact_from_openflow10(actions, actions_len / OFP_ACTION_ALIGN, - ofpacts); + error = translate(actions, actions_len / OFP_ACTION_ALIGN, ofpacts); if (error) { ofpbuf_clear(ofpacts); } + return error; +} + +/* Attempts to convert 'actions_len' bytes of OpenFlow 1.0 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. + * + * This function does not check that the actions are valid in a given context. + * The caller should do so, with ofpacts_check(). */ +enum ofperr +ofpacts_pull_openflow10(struct ofpbuf *openflow, unsigned int actions_len, + struct ofpbuf *ofpacts) +{ + return ofpacts_pull_actions(openflow, actions_len, ofpacts, + ofpacts_from_openflow10); +} + +/* OpenFlow 1.1 actions. */ + +/* 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 sizeof(union + * ofp_action) 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) +{ + switch (a->type) { + case CONSTANT_HTONS(OFPAT11_EXPERIMENTER): + return decode_nxast_action(a, code); + +#define OFPAT11_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" + + default: + return OFPERR_OFPBAC_BAD_TYPE; + } +} + +static enum ofperr +output_from_openflow11(const struct ofp11_action_output *oao, + struct ofpbuf *out) +{ + struct ofpact_output *output; + enum ofperr error; + + 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 ofputil_check_output_port(output->port, OFPP_MAX); +} + +static enum ofperr +ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out) +{ + enum ofputil_action_code code; + enum ofperr error; + + error = decode_openflow11_action(a, &code); + 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(); + + case OFPUTIL_OFPAT11_OUTPUT: + return output_from_openflow11((const struct ofp11_action_output *) a, + out); + + case OFPUTIL_OFPAT11_SET_VLAN_VID: + if (a->vlan_vid.vlan_vid & ~htons(0xfff)) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + ofpact_put_SET_VLAN_VID(out)->vlan_vid = ntohs(a->vlan_vid.vlan_vid); + break; + + case OFPUTIL_OFPAT11_SET_VLAN_PCP: + if (a->vlan_pcp.vlan_pcp & ~7) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + ofpact_put_SET_VLAN_PCP(out)->vlan_pcp = a->vlan_pcp.vlan_pcp; + break; + + case OFPUTIL_OFPAT11_SET_DL_SRC: + memcpy(ofpact_put_SET_ETH_SRC(out)->mac, + ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN); + break; + + case OFPUTIL_OFPAT11_SET_DL_DST: + memcpy(ofpact_put_SET_ETH_DST(out)->mac, + ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN); + break; + + case OFPUTIL_OFPAT11_SET_NW_SRC: + ofpact_put_SET_IPV4_SRC(out)->ipv4 = a->nw_addr.nw_addr; + break; + + case OFPUTIL_OFPAT11_SET_NW_DST: + ofpact_put_SET_IPV4_DST(out)->ipv4 = a->nw_addr.nw_addr; + break; + + case OFPUTIL_OFPAT11_SET_NW_TOS: + if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + ofpact_put_SET_IPV4_DSCP(out)->dscp = a->nw_tos.nw_tos; + break; + + case OFPUTIL_OFPAT11_SET_TP_SRC: + ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(a->tp_port.tp_port); + break; + + case OFPUTIL_OFPAT11_SET_TP_DST: + ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(a->tp_port.tp_port); + break; + +#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: +#include "ofp-util.def" + return ofpact_from_nxast(a, code, out); + } + + return error; +} + +static enum ofperr +ofpacts_from_openflow11(const union ofp_action *in, size_t n_in, + struct ofpbuf *out) +{ + const union ofp_action *a; + size_t left; + + ACTION_FOR_EACH (a, left, in, n_in) { + enum ofperr error = ofpact_from_openflow11(a, out); + if (error) { + VLOG_WARN_RL(&rl, "bad action at offset %td (%s)", + (a - in) * sizeof *a, ofperr_get_name(error)); + return error; + } + } + if (left) { + VLOG_WARN_RL(&rl, "bad action format at offset %zu", + (n_in - left) * sizeof *a); + return OFPERR_OFPBAC_BAD_LEN; + } + + return 0; +} + +/* OpenFlow 1.1 instructions. */ + +#define OVS_INSTRUCTIONS \ + DEFINE_INST(OFPIT11_GOTO_TABLE, \ + ofp11_instruction_goto_table, false, \ + "goto_table") \ + \ + DEFINE_INST(OFPIT11_WRITE_METADATA, \ + ofp11_instruction_write_metadata, false, \ + "write_metadata") \ + \ + DEFINE_INST(OFPIT11_WRITE_ACTIONS, \ + ofp11_instruction_actions, true, \ + "write_actions") \ + \ + DEFINE_INST(OFPIT11_APPLY_ACTIONS, \ + ofp11_instruction_actions, true, \ + "apply_actions") \ + \ + DEFINE_INST(OFPIT11_CLEAR_ACTIONS, \ + ofp11_instruction, false, \ + "clear_actions") + +enum ovs_instruction_type { +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) OVSINST_##ENUM, + OVS_INSTRUCTIONS +#undef DEFINE_INST +}; + +enum { +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) + 1 + N_OVS_INSTRUCTIONS = OVS_INSTRUCTIONS +#undef DEFINE_INST +}; + +#define DEFINE_INST(ENUM, STRUCT, EXTENSIBLE, NAME) \ + static inline void \ + instruction_init_##ENUM(struct STRUCT *s) \ + { \ + memset(s, 0, sizeof *s); \ + s->type = htons(ENUM); \ + s->len = htons(sizeof *s); \ + } \ + \ + static inline struct STRUCT * \ + 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 inline struct ofp11_instruction * +instruction_next(const struct ofp11_instruction *inst) +{ + return ((struct ofp11_instruction *) (void *) + ((uint8_t *) inst + ntohs(inst->len))); +} + +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); +} + +/* 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))) + +static enum ofperr +decode_openflow11_instruction(const struct ofp11_instruction *inst, + enum ovs_instruction_type *type) +{ + uint16_t len = ntohs(inst->len); + + switch (inst->type) { + case CONSTANT_HTONS(OFPIT11_EXPERIMENTER): + return OFPERR_OFPBIC_BAD_EXPERIMENTER; + +#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 + + default: + return OFPERR_OFPBIC_UNKNOWN_INST; + } +} + +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; + + memset(out, 0, N_OVS_INSTRUCTIONS * sizeof *out); + INSTRUCTION_FOR_EACH (inst, left, insts, n_insts) { + enum ovs_instruction_type type; + enum ofperr error; + + error = decode_openflow11_instruction(inst, &type); + if (error) { + return error; + } + + if (out[type]) { + return OFPERR_NXBIC_DUP_TYPE; + } + out[type] = inst; + } + + if (left) { + VLOG_WARN_RL(&rl, "bad instruction format at offset %zu", + (n_insts - left) * sizeof *inst); + return OFPERR_OFPBIC_BAD_LEN; + } return 0; } + +static void +get_actions_from_instruction(const struct ofp11_instruction *inst, + const union ofp_action **actions, + size_t *n_actions) +{ + *actions = (const union ofp_action *) (inst + 1); + *n_actions = (ntohs(inst->len) - sizeof *inst) / OFP11_INSTRUCTION_ALIGN; +} + +/* Attempts to convert 'actions_len' bytes of OpenFlow 1.1 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. + * + * In most places in OpenFlow 1.1 and 1.2, actions appear encapsulated in + * instructions, so you should call ofpacts_pull_openflow11_instructions() + * instead of this function. + * + * This function does not check that the actions are valid in a given context. + * The caller should do so, with ofpacts_check(). */ +enum ofperr +ofpacts_pull_openflow11_actions(struct ofpbuf *openflow, + unsigned int actions_len, + struct ofpbuf *ofpacts) +{ + enum ofperr error; + + error = ofpacts_pull_actions(openflow, actions_len, ofpacts, + ofpacts_from_openflow11); + if (!error) { + ofpact_pad(ofpacts); + } + return error; +} + +enum ofperr +ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow, + unsigned int instructions_len, + struct ofpbuf *ofpacts) +{ + 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; + + ofpbuf_clear(ofpacts); + + 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; + } + + 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; + } + + error = decode_openflow11_instructions( + instructions, instructions_len / OFP11_INSTRUCTION_ALIGN, + insts); + if (error) { + goto exit; + } + + if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) { + const union ofp_action *actions; + size_t n_actions; + + get_actions_from_instruction(insts[OVSINST_OFPIT11_APPLY_ACTIONS], + &actions, &n_actions); + error = ofpacts_from_openflow11(actions, n_actions, ofpacts); + if (error) { + goto exit; + } + } + + ofpact_pad(ofpacts); + + if (insts[OVSINST_OFPIT11_GOTO_TABLE] || + insts[OVSINST_OFPIT11_WRITE_METADATA] || + insts[OVSINST_OFPIT11_WRITE_ACTIONS] || + insts[OVSINST_OFPIT11_CLEAR_ACTIONS]) { + error = OFPERR_OFPBIC_UNSUP_INST; + goto exit; + } + +exit: + if (error) { + ofpbuf_clear(ofpacts); + } + return error; +} static enum ofperr ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports) @@ -744,7 +1169,7 @@ static void ofpact_output_to_openflow10(const struct ofpact_output *output, struct ofpbuf *out) { - struct ofp_action_output *oao; + struct ofp10_action_output *oao; oao = ofputil_put_OFPAT10_OUTPUT(out); oao->port = htons(output->port); @@ -844,12 +1269,12 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out) } } -/* Converts the 'ofpacts_len' bytes of ofpacts in 'ofpacts' into OpenFlow +/* Converts the 'ofpacts_len' bytes of ofpacts in 'ofpacts' into OpenFlow 1.0 * actions in 'openflow', appending the actions to any existing data in * 'openflow'. */ void -ofpacts_to_openflow(const struct ofpact ofpacts[], size_t ofpacts_len, - struct ofpbuf *openflow) +ofpacts_put_openflow10(const struct ofpact ofpacts[], size_t ofpacts_len, + struct ofpbuf *openflow) { const struct ofpact *a; @@ -858,6 +1283,136 @@ ofpacts_to_openflow(const struct ofpact ofpacts[], size_t ofpacts_len, } } +/* 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; + + oao = ofputil_put_OFPAT11_OUTPUT(out); + oao->port = ofputil_port_to_ofp11(output->port); + oao->max_len = htons(output->max_len); +} + +static void +ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out) +{ + 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: + ofputil_put_OFPAT11_SET_VLAN_VID(out)->vlan_vid + = htons(ofpact_get_SET_VLAN_VID(a)->vlan_vid); + break; + + case OFPACT_SET_VLAN_PCP: + ofputil_put_OFPAT11_SET_VLAN_PCP(out)->vlan_pcp + = ofpact_get_SET_VLAN_PCP(a)->vlan_pcp; + break; + + case OFPACT_STRIP_VLAN: + /* XXX */ + 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; + + 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; + + case OFPACT_SET_IPV4_SRC: + ofputil_put_OFPAT11_SET_NW_SRC(out)->nw_addr + = ofpact_get_SET_IPV4_SRC(a)->ipv4; + break; + + case OFPACT_SET_IPV4_DST: + ofputil_put_OFPAT11_SET_NW_DST(out)->nw_addr + = ofpact_get_SET_IPV4_DST(a)->ipv4; + break; + + case OFPACT_SET_IPV4_DSCP: + ofputil_put_OFPAT11_SET_NW_TOS(out)->nw_tos + = ofpact_get_SET_IPV4_DSCP(a)->dscp; + break; + + 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; + + 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; + + case OFPACT_CONTROLLER: + case OFPACT_OUTPUT_REG: + case OFPACT_BUNDLE: + case OFPACT_REG_MOVE: + case OFPACT_REG_LOAD: + case OFPACT_DEC_TTL: + 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_AUTOPATH: + case OFPACT_NOTE: + case OFPACT_EXIT: + ofpact_to_nxast(a, out); + break; + } +} + +/* Converts the ofpacts in 'ofpacts' (terminated by OFPACT_END) into OpenFlow + * 1.1 actions in 'openflow', appending the actions to any existing data in + * 'openflow'. */ +void +ofpacts_put_openflow11_actions(const struct ofpact ofpacts[], + size_t ofpacts_len, struct ofpbuf *openflow) +{ + const struct ofpact *a; + + OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + ofpact_to_openflow11(a, openflow); + } +} + +void +ofpacts_put_openflow11_instructions(const struct ofpact ofpacts[], + size_t ofpacts_len, + struct ofpbuf *openflow) +{ + struct ofp11_instruction_actions *oia; + size_t ofs; + + /* Put an OFPIT11_APPLY_ACTIONS instruction and fill it in. */ + ofs = openflow->size; + instruction_put_OFPIT11_APPLY_ACTIONS(openflow); + ofpacts_put_openflow11_actions(ofpacts, ofpacts_len, openflow); + + /* 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; + } +} + /* Returns true if 'action' outputs to 'port', false otherwise. */ static bool ofpact_outputs_to_port(const struct ofpact *ofpact, uint16_t port) diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index 59b9846b..7c9cb055 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -381,15 +381,26 @@ struct ofpact_note { }; /* Converting OpenFlow to ofpacts. */ -enum ofperr ofpacts_pull_openflow(struct ofpbuf *openflow, - unsigned int actions_len, - struct ofpbuf *ofpacts); +enum ofperr ofpacts_pull_openflow10(struct ofpbuf *openflow, + unsigned int actions_len, + struct ofpbuf *ofpacts); +enum ofperr ofpacts_pull_openflow11_actions(struct ofpbuf *openflow, + unsigned int actions_len, + struct ofpbuf *ofpacts); +enum ofperr ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow, + unsigned int instructions_len, + struct ofpbuf *ofpacts); enum ofperr ofpacts_check(const struct ofpact[], size_t ofpacts_len, const struct flow *, int max_ports); /* Converting ofpacts to OpenFlow. */ -void ofpacts_to_openflow(const struct ofpact[], size_t ofpacts_len, - struct ofpbuf *openflow); +void ofpacts_put_openflow10(const struct ofpact[], size_t ofpacts_len, + struct ofpbuf *openflow); +void ofpacts_put_openflow11_actions(const struct ofpact[], size_t ofpacts_len, + struct ofpbuf *openflow); +void ofpacts_put_openflow11_instructions(const struct ofpact[], + size_t ofpacts_len, + struct ofpbuf *openflow); /* Working with ofpacts. */ bool ofpacts_output_to_port(const struct ofpact[], size_t ofpacts_len, diff --git a/lib/ofp-errors.h b/lib/ofp-errors.h index 5f908db6..61cef418 100644 --- a/lib/ofp-errors.h +++ b/lib/ofp-errors.h @@ -193,7 +193,7 @@ enum ofperr { /* ## --------------------- ## */ /* OF1.1+(3). Error in instruction list. */ - OFPERR_OFPET_BAD_INSTRUCTION, + OFPERR_OFPIT_BAD_INSTRUCTION, /* OF1.1+(3,0). Unknown instruction. */ OFPERR_OFPBIC_UNKNOWN_INST, @@ -222,6 +222,9 @@ enum ofperr { /* OF1.2+(3,8). Permissions error. */ OFPERR_OFPBIC_EPERM, + /* NX1.1+(3,256). Duplicate instruction type in set of instructions. */ + OFPERR_NXBIC_DUP_TYPE, + /* ## --------------- ## */ /* ## OFPET_BAD_MATCH ## */ /* ## --------------- ## */ diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index 6236e500..922e2968 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -293,10 +293,12 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow, NOT_REACHED(); case OFPUTIL_OFPAT10_OUTPUT: + case OFPUTIL_OFPAT11_OUTPUT: parse_output(arg, ofpacts); break; case OFPUTIL_OFPAT10_SET_VLAN_VID: + case OFPUTIL_OFPAT11_SET_VLAN_VID: vid = str_to_u32(arg); if (vid & ~VLAN_VID_MASK) { ovs_fatal(0, "%s: not a valid VLAN VID", arg); @@ -305,6 +307,7 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow, break; case OFPUTIL_OFPAT10_SET_VLAN_PCP: + case OFPUTIL_OFPAT11_SET_VLAN_PCP: pcp = str_to_u32(arg); if (pcp & ~7) { ovs_fatal(0, "%s: not a valid VLAN PCP", arg); @@ -317,24 +320,29 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow, break; case OFPUTIL_OFPAT10_SET_DL_SRC: + case OFPUTIL_OFPAT11_SET_DL_SRC: str_to_mac(arg, ofpact_put_SET_ETH_SRC(ofpacts)->mac); break; case OFPUTIL_OFPAT10_SET_DL_DST: + case OFPUTIL_OFPAT11_SET_DL_DST: str_to_mac(arg, ofpact_put_SET_ETH_DST(ofpacts)->mac); break; case OFPUTIL_OFPAT10_SET_NW_SRC: + case OFPUTIL_OFPAT11_SET_NW_SRC: str_to_ip(arg, &ip); ofpact_put_SET_IPV4_SRC(ofpacts)->ipv4 = ip; break; case OFPUTIL_OFPAT10_SET_NW_DST: + case OFPUTIL_OFPAT11_SET_NW_DST: str_to_ip(arg, &ip); ofpact_put_SET_IPV4_DST(ofpacts)->ipv4 = ip; break; case OFPUTIL_OFPAT10_SET_NW_TOS: + case OFPUTIL_OFPAT11_SET_NW_TOS: tos = str_to_u32(arg); if (tos & ~IP_DSCP_MASK) { ovs_fatal(0, "%s: not a valid TOS", arg); @@ -343,10 +351,12 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow, break; case OFPUTIL_OFPAT10_SET_TP_SRC: + case OFPUTIL_OFPAT11_SET_TP_SRC: ofpact_put_SET_L4_SRC_PORT(ofpacts)->port = str_to_u32(arg); break; case OFPUTIL_OFPAT10_SET_TP_DST: + case OFPUTIL_OFPAT11_SET_TP_DST: ofpact_put_SET_L4_DST_PORT(ofpacts)->port = str_to_u32(arg); break; diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 4864815c..834bb622 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -1687,7 +1687,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm, ofputil_normalize_rule(&fm->cr); /* Now get the actions. */ - error = ofpacts_pull_openflow(&b, b.size, ofpacts); + error = ofpacts_pull_openflow10(&b, b.size, ofpacts); if (error) { return error; } @@ -1714,7 +1714,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm, if (error) { return error; } - error = ofpacts_pull_openflow(&b, b.size, ofpacts); + error = ofpacts_pull_openflow10(&b, b.size, ofpacts); if (error) { return error; } @@ -1805,7 +1805,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm, } if (fm->ofpacts) { - ofpacts_to_openflow(fm->ofpacts, fm->ofpacts_len, msg); + ofpacts_put_openflow10(fm->ofpacts, fm->ofpacts_len, msg); } update_openflow_length(msg); return msg; @@ -2048,7 +2048,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, return EINVAL; } - if (ofpacts_pull_openflow(msg, length - sizeof *ofs, ofpacts)) { + if (ofpacts_pull_openflow10(msg, length - sizeof *ofs, ofpacts)) { return EINVAL; } @@ -2088,7 +2088,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, } actions_len = length - sizeof *nfs - ROUND_UP(match_len, 8); - if (ofpacts_pull_openflow(msg, actions_len, ofpacts)) { + if (ofpacts_pull_openflow10(msg, actions_len, ofpacts)) { return EINVAL; } @@ -2159,7 +2159,7 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs, htonll(unknown_to_zero(fs->packet_count))); put_32aligned_be64(&ofs->byte_count, htonll(unknown_to_zero(fs->byte_count))); - ofpacts_to_openflow(fs->ofpacts, fs->ofpacts_len, reply); + ofpacts_put_openflow10(fs->ofpacts, fs->ofpacts_len, reply); ofs = ofpbuf_at_assert(reply, start_ofs, sizeof *ofs); ofs->length = htons(reply->size - start_ofs); @@ -2184,7 +2184,7 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs, nfs->cookie = fs->cookie; nfs->packet_count = htonll(fs->packet_count); nfs->byte_count = htonll(fs->byte_count); - ofpacts_to_openflow(fs->ofpacts, fs->ofpacts_len, reply); + ofpacts_put_openflow10(fs->ofpacts, fs->ofpacts_len, reply); nfs = ofpbuf_at_assert(reply, start_ofs, sizeof *nfs); nfs->length = htons(reply->size - start_ofs); @@ -2546,7 +2546,7 @@ ofputil_decode_packet_out(struct ofputil_packet_out *po, ofpbuf_use_const(&b, opo, ntohs(opo->header.length)); ofpbuf_pull(&b, sizeof *opo); - error = ofpacts_pull_openflow(&b, ntohs(opo->actions_len), ofpacts); + error = ofpacts_pull_openflow10(&b, ntohs(opo->actions_len), ofpacts); if (error) { return error; } @@ -3107,7 +3107,7 @@ ofputil_encode_packet_out(const struct ofputil_packet_out *po) msg = ofpbuf_new(size); put_openflow(sizeof *opo, OFPT_PACKET_OUT, msg); - ofpacts_to_openflow(po->ofpacts, po->ofpacts_len, msg); + ofpacts_put_openflow10(po->ofpacts, po->ofpacts_len, msg); opo = msg->data; opo->buffer_id = htonl(po->buffer_id); @@ -3662,7 +3662,8 @@ ofputil_action_code_from_name(const char *name) { static const char *names[OFPUTIL_N_ACTIONS] = { NULL, -#define OFPAT10_ACTION(ENUM, STRUCT, NAME) NAME, +#define OFPAT10_ACTION(ENUM, STRUCT, NAME) NAME, +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) NAME, #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) NAME, #include "ofp-util.def" }; @@ -3691,6 +3692,7 @@ ofputil_put_action(enum ofputil_action_code code, struct ofpbuf *buf) #define OFPAT10_ACTION(ENUM, STRUCT, NAME) \ case OFPUTIL_##ENUM: return ofputil_put_##ENUM(buf); +#define OFPAT11_ACTION OFPAT10_ACTION #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) \ case OFPUTIL_##ENUM: return ofputil_put_##ENUM(buf); #include "ofp-util.def" @@ -3714,6 +3716,7 @@ ofputil_put_action(enum ofputil_action_code code, struct ofpbuf *buf) ofputil_init_##ENUM(s); \ return s; \ } +#define OFPAT11_ACTION OFPAT10_ACTION #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) \ void \ ofputil_init_##ENUM(struct STRUCT *s) \ diff --git a/lib/ofp-util.def b/lib/ofp-util.def index 8739ac07..974cd8f8 100644 --- a/lib/ofp-util.def +++ b/lib/ofp-util.def @@ -3,7 +3,7 @@ #ifndef OFPAT10_ACTION #define OFPAT10_ACTION(ENUM, STRUCT, NAME) #endif -OFPAT10_ACTION(OFPAT10_OUTPUT, ofp_action_output, "output") +OFPAT10_ACTION(OFPAT10_OUTPUT, ofp10_action_output, "output") OFPAT10_ACTION(OFPAT10_SET_VLAN_VID, ofp_action_vlan_vid, "mod_vlan_vid") OFPAT10_ACTION(OFPAT10_SET_VLAN_PCP, ofp_action_vlan_pcp, "mod_vlan_pcp") OFPAT10_ACTION(OFPAT10_STRIP_VLAN, ofp_action_header, "strip_vlan") @@ -15,7 +15,26 @@ OFPAT10_ACTION(OFPAT10_SET_NW_TOS, ofp_action_nw_tos, "mod_nw_tos") OFPAT10_ACTION(OFPAT10_SET_TP_SRC, ofp_action_tp_port, "mod_tp_src") OFPAT10_ACTION(OFPAT10_SET_TP_DST, ofp_action_tp_port, "mod_tp_dst") OFPAT10_ACTION(OFPAT10_ENQUEUE, ofp_action_enqueue, "enqueue") -#undef OFPAT10_ACTION + +#ifndef OFPAT11_ACTION +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) +#endif +OFPAT11_ACTION(OFPAT11_OUTPUT, ofp11_action_output, "output") +OFPAT11_ACTION(OFPAT11_SET_VLAN_VID, ofp_action_vlan_vid, "mod_vlan_vid") +OFPAT11_ACTION(OFPAT11_SET_VLAN_PCP, ofp_action_vlan_pcp, "mod_vlan_pcp") +OFPAT11_ACTION(OFPAT11_SET_DL_SRC, ofp_action_dl_addr, "mod_dl_src") +OFPAT11_ACTION(OFPAT11_SET_DL_DST, ofp_action_dl_addr, "mod_dl_dst") +OFPAT11_ACTION(OFPAT11_SET_NW_SRC, ofp_action_nw_addr, "mod_nw_src") +OFPAT11_ACTION(OFPAT11_SET_NW_DST, ofp_action_nw_addr, "mod_nw_dst") +OFPAT11_ACTION(OFPAT11_SET_NW_TOS, ofp_action_nw_tos, "mod_nw_tos") +//OFPAT11_ACTION(OFPAT11_SET_NW_ECN, ofp11_action_nw_ecn, "mod_nw_ecn") +OFPAT11_ACTION(OFPAT11_SET_TP_SRC, ofp_action_tp_port, "mod_tp_src") +OFPAT11_ACTION(OFPAT11_SET_TP_DST, ofp_action_tp_port, "mod_tp_dst") +//OFPAT11_ACTION(OFPAT11_PUSH_VLAN, ofp11_action_push, "push_vlan") +//OFPAT11_ACTION(OFPAT11_POP_VLAN, ofp_action_header, "pop_vlan") +//OFPAT11_ACTION(OFPAT11_SET_QUEUE, ofp11_action_set_queue, "set_queue") +//OFPAT11_ACTION(OFPAT11_SET_NW_TTL, ofp11_action_nw_ttl, "set_nw_ttl") +//OFPAT11_ACTION(OFPAT11_DEC_NW_TTL, ofp_action_header, "dec_ttl") #ifndef NXAST_ACTION #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) @@ -39,4 +58,7 @@ NXAST_ACTION(NXAST_EXIT, nx_action_header, 0, "exit") NXAST_ACTION(NXAST_DEC_TTL, nx_action_header, 0, "dec_ttl") NXAST_ACTION(NXAST_FIN_TIMEOUT, nx_action_fin_timeout, 0, "fin_timeout") NXAST_ACTION(NXAST_CONTROLLER, nx_action_controller, 0, "controller") + +#undef OFPAT10_ACTION +#undef OFPAT11_ACTION #undef NXAST_ACTION diff --git a/lib/ofp-util.h b/lib/ofp-util.h index 30e04c4b..703de501 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -598,14 +598,16 @@ bool ofputil_frag_handling_from_string(const char *, enum ofp_config_flags *); */ enum OVS_PACKED_ENUM ofputil_action_code { OFPUTIL_ACTION_INVALID, -#define OFPAT10_ACTION(ENUM, STRUCT, NAME) OFPUTIL_##ENUM, +#define OFPAT10_ACTION(ENUM, STRUCT, NAME) OFPUTIL_##ENUM, +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) OFPUTIL_##ENUM, #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) OFPUTIL_##ENUM, #include "ofp-util.def" }; /* The number of values of "enum ofputil_action_code". */ enum { -#define OFPAT10_ACTION(ENUM, STRUCT, NAME) + 1 +#define OFPAT10_ACTION(ENUM, STRUCT, NAME) + 1 +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) + 1 #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) + 1 OFPUTIL_N_ACTIONS = 1 #include "ofp-util.def" @@ -632,6 +634,9 @@ void *ofputil_put_action(enum ofputil_action_code, struct ofpbuf *buf); #define OFPAT10_ACTION(ENUM, STRUCT, NAME) \ void ofputil_init_##ENUM(struct STRUCT *); \ struct STRUCT *ofputil_put_##ENUM(struct ofpbuf *); +#define OFPAT11_ACTION(ENUM, STRUCT, NAME) \ + void ofputil_init_##ENUM(struct STRUCT *); \ + struct STRUCT *ofputil_put_##ENUM(struct ofpbuf *); #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) \ void ofputil_init_##ENUM(struct STRUCT *); \ struct STRUCT *ofputil_put_##ENUM(struct ofpbuf *); diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c index 87dc2ad3..18b80b87 100644 --- a/ofproto/connmgr.c +++ b/ofproto/connmgr.c @@ -1372,7 +1372,7 @@ schedule_packet_in(struct ofconn *ofconn, struct ofputil_packet_in pin) pin.send_len = pin.packet_len; } else { /* Caller should have initialized 'send_len' to 'max_len' specified in - * struct ofp_action_output. */ + * output action. */ } if (pin.buffer_id != UINT32_MAX) { pin.send_len = MIN(pin.send_len, ofconn->miss_send_len); diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at index 6f702136..ba8d309d 100644 --- a/tests/ofp-actions.at +++ b/tests/ofp-actions.at @@ -119,3 +119,192 @@ AT_CHECK( [ovs-ofctl '-vPATTERN:console:%c|%p|%m' parse-ofp10-actions < input.txt], [0], [expout], [experr]) AT_CLEANUP + +AT_SETUP([OpenFlow 1.1 action translation]) +AT_KEYWORDS([OF1.1]) +AT_DATA([test-data], [dnl +# actions=LOCAL +0000 0010 fffffffe 04d2 000000000000 + +# actions=CONTROLLER:1234 +0000 0010 fffffffd 04d2 000000000000 + +# actions=mod_vlan_vid:9 +0001 0008 0009 0000 + +# actions=mod_vlan_pcp:6 +0002 0008 06 000000 + +# actions=mod_dl_src:00:11:22:33:44:55 +0003 0010 001122334455 000000000000 + +# actions=mod_dl_dst:10:20:30:40:50:60 +0004 0010 102030405060 000000000000 + +# actions=mod_nw_src:1.2.3.4 +0005 0008 01020304 + +# actions=mod_nw_dst:192.168.0.1 +0006 0008 c0a80001 + +# actions=mod_nw_tos:48 +0007 0008 30 000000 + +# actions=mod_tp_src:80 +0009 0008 0050 0000 + +# actions=mod_tp_dst:443 +000a 0008 01bb 0000 + +# actions=resubmit:5 +ffff 0010 00002320 0001 0005 00000000 + +# actions=set_tunnel:0x12345678 +ffff 0010 00002320 0002 0000 12345678 + +# actions=set_queue:2309737729 +ffff 0010 00002320 0004 0000 89abcd01 + +# actions=pop_queue +ffff 0010 00002320 0005 000000000000 + +# actions=move:NXM_OF_IN_PORT[]->NXM_OF_VLAN_TCI[] +ffff 0018 00002320 0006 0010 0000 0000 00000002 00000802 + +# actions=load:0xf009->NXM_OF_VLAN_TCI[] +ffff 0018 00002320 0007 000f 00000802 000000000000f009 + +# actions=note:11.e9.9a.ad.67.f3 +ffff 0010 00002320 0008 11e99aad67f3 + +# actions=set_tunnel64:0xc426384d49c53d60 +ffff 0018 00002320 0009 000000000000 c426384d49c53d60 + +# actions=set_tunnel64:0x885f3298 +ffff 0018 00002320 0009 000000000000 00000000885f3298 + +# actions=multipath(eth_src,50,modulo_n,1,0,NXM_NX_REG0[]) +ffff 0020 00002320 000a 0000 0032 0000 0000 0000 0000 0000 0000 001f 00010004 + +# actions=autopath(2,NXM_NX_REG0[2..30]) +ffff 0018 00002320 000b 009c 00010004 00000002 00000000 + +# actions=bundle(eth_src,0,hrw,ofport,slaves:4,8) +ffff 0028 00002320 000c 0001 0000 0000 00000002 0002 0000 00000000 00000000 dnl +0004 0008 00000000 + +# actions=bundle_load(eth_src,0,hrw,ofport,NXM_NX_REG0[],slaves:4,8) +ffff 0028 00002320 000d 0001 0000 0000 00000002 0002 001f 00010004 00000000 dnl +0004 0008 00000000 + +# actions=resubmit(10,5) +ffff 0010 00002320 000e 000a 05 000000 + +# actions=output:NXM_NX_REG1[5..10] +ffff 0018 00002320 000f 0145 00010204 ffff 000000000000 + +# actions=learn(table=2,idle_timeout=10,hard_timeout=20,fin_idle_timeout=2,fin_hard_timeout=4,priority=80,cookie=0x123456789abcdef0,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[]) +ffff 0048 00002320 0010 000a 0014 0050 123456789abcdef0 0000 02 00 0002 0004 dnl +000c 00000802 0000 00000802 0000 dnl +0030 00000406 0000 00000206 0000 dnl +1010 00000002 0000 dnl +00000000 + +# actions=exit +ffff 0010 00002320 0011 000000000000 + +# actions=dec_ttl +ffff 0010 00002320 0012 000000000000 + +# actions=fin_timeout(idle_timeout=10,hard_timeout=20) +ffff 0010 00002320 0013 000a 0014 0000 + +# actions=controller(reason=invalid_ttl,max_len=1234,id=5678) +ffff 0010 00002320 0014 04d2 162e 02 00 + +]) +sed '/^[[#&]]/d' < test-data > input.txt +sed -n 's/^# //p; /^$/p' < test-data > expout +sed -n 's/^& //p' < test-data > experr +AT_CAPTURE_FILE([input.txt]) +AT_CAPTURE_FILE([expout]) +AT_CAPTURE_FILE([experr]) +AT_CHECK( + [ovs-ofctl '-vPATTERN:console:%c|%p|%m' parse-ofp11-actions < input.txt], + [0], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([OpenFlow 1.1 instruction translation]) +AT_KEYWORDS([OF1.1]) +AT_DATA([test-data], [dnl +# actions=LOCAL +0004 0018 00000000 dnl +0000 0010 fffffffe 04d2 000000000000 + +dnl Check that an empty Apply-Actions instruction gets dropped. +# actions=drop +# 0: 00 -> (none) +# 1: 04 -> (none) +# 2: 00 -> (none) +# 3: 08 -> (none) +# 4: 00 -> (none) +# 5: 00 -> (none) +# 6: 00 -> (none) +# 7: 00 -> (none) +0004 0008 00000000 + +# bad OF1.1 instructions: NXBIC_DUP_TYPE +0004 0008 00000000 0004 0008 00000000 + +dnl Instructions not multiple of 8 in length. +& ofp_actions|WARN|OpenFlow message instructions length 9 is not a multiple of 8 +# bad OF1.1 instructions: OFPBIC_BAD_LEN +0004 0009 01 00000000 + +dnl Goto-Table instruction too long. +# bad OF1.1 instructions: OFPBIC_BAD_LEN +0001 0010 01 000000 0000000000000000 + +dnl Goto-Table not supported yet. +# bad OF1.1 instructions: OFPBIC_UNSUP_INST +0001 0008 01 000000 + +dnl Write-Metadata not supported yet. +# bad OF1.1 instructions: OFPBIC_UNSUP_INST +0002 0018 00000000 fedcba9876543210 ffffffffffffffff + +dnl Write-Metadata too short. +# bad OF1.1 instructions: OFPBIC_BAD_LEN +0002 0010 00000000 fedcba9876543210 + +dnl Write-Metadata too long. +# bad OF1.1 instructions: OFPBIC_BAD_LEN +0002 0020 00000000 fedcba9876543210 ffffffffffffffff 0000000000000000 + +dnl Write-Actions not supported yet. +# bad OF1.1 instructions: OFPBIC_UNSUP_INST +0003 0008 01 000000 + +dnl Clear-Actions not supported yet. +# bad OF1.1 instructions: OFPBIC_UNSUP_INST +0005 0008 01 000000 + +dnl Experimenter actions not supported yet. +# bad OF1.1 instructions: OFPBIC_BAD_EXPERIMENTER +ffff 0008 01 000000 + +dnl Bad instruction number (0 not assigned). +# bad OF1.1 instructions: OFPBIC_UNKNOWN_INST +0000 0008 01 000000 + +]) +sed '/^[[#&]]/d' < test-data > input.txt +sed -n 's/^# //p; /^$/p' < test-data > expout +sed -n 's/^& //p' < test-data > experr +AT_CAPTURE_FILE([input.txt]) +AT_CAPTURE_FILE([expout]) +AT_CAPTURE_FILE([experr]) +AT_CHECK( + [ovs-ofctl '-vPATTERN:console:%c|%p|%m' parse-ofp11-instructions < input.txt], + [0], [expout], [experr]) +AT_CLEANUP diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index b95f5da5..7b0b2204 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -2046,7 +2046,7 @@ do_parse_ofp10_actions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) /* Convert to ofpacts. */ ofpbuf_init(&ofpacts, 0); size = of10_in.size; - error = ofpacts_pull_openflow(&of10_in, of10_in.size, &ofpacts); + error = ofpacts_pull_openflow10(&of10_in, of10_in.size, &ofpacts); if (error) { printf("bad OF1.1 actions: %s\n\n", ofperr_get_name(error)); ofpbuf_uninit(&ofpacts); @@ -2063,7 +2063,7 @@ do_parse_ofp10_actions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) /* Convert back to ofp10 actions and print differences from input. */ ofpbuf_init(&of10_out, 0); - ofpacts_to_openflow(ofpacts.data, ofpacts.size, &of10_out); + ofpacts_put_openflow10(ofpacts.data, ofpacts.size, &of10_out); print_differences(of10_in.data, of10_in.size, of10_out.data, of10_out.size); @@ -2091,7 +2091,6 @@ do_parse_ofp11_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) struct ofp11_match match_out; struct cls_rule rule; enum ofperr error; - int i; /* Parse hex bytes. */ ofpbuf_init(&match_in, 0); @@ -2118,17 +2117,129 @@ do_parse_ofp11_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) /* Convert back to ofp11_match and print differences from input. */ ofputil_cls_rule_to_ofp11_match(&rule, &match_out); - for (i = 0; i < sizeof match_out; i++) { - uint8_t in = ((const uint8_t *) match_in.data)[i]; - uint8_t out = ((const uint8_t *) &match_out)[i]; + print_differences(match_in.data, match_in.size, + &match_out, sizeof match_out); + putchar('\n'); - if (in != out) { - printf("%2d: %02"PRIx8" -> %02"PRIx8"\n", i, in, out); - } + ofpbuf_uninit(&match_in); + } + ds_destroy(&in); +} + +/* "parse-ofp11-actions": reads a series of OpenFlow 1.1 action specifications + * as hex bytes from stdin, converts them to ofpacts, prints them as strings + * on stdout, and then converts them back to hex bytes and prints any + * differences from the input. */ +static void +do_parse_ofp11_actions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + struct ds in; + + ds_init(&in); + while (!ds_get_preprocessed_line(&in, stdin)) { + struct ofpbuf of11_out; + struct ofpbuf of11_in; + struct ofpbuf ofpacts; + enum ofperr error; + size_t size; + struct ds s; + + /* Parse hex bytes. */ + ofpbuf_init(&of11_in, 0); + if (ofpbuf_put_hex(&of11_in, ds_cstr(&in), NULL)[0] != '\0') { + ovs_fatal(0, "Trailing garbage in hex data"); } + + /* Convert to ofpacts. */ + ofpbuf_init(&ofpacts, 0); + size = of11_in.size; + error = ofpacts_pull_openflow11_actions(&of11_in, of11_in.size, + &ofpacts); + if (error) { + printf("bad OF1.1 actions: %s\n\n", ofperr_get_name(error)); + ofpbuf_uninit(&ofpacts); + ofpbuf_uninit(&of11_in); + continue; + } + ofpbuf_push_uninit(&of11_in, size); + + /* Print cls_rule. */ + ds_init(&s); + ofpacts_format(ofpacts.data, ofpacts.size, &s); + puts(ds_cstr(&s)); + ds_destroy(&s); + + /* Convert back to ofp11 actions and print differences from input. */ + ofpbuf_init(&of11_out, 0); + ofpacts_put_openflow11_actions(ofpacts.data, ofpacts.size, &of11_out); + + print_differences(of11_in.data, of11_in.size, + of11_out.data, of11_out.size); putchar('\n'); - ofpbuf_uninit(&match_in); + ofpbuf_uninit(&ofpacts); + ofpbuf_uninit(&of11_in); + ofpbuf_uninit(&of11_out); + } + ds_destroy(&in); +} + +/* "parse-ofp11-instructions": reads a series of OpenFlow 1.1 instruction + * specifications as hex bytes from stdin, converts them to ofpacts, prints + * them as strings on stdout, and then converts them back to hex bytes and + * prints any differences from the input. */ +static void +do_parse_ofp11_instructions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + struct ds in; + + ds_init(&in); + while (!ds_get_preprocessed_line(&in, stdin)) { + struct ofpbuf of11_out; + struct ofpbuf of11_in; + struct ofpbuf ofpacts; + enum ofperr error; + size_t size; + struct ds s; + + /* Parse hex bytes. */ + ofpbuf_init(&of11_in, 0); + if (ofpbuf_put_hex(&of11_in, ds_cstr(&in), NULL)[0] != '\0') { + ovs_fatal(0, "Trailing garbage in hex data"); + } + + /* Convert to ofpacts. */ + ofpbuf_init(&ofpacts, 0); + size = of11_in.size; + error = ofpacts_pull_openflow11_instructions(&of11_in, of11_in.size, + &ofpacts); + if (error) { + printf("bad OF1.1 instructions: %s\n\n", ofperr_get_name(error)); + ofpbuf_uninit(&ofpacts); + ofpbuf_uninit(&of11_in); + continue; + } + ofpbuf_push_uninit(&of11_in, size); + + /* Print cls_rule. */ + ds_init(&s); + ofpacts_format(ofpacts.data, ofpacts.size, &s); + puts(ds_cstr(&s)); + ds_destroy(&s); + + /* Convert back to ofp11 instructions and print differences from + * input. */ + ofpbuf_init(&of11_out, 0); + ofpacts_put_openflow11_instructions(ofpacts.data, ofpacts.size, + &of11_out); + + print_differences(of11_in.data, of11_in.size, + of11_out.data, of11_out.size); + putchar('\n'); + + ofpbuf_uninit(&ofpacts); + ofpbuf_uninit(&of11_in); + ofpbuf_uninit(&of11_out); } ds_destroy(&in); } @@ -2211,6 +2322,8 @@ static const struct command all_commands[] = { { "parse-oxm", 0, 0, do_parse_oxm }, { "parse-ofp10-actions", 0, 0, do_parse_ofp10_actions }, { "parse-ofp11-match", 0, 0, do_parse_ofp11_match }, + { "parse-ofp11-actions", 0, 0, do_parse_ofp11_actions }, + { "parse-ofp11-instructions", 0, 0, do_parse_ofp11_instructions }, { "print-error", 1, 1, do_print_error }, { "ofp-print", 1, 2, do_ofp_print },