/* * Copyright (c) 2008-2017 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "openvswitch/ofp-packet.h" #include #include "dp-packet.h" #include "nx-match.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-errors.h" #include "openvswitch/ofp-msgs.h" #include "openvswitch/ofp-parse.h" #include "openvswitch/ofp-print.h" #include "openvswitch/ofp-port.h" #include "openvswitch/ofp-prop.h" #include "openvswitch/ofp-table.h" #include "openvswitch/ofpbuf.h" #include "openvswitch/vlog.h" #include "util.h" #include "uuid.h" VLOG_DEFINE_THIS_MODULE(ofp_packet); static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); const char * ofputil_packet_in_format_to_string(enum ofputil_packet_in_format format) { switch (format) { case OFPUTIL_PACKET_IN_STD: return "standard"; case OFPUTIL_PACKET_IN_NXT: return "nxt_packet_in"; case OFPUTIL_PACKET_IN_NXT2: return "nxt_packet_in2"; default: OVS_NOT_REACHED(); } } int ofputil_packet_in_format_from_string(const char *s) { return (!strcmp(s, "standard") || !strcmp(s, "openflow10") ? OFPUTIL_PACKET_IN_STD : !strcmp(s, "nxt_packet_in") || !strcmp(s, "nxm") ? OFPUTIL_PACKET_IN_NXT : !strcmp(s, "nxt_packet_in2") ? OFPUTIL_PACKET_IN_NXT2 : -1); } struct ofpbuf * ofputil_encode_set_packet_in_format(enum ofp_version ofp_version, enum ofputil_packet_in_format format) { struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_SET_PACKET_IN_FORMAT, ofp_version, 0); ovs_be32 *spif = ofpbuf_put_uninit(msg, sizeof *spif); *spif = htonl(format); return msg; } enum ofperr ofputil_decode_set_packet_in_format(const struct ofp_header *oh, enum ofputil_packet_in_format *format) { struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length)); ovs_assert(ofpraw_pull_assert(&b) == OFPRAW_NXT_SET_PACKET_IN_FORMAT); ovs_be32 *spifp = ofpbuf_pull(&b, sizeof *spifp); uint32_t spif = ntohl(*spifp); switch (spif) { case OFPUTIL_PACKET_IN_STD: case OFPUTIL_PACKET_IN_NXT: case OFPUTIL_PACKET_IN_NXT2: *format = spif; return 0; default: VLOG_WARN_RL(&rl, "NXT_SET_PACKET_IN_FORMAT message specified invalid " "packet-in format %"PRIu32, spif); return OFPERR_OFPBRC_EPERM; } } /* The caller has done basic initialization of '*pin'; the other output * arguments needs to be initialized. */ static enum ofperr decode_nx_packet_in2(const struct ofp_header *oh, bool loose, const struct tun_table *tun_table, const struct vl_mff_map *vl_mff_map, struct ofputil_packet_in *pin, size_t *total_len, uint32_t *buffer_id, struct ofpbuf *continuation) { *total_len = 0; *buffer_id = UINT32_MAX; struct ofpbuf properties; ofpbuf_use_const(&properties, oh, ntohs(oh->length)); ofpraw_pull_assert(&properties); while (properties.size > 0) { struct ofpbuf payload; uint64_t type; enum ofperr error = ofpprop_pull(&properties, &payload, &type); if (error) { return error; } switch (type) { case NXPINT_PACKET: pin->packet = payload.msg; pin->packet_len = ofpbuf_msgsize(&payload); break; case NXPINT_FULL_LEN: { uint32_t u32; error = ofpprop_parse_u32(&payload, &u32); if (!error) { *total_len = u32; } break; } case NXPINT_BUFFER_ID: error = ofpprop_parse_u32(&payload, buffer_id); break; case NXPINT_TABLE_ID: error = ofpprop_parse_u8(&payload, &pin->table_id); break; case NXPINT_COOKIE: error = ofpprop_parse_be64(&payload, &pin->cookie); break; case NXPINT_REASON: { uint8_t reason; error = ofpprop_parse_u8(&payload, &reason); if (!error) { pin->reason = reason; } break; } case NXPINT_METADATA: error = oxm_decode_match(payload.msg, ofpbuf_msgsize(&payload), loose, tun_table, vl_mff_map, &pin->flow_metadata); pin->flow_metadata.flow.tunnel.metadata.tab = tun_table; break; case NXPINT_USERDATA: pin->userdata = payload.msg; pin->userdata_len = ofpbuf_msgsize(&payload); break; case NXPINT_CONTINUATION: if (continuation) { error = ofpprop_parse_nested(&payload, continuation); } break; default: error = OFPPROP_UNKNOWN(loose, "NX_PACKET_IN2", type); break; } if (error) { return error; } } if (!pin->packet_len) { VLOG_WARN_RL(&rl, "NXT_PACKET_IN2 lacks packet"); return OFPERR_OFPBRC_BAD_LEN; } else if (!*total_len) { *total_len = pin->packet_len; } else if (*total_len < pin->packet_len) { VLOG_WARN_RL(&rl, "NXT_PACKET_IN2 claimed full_len < len"); return OFPERR_OFPBRC_BAD_LEN; } return 0; } /* Decodes the packet-in message starting at 'oh' into '*pin'. Populates * 'pin->packet' and 'pin->packet_len' with the part of the packet actually * included in the message. If 'total_lenp' is nonnull, populates * '*total_lenp' with the original length of the packet (which is larger than * 'packet->len' if only part of the packet was included). If 'buffer_idp' is * nonnull, stores the packet's buffer ID in '*buffer_idp' (UINT32_MAX if it * was not buffered). * * Populates 'continuation', if nonnull, with the continuation data from the * packet-in (an empty buffer, if 'oh' did not contain continuation data). The * format of this data is supposed to be opaque to anything other than * ovs-vswitchd, so that in any other process the only reasonable use of this * data is to be copied into an NXT_RESUME message via ofputil_encode_resume(). * * This function points 'pin->packet' into 'oh', so the caller should not free * it separately from the original OpenFlow message. This is also true for * 'pin->userdata' (which could also end up NULL if there is no userdata). * * 'vl_mff_map' is an optional parameter that is used to validate the length * of variable length mf_fields in 'match'. If it is not provided, the * default mf_fields with maximum length will be used. * * Returns 0 if successful, otherwise an OpenFlow error code. */ enum ofperr ofputil_decode_packet_in(const struct ofp_header *oh, bool loose, const struct tun_table *tun_table, const struct vl_mff_map *vl_mff_map, struct ofputil_packet_in *pin, size_t *total_lenp, uint32_t *buffer_idp, struct ofpbuf *continuation) { uint32_t buffer_id; size_t total_len; memset(pin, 0, sizeof *pin); pin->cookie = OVS_BE64_MAX; if (continuation) { ofpbuf_use_const(continuation, NULL, 0); } struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length)); enum ofpraw raw = ofpraw_pull_assert(&b); if (raw == OFPRAW_OFPT13_PACKET_IN || raw == OFPRAW_OFPT12_PACKET_IN) { const struct ofp12_packet_in *opi = ofpbuf_pull(&b, sizeof *opi); const ovs_be64 *cookie = (raw == OFPRAW_OFPT13_PACKET_IN ? ofpbuf_pull(&b, sizeof *cookie) : NULL); enum ofperr error = oxm_pull_match_loose(&b, false, tun_table, &pin->flow_metadata); pin->flow_metadata.flow.tunnel.metadata.tab = tun_table; if (error) { return error; } if (!ofpbuf_try_pull(&b, 2)) { return OFPERR_OFPBRC_BAD_LEN; } pin->reason = opi->reason; pin->table_id = opi->table_id; buffer_id = ntohl(opi->buffer_id); total_len = ntohs(opi->total_len); if (cookie) { pin->cookie = *cookie; } pin->packet = b.data; pin->packet_len = b.size; } else if (raw == OFPRAW_OFPT10_PACKET_IN) { const struct ofp10_packet_in *opi; opi = ofpbuf_pull(&b, offsetof(struct ofp10_packet_in, data)); pin->packet = CONST_CAST(uint8_t *, opi->data); pin->packet_len = b.size; match_init_catchall(&pin->flow_metadata); match_set_in_port(&pin->flow_metadata, u16_to_ofp(ntohs(opi->in_port))); pin->reason = opi->reason; buffer_id = ntohl(opi->buffer_id); total_len = ntohs(opi->total_len); } else if (raw == OFPRAW_OFPT11_PACKET_IN) { const struct ofp11_packet_in *opi; ofp_port_t in_port; enum ofperr error; opi = ofpbuf_pull(&b, sizeof *opi); pin->packet = b.data; pin->packet_len = b.size; buffer_id = ntohl(opi->buffer_id); error = ofputil_port_from_ofp11(opi->in_port, &in_port); if (error) { return error; } match_init_catchall(&pin->flow_metadata); match_set_in_port(&pin->flow_metadata, in_port); total_len = ntohs(opi->total_len); pin->reason = opi->reason; pin->table_id = opi->table_id; } else if (raw == OFPRAW_NXT_PACKET_IN) { const struct nx_packet_in *npi; int error; npi = ofpbuf_pull(&b, sizeof *npi); error = nx_pull_match_loose(&b, ntohs(npi->match_len), &pin->flow_metadata, NULL, NULL, false, NULL); if (error) { return error; } if (!ofpbuf_try_pull(&b, 2)) { return OFPERR_OFPBRC_BAD_LEN; } pin->reason = npi->reason; pin->table_id = npi->table_id; pin->cookie = npi->cookie; buffer_id = ntohl(npi->buffer_id); total_len = ntohs(npi->total_len); pin->packet = b.data; pin->packet_len = b.size; } else if (raw == OFPRAW_NXT_PACKET_IN2 || raw == OFPRAW_NXT_RESUME) { enum ofperr error = decode_nx_packet_in2(oh, loose, tun_table, vl_mff_map, pin, &total_len, &buffer_id, continuation); if (error) { return error; } } else { OVS_NOT_REACHED(); } if (total_lenp) { *total_lenp = total_len; } if (buffer_idp) { *buffer_idp = buffer_id; } return 0; } static int encode_packet_in_reason(enum ofp_packet_in_reason reason, enum ofp_version version) { switch (reason) { case OFPR_NO_MATCH: case OFPR_ACTION: case OFPR_INVALID_TTL: return reason; case OFPR_ACTION_SET: case OFPR_GROUP: case OFPR_PACKET_OUT: return version < OFP14_VERSION ? OFPR_ACTION : reason; case OFPR_EXPLICIT_MISS: return version < OFP13_VERSION ? OFPR_ACTION : OFPR_NO_MATCH; case OFPR_IMPLICIT_MISS: return OFPR_NO_MATCH; case OFPR_N_REASONS: default: OVS_NOT_REACHED(); } } /* Only NXT_PACKET_IN2 (not NXT_RESUME) should include NXCPT_USERDATA, so this * function omits it. The caller can add it itself if desired. */ static void ofputil_put_packet_in(const struct ofputil_packet_in *pin, enum ofp_version version, size_t include_bytes, struct ofpbuf *msg) { /* Add packet properties. */ ofpprop_put(msg, NXPINT_PACKET, pin->packet, include_bytes); if (include_bytes != pin->packet_len) { ofpprop_put_u32(msg, NXPINT_FULL_LEN, pin->packet_len); } /* Add flow properties. */ ofpprop_put_u8(msg, NXPINT_TABLE_ID, pin->table_id); if (pin->cookie != OVS_BE64_MAX) { ofpprop_put_be64(msg, NXPINT_COOKIE, pin->cookie); } /* Add other properties. */ ofpprop_put_u8(msg, NXPINT_REASON, encode_packet_in_reason(pin->reason, version)); size_t start = ofpprop_start(msg, NXPINT_METADATA); oxm_put_raw(msg, &pin->flow_metadata, version); ofpprop_end(msg, start); } static void put_actions_property(struct ofpbuf *msg, uint64_t prop_type, enum ofp_version version, const struct ofpact *actions, size_t actions_len) { if (actions_len) { size_t start = ofpprop_start_nested(msg, prop_type); ofpacts_put_openflow_actions(actions, actions_len, msg, version); ofpprop_end(msg, start); } } enum nx_continuation_prop_type { NXCPT_BRIDGE = 0x8000, NXCPT_STACK, NXCPT_MIRRORS, NXCPT_CONNTRACKED, NXCPT_TABLE_ID, NXCPT_COOKIE, NXCPT_ACTIONS, NXCPT_ACTION_SET, NXCPT_ODP_PORT, }; /* Only NXT_PACKET_IN2 (not NXT_RESUME) should include NXCPT_USERDATA, so this * function omits it. The caller can add it itself if desired. */ static void ofputil_put_packet_in_private(const struct ofputil_packet_in_private *pin, enum ofp_version version, size_t include_bytes, struct ofpbuf *msg) { ofputil_put_packet_in(&pin->base, version, include_bytes, msg); size_t continuation_ofs = ofpprop_start_nested(msg, NXPINT_CONTINUATION); size_t inner_ofs = msg->size; if (!uuid_is_zero(&pin->bridge)) { ofpprop_put_uuid(msg, NXCPT_BRIDGE, &pin->bridge); } struct ofpbuf pin_stack; ofpbuf_use_const(&pin_stack, pin->stack, pin->stack_size); while (pin_stack.size) { uint8_t len; uint8_t *val = nx_stack_pop(&pin_stack, &len); ofpprop_put(msg, NXCPT_STACK, val, len); } if (pin->mirrors) { ofpprop_put_u32(msg, NXCPT_MIRRORS, pin->mirrors); } if (pin->conntracked) { ofpprop_put_flag(msg, NXCPT_CONNTRACKED); } if (pin->actions_len) { /* Divide 'pin->actions' into groups that begins with an * unroll_xlate action. For each group, emit a NXCPT_TABLE_ID and * NXCPT_COOKIE property (if either has changed; each is initially * assumed 0), then a NXCPT_ACTIONS property with the grouped * actions. * * The alternative is to make OFPACT_UNROLL_XLATE public. We can * always do that later, since this is a private property. */ const struct ofpact *const end = ofpact_end(pin->actions, pin->actions_len); const struct ofpact_unroll_xlate *unroll = NULL; uint8_t table_id = 0; ovs_be64 cookie = 0; const struct ofpact *a; for (a = pin->actions; ; a = ofpact_next(a)) { if (a == end || a->type == OFPACT_UNROLL_XLATE) { if (unroll) { if (table_id != unroll->rule_table_id) { ofpprop_put_u8(msg, NXCPT_TABLE_ID, unroll->rule_table_id); table_id = unroll->rule_table_id; } if (cookie != unroll->rule_cookie) { ofpprop_put_be64(msg, NXCPT_COOKIE, unroll->rule_cookie); cookie = unroll->rule_cookie; } } const struct ofpact *start = unroll ? ofpact_next(&unroll->ofpact) : pin->actions; put_actions_property(msg, NXCPT_ACTIONS, version, start, (a - start) * sizeof *a); if (a == end) { break; } unroll = ofpact_get_UNROLL_XLATE(a); } } } if (pin->action_set_len) { size_t start = ofpprop_start_nested(msg, NXCPT_ACTION_SET); ofpacts_put_openflow_actions(pin->action_set, pin->action_set_len, msg, version); ofpprop_end(msg, start); } if (pin->odp_port) { ofpprop_put_u32(msg, NXCPT_ODP_PORT, odp_to_u32(pin->odp_port)); } if (msg->size > inner_ofs) { ofpprop_end(msg, continuation_ofs); } else { msg->size = continuation_ofs; } } static struct ofpbuf * ofputil_encode_ofp10_packet_in(const struct ofputil_packet_in *pin) { struct ofp10_packet_in *opi; struct ofpbuf *msg; msg = ofpraw_alloc_xid(OFPRAW_OFPT10_PACKET_IN, OFP10_VERSION, htonl(0), pin->packet_len); opi = ofpbuf_put_zeros(msg, offsetof(struct ofp10_packet_in, data)); opi->total_len = htons(pin->packet_len); opi->in_port = htons(ofp_to_u16(pin->flow_metadata.flow.in_port.ofp_port)); opi->reason = encode_packet_in_reason(pin->reason, OFP10_VERSION); opi->buffer_id = htonl(UINT32_MAX); return msg; } static struct ofpbuf * ofputil_encode_nx_packet_in(const struct ofputil_packet_in *pin, enum ofp_version version) { struct nx_packet_in *npi; struct ofpbuf *msg; size_t match_len; /* The final argument is just an estimate of the space required. */ msg = ofpraw_alloc_xid(OFPRAW_NXT_PACKET_IN, version, htonl(0), NXM_TYPICAL_LEN + 2 + pin->packet_len); ofpbuf_put_zeros(msg, sizeof *npi); match_len = nx_put_match(msg, &pin->flow_metadata, 0, 0); ofpbuf_put_zeros(msg, 2); npi = msg->msg; npi->buffer_id = htonl(UINT32_MAX); npi->total_len = htons(pin->packet_len); npi->reason = encode_packet_in_reason(pin->reason, version); npi->table_id = pin->table_id; npi->cookie = pin->cookie; npi->match_len = htons(match_len); return msg; } static struct ofpbuf * ofputil_encode_nx_packet_in2(const struct ofputil_packet_in_private *pin, enum ofp_version version, size_t include_bytes) { /* 'extra' is just an estimate of the space required. */ size_t extra = (pin->base.packet_len + NXM_TYPICAL_LEN /* flow_metadata */ + pin->stack_size * 4 + pin->actions_len + pin->action_set_len + 256); /* fudge factor */ struct ofpbuf *msg = ofpraw_alloc_xid(OFPRAW_NXT_PACKET_IN2, version, htonl(0), extra); ofputil_put_packet_in_private(pin, version, include_bytes, msg); if (pin->base.userdata_len) { ofpprop_put(msg, NXPINT_USERDATA, pin->base.userdata, pin->base.userdata_len); } ofpmsg_update_length(msg); return msg; } static struct ofpbuf * ofputil_encode_ofp11_packet_in(const struct ofputil_packet_in *pin) { struct ofp11_packet_in *opi; struct ofpbuf *msg; msg = ofpraw_alloc_xid(OFPRAW_OFPT11_PACKET_IN, OFP11_VERSION, htonl(0), pin->packet_len); opi = ofpbuf_put_zeros(msg, sizeof *opi); opi->buffer_id = htonl(UINT32_MAX); opi->in_port = ofputil_port_to_ofp11( pin->flow_metadata.flow.in_port.ofp_port); opi->in_phy_port = opi->in_port; opi->total_len = htons(pin->packet_len); opi->reason = encode_packet_in_reason(pin->reason, OFP11_VERSION); opi->table_id = pin->table_id; return msg; } static struct ofpbuf * ofputil_encode_ofp12_packet_in(const struct ofputil_packet_in *pin, enum ofp_version version) { enum ofpraw raw = (version >= OFP13_VERSION ? OFPRAW_OFPT13_PACKET_IN : OFPRAW_OFPT12_PACKET_IN); struct ofpbuf *msg; /* The final argument is just an estimate of the space required. */ msg = ofpraw_alloc_xid(raw, version, htonl(0), NXM_TYPICAL_LEN + 2 + pin->packet_len); struct ofp12_packet_in *opi = ofpbuf_put_zeros(msg, sizeof *opi); opi->buffer_id = htonl(UINT32_MAX); opi->total_len = htons(pin->packet_len); opi->reason = encode_packet_in_reason(pin->reason, version); opi->table_id = pin->table_id; if (version >= OFP13_VERSION) { ovs_be64 cookie = pin->cookie; ofpbuf_put(msg, &cookie, sizeof cookie); } oxm_put_match(msg, &pin->flow_metadata, version); ofpbuf_put_zeros(msg, 2); return msg; } /* Converts abstract ofputil_packet_in_private 'pin' into a PACKET_IN message * for 'protocol', using the packet-in format specified by 'format'. * * This function is really meant only for use by ovs-vswitchd. To any other * code, the "continuation" data, i.e. the data that is in struct * ofputil_packet_in_private but not in struct ofputil_packet_in, is supposed * to be opaque (and it might change from one OVS version to another). Thus, * if any other code wants to encode a packet-in, it should use a non-"private" * version of this function. (Such a version doesn't currently exist because * only ovs-vswitchd currently wants to encode packet-ins. If you need one, * write it...) */ struct ofpbuf * ofputil_encode_packet_in_private(const struct ofputil_packet_in_private *pin, enum ofputil_protocol protocol, enum ofputil_packet_in_format format) { enum ofp_version version = ofputil_protocol_to_ofp_version(protocol); struct ofpbuf *msg; switch (format) { case OFPUTIL_PACKET_IN_STD: switch (protocol) { case OFPUTIL_P_OF10_STD: case OFPUTIL_P_OF10_STD_TID: case OFPUTIL_P_OF10_NXM: case OFPUTIL_P_OF10_NXM_TID: msg = ofputil_encode_ofp10_packet_in(&pin->base); break; case OFPUTIL_P_OF11_STD: msg = ofputil_encode_ofp11_packet_in(&pin->base); break; case OFPUTIL_P_OF12_OXM: case OFPUTIL_P_OF13_OXM: case OFPUTIL_P_OF14_OXM: case OFPUTIL_P_OF15_OXM: msg = ofputil_encode_ofp12_packet_in(&pin->base, version); break; default: OVS_NOT_REACHED(); } break; case OFPUTIL_PACKET_IN_NXT: msg = ofputil_encode_nx_packet_in(&pin->base, version); break; case OFPUTIL_PACKET_IN_NXT2: return ofputil_encode_nx_packet_in2(pin, version, pin->base.packet_len); default: OVS_NOT_REACHED(); } ofpbuf_put(msg, pin->base.packet, pin->base.packet_len); ofpmsg_update_length(msg); return msg; } /* Returns a string form of 'reason'. The return value is either a statically * allocated constant string or the 'bufsize'-byte buffer 'reasonbuf'. * 'bufsize' should be at least OFPUTIL_PACKET_IN_REASON_BUFSIZE. */ const char * ofputil_packet_in_reason_to_string(enum ofp_packet_in_reason reason, char *reasonbuf, size_t bufsize) { switch (reason) { case OFPR_NO_MATCH: return "no_match"; case OFPR_ACTION: return "action"; case OFPR_INVALID_TTL: return "invalid_ttl"; case OFPR_ACTION_SET: return "action_set"; case OFPR_GROUP: return "group"; case OFPR_PACKET_OUT: return "packet_out"; case OFPR_EXPLICIT_MISS: case OFPR_IMPLICIT_MISS: return ""; case OFPR_N_REASONS: default: snprintf(reasonbuf, bufsize, "%d", (int) reason); return reasonbuf; } } bool ofputil_packet_in_reason_from_string(const char *s, enum ofp_packet_in_reason *reason) { int i; for (i = 0; i < OFPR_N_REASONS; i++) { char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE]; const char *reason_s; reason_s = ofputil_packet_in_reason_to_string(i, reasonbuf, sizeof reasonbuf); if (!strcasecmp(s, reason_s)) { *reason = i; return true; } } return false; } /* Returns a newly allocated NXT_RESUME message for 'pin', with the given * 'continuation', for 'protocol'. This message is suitable for resuming the * pipeline traveral of the packet represented by 'pin', if sent to the switch * from which 'pin' was received. */ struct ofpbuf * ofputil_encode_resume(const struct ofputil_packet_in *pin, const struct ofpbuf *continuation, enum ofputil_protocol protocol) { enum ofp_version version = ofputil_protocol_to_ofp_version(protocol); size_t extra = pin->packet_len + NXM_TYPICAL_LEN + continuation->size; struct ofpbuf *msg = ofpraw_alloc_xid(OFPRAW_NXT_RESUME, version, 0, extra); ofputil_put_packet_in(pin, version, pin->packet_len, msg); ofpprop_put_nested(msg, NXPINT_CONTINUATION, continuation); ofpmsg_update_length(msg); return msg; } static enum ofperr parse_stack_prop(const struct ofpbuf *property, struct ofpbuf *stack) { unsigned int len = ofpbuf_msgsize(property); if (len > sizeof(union mf_subvalue)) { VLOG_WARN_RL(&rl, "NXCPT_STACK property has bad length %u", len); return OFPERR_OFPBPC_BAD_LEN; } nx_stack_push_bottom(stack, property->msg, len); return 0; } static enum ofperr parse_actions_property(struct ofpbuf *property, enum ofp_version version, struct ofpbuf *ofpacts) { if (!ofpbuf_try_pull(property, ROUND_UP(ofpbuf_headersize(property), 8))) { VLOG_WARN_RL(&rl, "actions property has bad length %"PRIu32, property->size); return OFPERR_OFPBPC_BAD_LEN; } return ofpacts_pull_openflow_actions(property, property->size, version, NULL, NULL, ofpacts); } /* This is like ofputil_decode_packet_in(), except that it decodes the * continuation data into 'pin'. The format of this data is supposed to be * opaque to any process other than ovs-vswitchd, so this function should not * be used outside ovs-vswitchd. * * 'vl_mff_map' is an optional parameter that is used to validate the length * of variable length mf_fields in 'match'. If it is not provided, the * default mf_fields with maximum length will be used. * * When successful, 'pin' contains some dynamically allocated data. Call * ofputil_packet_in_private_destroy() to free this data. */ enum ofperr ofputil_decode_packet_in_private(const struct ofp_header *oh, bool loose, const struct tun_table *tun_table, const struct vl_mff_map *vl_mff_map, struct ofputil_packet_in_private *pin, size_t *total_len, uint32_t *buffer_id) { memset(pin, 0, sizeof *pin); struct ofpbuf continuation; enum ofperr error; error = ofputil_decode_packet_in(oh, loose, tun_table, vl_mff_map, &pin->base, total_len, buffer_id, &continuation); if (error) { return error; } struct ofpbuf actions, action_set; ofpbuf_init(&actions, 0); ofpbuf_init(&action_set, 0); uint8_t table_id = 0; ovs_be64 cookie = 0; struct ofpbuf stack; ofpbuf_init(&stack, 0); while (continuation.size > 0) { struct ofpbuf payload; uint64_t type; error = ofpprop_pull(&continuation, &payload, &type); if (error) { break; } switch (type) { case NXCPT_BRIDGE: error = ofpprop_parse_uuid(&payload, &pin->bridge); break; case NXCPT_STACK: error = parse_stack_prop(&payload, &stack); break; case NXCPT_MIRRORS: error = ofpprop_parse_u32(&payload, &pin->mirrors); break; case NXCPT_CONNTRACKED: pin->conntracked = true; break; case NXCPT_TABLE_ID: error = ofpprop_parse_u8(&payload, &table_id); break; case NXCPT_COOKIE: error = ofpprop_parse_be64(&payload, &cookie); break; case NXCPT_ACTIONS: { struct ofpact_unroll_xlate *unroll = ofpact_put_UNROLL_XLATE(&actions); unroll->rule_table_id = table_id; unroll->rule_cookie = cookie; error = parse_actions_property(&payload, oh->version, &actions); break; } case NXCPT_ACTION_SET: error = parse_actions_property(&payload, oh->version, &action_set); break; case NXCPT_ODP_PORT: { uint32_t value; error = ofpprop_parse_u32(&payload, &value); if (!error) { pin->odp_port = u32_to_odp(value); } break; } default: error = OFPPROP_UNKNOWN(loose, "continuation", type); break; } if (error) { break; } } pin->actions_len = actions.size; pin->actions = ofpbuf_steal_data(&actions); pin->action_set_len = action_set.size; pin->action_set = ofpbuf_steal_data(&action_set); pin->stack_size = stack.size; pin->stack = ofpbuf_steal_data(&stack); if (error) { ofputil_packet_in_private_destroy(pin); } return error; } 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]); } } void ofputil_packet_in_private_format(struct ds *s, const struct ofputil_packet_in_private *pin, size_t total_len, uint32_t buffer_id, const struct ofputil_port_map *port_map, const struct ofputil_table_map *table_map, int verbosity) { char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE]; const struct ofputil_packet_in *public = &pin->base; if (public->table_id || ofputil_table_map_get_name(table_map, public->table_id)) { ds_put_format(s, " table_id="); ofputil_format_table(public->table_id, table_map, s); } if (public->cookie != OVS_BE64_MAX) { ds_put_format(s, " cookie=0x%"PRIx64, ntohll(public->cookie)); } ds_put_format(s, " total_len=%"PRIuSIZE" ", total_len); match_format(&public->flow_metadata, port_map, s, OFP_DEFAULT_PRIORITY); ds_put_format(s, " (via %s)", ofputil_packet_in_reason_to_string(public->reason, reasonbuf, sizeof reasonbuf)); ds_put_format(s, " data_len=%"PRIuSIZE, public->packet_len); if (buffer_id == UINT32_MAX) { ds_put_format(s, " (unbuffered)"); if (total_len != public->packet_len) { ds_put_format(s, " (***total_len != data_len***)"); } } else { ds_put_format(s, " buffer=0x%08"PRIx32, buffer_id); if (total_len < public->packet_len) { ds_put_format(s, " (***total_len < data_len***)"); } } ds_put_char(s, '\n'); if (public->userdata_len) { ds_put_cstr(s, " userdata="); format_hex_arg(s, pin->base.userdata, pin->base.userdata_len); ds_put_char(s, '\n'); } if (!uuid_is_zero(&pin->bridge)) { ds_put_format(s, " continuation.bridge="UUID_FMT"\n", UUID_ARGS(&pin->bridge)); } if (pin->stack_size) { ds_put_cstr(s, " continuation.stack=(top)"); struct ofpbuf pin_stack; ofpbuf_use_const(&pin_stack, pin->stack, pin->stack_size); while (pin_stack.size) { uint8_t len; uint8_t *val = nx_stack_pop(&pin_stack, &len); union mf_subvalue value; ds_put_char(s, ' '); memset(&value, 0, sizeof value - len); memcpy(&value.u8[sizeof value - len], val, len); mf_subvalue_format(&value, s); } ds_put_cstr(s, " (bottom)\n"); } if (pin->mirrors) { ds_put_format(s, " continuation.mirrors=0x%"PRIx32"\n", pin->mirrors); } if (pin->conntracked) { ds_put_cstr(s, " continuation.conntracked=true\n"); } struct ofpact_format_params fp = { .port_map = port_map, .table_map = table_map, .s = s, }; if (pin->actions_len) { ds_put_cstr(s, " continuation.actions="); ofpacts_format(pin->actions, pin->actions_len, &fp); ds_put_char(s, '\n'); } if (pin->action_set_len) { ds_put_cstr(s, " continuation.action_set="); ofpacts_format(pin->action_set, pin->action_set_len, &fp); ds_put_char(s, '\n'); } if (pin->odp_port) { ds_put_format(s, " continuation.odp_port=%"PRIu32, pin->odp_port); ds_put_char(s, '\n'); } if (verbosity > 0) { char *packet = ofp_packet_to_string( public->packet, public->packet_len, public->flow_metadata.flow.packet_type); ds_put_cstr(s, packet); free(packet); } if (verbosity > 2) { ds_put_hex_dump(s, public->packet, public->packet_len, 0, false); } } /* Frees data in 'pin' that is dynamically allocated by * ofputil_decode_packet_in_private(). * * 'pin->base' contains some pointer members that * ofputil_decode_packet_in_private() doesn't initialize to newly allocated * data, so this function doesn't free those. */ void ofputil_packet_in_private_destroy(struct ofputil_packet_in_private *pin) { if (pin) { free(pin->stack); free(pin->actions); free(pin->action_set); } } /* Converts an OFPT_PACKET_OUT in 'opo' into an abstract ofputil_packet_out in * 'po'. * * Uses 'ofpacts' to store the abstract OFPACT_* version of the packet out * message's actions. The caller must initialize 'ofpacts' and retains * ownership of it. 'po->ofpacts' will point into the 'ofpacts' buffer. * * 'po->packet' refers to the packet data in 'oh', so the buffer containing * 'oh' must not be destroyed while 'po' is being used. * * Returns 0 if successful, otherwise an OFPERR_* value. */ enum ofperr ofputil_decode_packet_out(struct ofputil_packet_out *po, const struct ofp_header *oh, const struct tun_table *tun_table, struct ofpbuf *ofpacts) { struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length)); enum ofpraw raw = ofpraw_pull_assert(&b); ofpbuf_clear(ofpacts); match_init_catchall(&po->flow_metadata); if (raw == OFPRAW_OFPT15_PACKET_OUT) { enum ofperr error; const struct ofp15_packet_out *opo = ofpbuf_pull(&b, sizeof *opo); po->buffer_id = ntohl(opo->buffer_id); error = oxm_pull_match_loose(&b, true, tun_table, &po->flow_metadata); if (error) { return error; } if (!po->flow_metadata.wc.masks.in_port.ofp_port) { return OFPERR_OFPBRC_BAD_PORT; } error = ofpacts_pull_openflow_actions(&b, ntohs(opo->actions_len), oh->version, NULL, NULL, ofpacts); if (error) { return error; } } else if (raw == OFPRAW_OFPT11_PACKET_OUT) { enum ofperr error; ofp_port_t in_port; const struct ofp11_packet_out *opo = ofpbuf_pull(&b, sizeof *opo); po->buffer_id = ntohl(opo->buffer_id); error = ofputil_port_from_ofp11(opo->in_port, &in_port); if (error) { return error; } match_set_packet_type(&po->flow_metadata, htonl(PT_ETH)); match_set_in_port(&po->flow_metadata, in_port); error = ofpacts_pull_openflow_actions(&b, ntohs(opo->actions_len), oh->version, NULL, NULL, ofpacts); if (error) { return error; } } else if (raw == OFPRAW_OFPT10_PACKET_OUT) { enum ofperr error; const struct ofp10_packet_out *opo = ofpbuf_pull(&b, sizeof *opo); po->buffer_id = ntohl(opo->buffer_id); match_set_packet_type(&po->flow_metadata, htonl(PT_ETH)); match_set_in_port(&po->flow_metadata, u16_to_ofp(ntohs(opo->in_port))); error = ofpacts_pull_openflow_actions(&b, ntohs(opo->actions_len), oh->version, NULL, NULL, ofpacts); if (error) { return error; } } else { OVS_NOT_REACHED(); } ofp_port_t in_port = po->flow_metadata.flow.in_port.ofp_port; if (ofp_to_u16(in_port) >= ofp_to_u16(OFPP_MAX) && in_port != OFPP_LOCAL && in_port != OFPP_NONE && in_port != OFPP_CONTROLLER) { VLOG_WARN_RL(&rl, "packet-out has bad input port %#"PRIx32, po->flow_metadata.flow.in_port.ofp_port); return OFPERR_OFPBRC_BAD_PORT; } po->ofpacts = ofpacts->data; po->ofpacts_len = ofpacts->size; if (po->buffer_id == UINT32_MAX) { po->packet = b.data; po->packet_len = b.size; } else { po->packet = NULL; po->packet_len = 0; } return 0; } struct ofpbuf * ofputil_encode_packet_out(const struct ofputil_packet_out *po, enum ofputil_protocol protocol) { enum ofp_version ofp_version = ofputil_protocol_to_ofp_version(protocol); struct ofpbuf *msg; size_t size; size = po->ofpacts_len; if (po->buffer_id == UINT32_MAX) { size += po->packet_len; } switch (ofp_version) { case OFP10_VERSION: { struct ofp10_packet_out *opo; size_t actions_ofs; msg = ofpraw_alloc(OFPRAW_OFPT10_PACKET_OUT, OFP10_VERSION, size); ofpbuf_put_zeros(msg, sizeof *opo); actions_ofs = msg->size; ofpacts_put_openflow_actions(po->ofpacts, po->ofpacts_len, msg, ofp_version); opo = msg->msg; opo->buffer_id = htonl(po->buffer_id); opo->in_port =htons(ofp_to_u16( po->flow_metadata.flow.in_port.ofp_port)); opo->actions_len = htons(msg->size - actions_ofs); break; } case OFP11_VERSION: case OFP12_VERSION: case OFP13_VERSION: case OFP14_VERSION: { struct ofp11_packet_out *opo; size_t len; msg = ofpraw_alloc(OFPRAW_OFPT11_PACKET_OUT, ofp_version, size); ofpbuf_put_zeros(msg, sizeof *opo); len = ofpacts_put_openflow_actions(po->ofpacts, po->ofpacts_len, msg, ofp_version); opo = msg->msg; opo->buffer_id = htonl(po->buffer_id); opo->in_port = ofputil_port_to_ofp11(po->flow_metadata.flow.in_port.ofp_port); opo->actions_len = htons(len); break; } case OFP15_VERSION: { struct ofp15_packet_out *opo; size_t len; /* The final argument is just an estimate of the space required. */ msg = ofpraw_alloc(OFPRAW_OFPT15_PACKET_OUT, ofp_version, size + NXM_TYPICAL_LEN); ofpbuf_put_zeros(msg, sizeof *opo); oxm_put_match(msg, &po->flow_metadata, ofp_version); len = ofpacts_put_openflow_actions(po->ofpacts, po->ofpacts_len, msg, ofp_version); opo = msg->msg; opo->buffer_id = htonl(po->buffer_id); opo->actions_len = htons(len); break; } default: OVS_NOT_REACHED(); } if (po->buffer_id == UINT32_MAX) { ofpbuf_put(msg, po->packet, po->packet_len); } ofpmsg_update_length(msg); return msg; } void ofputil_packet_out_format(struct ds *s, const struct ofputil_packet_out *po, const struct ofputil_port_map *port_map, const struct ofputil_table_map *table_map, int verbosity) { ds_put_char(s, ' '); match_format(&po->flow_metadata, port_map, s, OFP_DEFAULT_PRIORITY); ds_put_cstr(s, " actions="); struct ofpact_format_params fp = { .port_map = port_map, .table_map = table_map, .s = s, }; ofpacts_format(po->ofpacts, po->ofpacts_len, &fp); if (po->buffer_id == UINT32_MAX) { ds_put_format(s, " data_len=%"PRIuSIZE, po->packet_len); if (verbosity > 0 && po->packet_len > 0) { ovs_be32 po_packet_type = po->flow_metadata.flow.packet_type; char *packet = ofp_packet_to_string(po->packet, po->packet_len, po_packet_type); ds_put_char(s, '\n'); ds_put_cstr(s, packet); free(packet); } if (verbosity > 2) { ds_put_hex_dump(s, po->packet, po->packet_len, 0, false); } } else { ds_put_format(s, " buffer=0x%08"PRIx32, po->buffer_id); } } /* Parse a string representation of a OFPT_PACKET_OUT to '*po'. If successful, * both 'po->ofpacts' and 'po->packet' must be free()d by the caller. */ static char * OVS_WARN_UNUSED_RESULT parse_ofp_packet_out_str__(struct ofputil_packet_out *po, char *string, const struct ofputil_port_map *port_map, const struct ofputil_table_map *table_map, enum ofputil_protocol *usable_protocols) { enum ofputil_protocol action_usable_protocols; uint64_t stub[256 / 8]; struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); struct dp_packet *packet = NULL; char *act_str = NULL; char *name, *value; char *error = NULL; *usable_protocols = OFPUTIL_P_ANY; *po = (struct ofputil_packet_out) { .buffer_id = UINT32_MAX, }; match_init_catchall(&po->flow_metadata); match_set_in_port(&po->flow_metadata, OFPP_CONTROLLER); act_str = ofp_extract_actions(string); while (ofputil_parse_key_value(&string, &name, &value)) { if (!*value) { error = xasprintf("field %s missing value", name); goto out; } if (!strcmp(name, "in_port")) { ofp_port_t in_port; if (!ofputil_port_from_string(value, port_map, &in_port)) { error = xasprintf("%s is not a valid OpenFlow port", value); goto out; } if (ofp_to_u16(in_port) > ofp_to_u16(OFPP_MAX) && in_port != OFPP_LOCAL && in_port != OFPP_NONE && in_port != OFPP_CONTROLLER) { error = xasprintf( "%s is not a valid OpenFlow port for PACKET_OUT", value); goto out; } match_set_in_port(&po->flow_metadata, in_port); } else if (!strcmp(name, "packet_type")) { char *ns = value; char *ns_type = strstr(value, ","); if (ns_type) { ovs_be32 packet_type; *ns_type = '\0'; packet_type = PACKET_TYPE_BE(strtoul(ns, NULL, 0), strtoul(++ns_type, NULL, 0)); match_set_packet_type(&po->flow_metadata, packet_type); } else { error = xasprintf("%s(%s) can't be interpreted", name, value); goto out; } } else if (!strcmp(name, "packet")) { const char *error_msg = eth_from_hex(value, &packet); if (error_msg) { error = xasprintf("%s: %s", name, error_msg); goto out; } } else { const struct mf_field *mf = mf_from_name(name); if (!mf) { error = xasprintf("unknown keyword %s", name); goto out; } error = ofp_parse_field(mf, value, port_map, &po->flow_metadata, usable_protocols); if (error) { goto out; } if (!mf_is_pipeline_field(mf)) { error = xasprintf("%s is not a valid pipeline field " "for PACKET_OUT", name); goto out; } } } if (!packet || !dp_packet_size(packet)) { error = xstrdup("must specify packet"); goto out; } if (act_str) { struct ofpact_parse_params pp = { .port_map = port_map, .table_map = table_map, .ofpacts = &ofpacts, .usable_protocols = &action_usable_protocols, }; error = ofpacts_parse_actions(act_str, &pp); *usable_protocols &= action_usable_protocols; if (error) { goto out; } } po->ofpacts_len = ofpacts.size; po->ofpacts = ofpbuf_steal_data(&ofpacts); po->packet_len = dp_packet_size(packet); po->packet = dp_packet_steal_data(packet); out: ofpbuf_uninit(&ofpacts); dp_packet_delete(packet); return error; } /* Convert 'str_' (as described in the Packet-Out Syntax section of the * ovs-ofctl man page) into 'po' for sending a OFPT_PACKET_OUT message to a * switch. Returns the set of usable protocols in '*usable_protocols'. * * Returns NULL if successful, otherwise a malloc()'d string describing the * error. The caller is responsible for freeing the returned string. * If successful, both 'po->ofpacts' and 'po->packet' must be free()d by * the caller. */ char * OVS_WARN_UNUSED_RESULT parse_ofp_packet_out_str(struct ofputil_packet_out *po, const char *str_, const struct ofputil_port_map *port_map, const struct ofputil_table_map *table_map, enum ofputil_protocol *usable_protocols) { char *string = xstrdup(str_); char *error; error = parse_ofp_packet_out_str__(po, string, port_map, table_map, usable_protocols); if (error) { po->ofpacts = NULL; po->ofpacts_len = 0; } free(string); return error; }