/* * 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-table.h" #include "bitmap.h" #include "nx-match.h" #include "openvswitch/dynamic-string.h" #include "openvswitch/json.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-msgs.h" #include "openvswitch/ofp-print.h" #include "openvswitch/ofp-prop.h" #include "openvswitch/ofpbuf.h" #include "openvswitch/vlog.h" #include "util.h" VLOG_DEFINE_THIS_MODULE(ofp_table); static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); static ovs_be32 ofputil_encode_table_config(enum ofputil_table_miss, enum ofputil_table_eviction, enum ofputil_table_vacancy, enum ofp_version); static enum ofputil_table_vacancy ofputil_decode_table_vacancy( ovs_be32 config, enum ofp_version); static enum ofputil_table_eviction ofputil_decode_table_eviction( ovs_be32 config, enum ofp_version); const char * ofputil_table_miss_to_string(enum ofputil_table_miss miss) { switch (miss) { case OFPUTIL_TABLE_MISS_DEFAULT: return "default"; case OFPUTIL_TABLE_MISS_CONTROLLER: return "controller"; case OFPUTIL_TABLE_MISS_CONTINUE: return "continue"; case OFPUTIL_TABLE_MISS_DROP: return "drop"; default: return "***error***"; } } const char * ofputil_table_eviction_to_string(enum ofputil_table_eviction eviction) { switch (eviction) { case OFPUTIL_TABLE_EVICTION_DEFAULT: return "default"; case OFPUTIL_TABLE_EVICTION_ON: return "on"; case OFPUTIL_TABLE_EVICTION_OFF: return "off"; default: return "***error***"; } } const char * ofputil_table_vacancy_to_string(enum ofputil_table_vacancy vacancy) { switch (vacancy) { case OFPUTIL_TABLE_VACANCY_DEFAULT: return "default"; case OFPUTIL_TABLE_VACANCY_ON: return "on"; case OFPUTIL_TABLE_VACANCY_OFF: return "off"; default: return "***error***"; } } static bool ofp15_table_features_command_is_valid(enum ofp15_table_features_command cmd) { switch (cmd) { case OFPTFC15_REPLACE: case OFPTFC15_MODIFY: case OFPTFC15_ENABLE: case OFPTFC15_DISABLE: return true; default: return false; } } static const char * ofp15_table_features_command_to_string(enum ofp15_table_features_command cmd) { switch (cmd) { case OFPTFC15_REPLACE: return "replace"; case OFPTFC15_MODIFY: return "modify"; case OFPTFC15_ENABLE: return "enable"; case OFPTFC15_DISABLE: return "disable"; default: return "***bad command***"; } } /* ofputil_table_map. */ void ofputil_table_map_init(struct ofputil_table_map *map) { namemap_init(&map->map); } void ofputil_table_map_put(struct ofputil_table_map *map, uint8_t table_id, const char *name) { namemap_put(&map->map, table_id, name); } const char * ofputil_table_map_get_name(const struct ofputil_table_map *map, uint8_t table_id) { struct namemap_node *node = map ? namemap_find_by_number(&map->map, table_id) : NULL; return node && !node->duplicate ? node->name : NULL; } uint8_t ofputil_table_map_get_number(const struct ofputil_table_map *map, const char *name) { struct namemap_node *node = map ? namemap_find_by_name(&map->map, name) : NULL; return node && !node->duplicate ? node->number : UINT8_MAX; } void ofputil_table_map_destroy(struct ofputil_table_map *map) { namemap_destroy(&map->map); } /* Table numbers. */ /* Stores the table number represented by 's' into '*tablep'. 's' may be an * integer or, if 'table_map' is nonnull, a name (quoted or unquoted). * * Returns true if successful, false if 's' is not a valid OpenFlow table * number or name. The caller should issue an error message in this case, * because this function usually does not. (This gives the caller an * opportunity to look up the table name another way, e.g. by contacting the * switch and listing the names of all its tables). */ bool ofputil_table_from_string(const char *s, const struct ofputil_table_map *table_map, uint8_t *tablep) { *tablep = 0; if (*s == '-') { VLOG_WARN("Negative value %s is not a valid table number.", s); return false; } unsigned int table; if (str_to_uint(s, 10, &table)) { if (table > 255) { VLOG_WARN("table %u is outside the supported range 0 through 255", table); return false; } *tablep = table; return true; } else { if (s[0] != '"') { table = ofputil_table_map_get_number(table_map, s); } else { size_t length = strlen(s); char *name = NULL; if (length > 1 && s[length - 1] == '"' && json_string_unescape(s + 1, length - 2, &name)) { table = ofputil_table_map_get_number(table_map, name); } free(name); } if (table != UINT8_MAX) { *tablep = table; return true; } return false; } } /* Appends to 's' a string representation of the OpenFlow table number 'table', * either the table number or a name drawn from 'table_map'. */ void ofputil_format_table(uint8_t table, const struct ofputil_table_map *table_map, struct ds *s) { const char *table_name = ofputil_table_map_get_name(table_map, table); if (table_name) { namemap_put_name(table_name, s); } else { ds_put_format(s, "%"PRIu8, table); } } /* Puts in the 'bufsize' byte in 'namebuf' a null-terminated string * representation of OpenFlow table number 'table', either the table's number * or a name drawn from 'table_map'. */ void ofputil_table_to_string(uint8_t table, const struct ofputil_table_map *table_map, char *namebuf, size_t bufsize) { const char *table_name = ofputil_table_map_get_name(table_map, table); if (table_name) { struct ds s = DS_EMPTY_INITIALIZER; namemap_put_name(table_name, &s); ovs_strlcpy(namebuf, ds_cstr(&s), bufsize); ds_destroy(&s); return; } snprintf(namebuf, bufsize, "%"PRIu8, table); } /* Table features. */ static enum ofperr pull_table_feature_property(struct ofpbuf *msg, struct ofpbuf *payload, uint64_t *typep) { enum ofperr error; error = ofpprop_pull(msg, payload, typep); if (payload && !error) { ofpbuf_pull(payload, (char *)payload->msg - (char *)payload->header); } return error; } static enum ofperr parse_action_bitmap(struct ofpbuf *payload, enum ofp_version ofp_version, uint64_t *ofpacts) { uint32_t types = 0; while (payload->size > 0) { enum ofperr error; uint64_t type; error = ofpprop_pull__(payload, NULL, 1, 0x10000, &type); if (error) { return error; } if (type < CHAR_BIT * sizeof types) { types |= 1u << type; } } *ofpacts = ofpact_bitmap_from_openflow(htonl(types), ofp_version); return 0; } static enum ofperr parse_instruction_ids(struct ofpbuf *payload, bool loose, uint32_t *insts) { *insts = 0; while (payload->size > 0) { enum ovs_instruction_type inst; enum ofperr error; uint64_t ofpit; /* OF1.3 and OF1.4 aren't clear about padding in the instruction IDs. * It seems clear that they aren't padded to 8 bytes, though, because * both standards say that "non-experimenter instructions are 4 bytes" * and do not mention any padding before the first instruction ID. * (There wouldn't be any point in padding to 8 bytes if the IDs were * aligned on an odd 4-byte boundary.) * * Anyway, we just assume they're all glommed together on byte * boundaries. */ error = ofpprop_pull__(payload, NULL, 1, 0x10000, &ofpit); if (error) { return error; } error = ovs_instruction_type_from_inst_type(&inst, ofpit); if (!error) { *insts |= 1u << inst; } else if (!loose) { return error; } } return 0; } static enum ofperr parse_table_features_next_table(struct ofpbuf *payload, unsigned long int *next_tables) { size_t i; memset(next_tables, 0, bitmap_n_bytes(255)); for (i = 0; i < payload->size; i++) { uint8_t id = ((const uint8_t *) payload->data)[i]; if (id >= 255) { return OFPERR_OFPBPC_BAD_VALUE; } bitmap_set1(next_tables, id); } return 0; } static enum ofperr parse_oxms(struct ofpbuf *payload, bool loose, struct mf_bitmap *exactp, struct mf_bitmap *maskedp) { struct mf_bitmap exact = MF_BITMAP_INITIALIZER; struct mf_bitmap masked = MF_BITMAP_INITIALIZER; while (payload->size > 0) { const struct mf_field *field; enum ofperr error; bool hasmask; error = nx_pull_header(payload, NULL, &field, &hasmask); if (!error) { bitmap_set1(hasmask ? masked.bm : exact.bm, field->id); } else if (error != OFPERR_OFPBMC_BAD_FIELD || !loose) { return error; } } if (exactp) { *exactp = exact; } else if (!bitmap_is_all_zeros(exact.bm, MFF_N_IDS)) { return OFPERR_OFPBMC_BAD_MASK; } if (maskedp) { *maskedp = masked; } else if (!bitmap_is_all_zeros(masked.bm, MFF_N_IDS)) { return OFPERR_OFPBMC_BAD_MASK; } return 0; } /* Converts an OFPMP_TABLE_FEATURES request or reply in 'msg' into an abstract * ofputil_table_features in 'tf'. * * If 'raw_properties' is nonnull, this function ignores properties and values * that it does not understand, as a controller would want to do when * interpreting capabilities provided by a switch. In this mode, on success, * it initializes 'raw_properties' to contain the properties that were parsed; * this allows the caller to later re-serialize the same properties without * change. * * If 'raw_properties' is null, this function treats unknown properties and * values as an error, as a switch would want to do when interpreting a * configuration request made by a controller. * * A single OpenFlow message can specify features for multiple tables. Calling * this function multiple times for a single 'msg' iterates through the tables * in the message. The caller must initially leave 'msg''s layer pointers null * and not modify them between calls. * * Returns 0 if successful, EOF if no tables were left in this 'msg', otherwise * a positive "enum ofperr" value. */ int ofputil_decode_table_features(struct ofpbuf *msg, struct ofputil_table_features *tf, struct ofpbuf *raw_properties) { bool loose = raw_properties != NULL; memset(tf, 0, sizeof *tf); if (!msg->header) { ofpraw_pull_assert(msg); } if (!msg->size) { return EOF; } const struct ofp_header *oh = msg->header; struct ofp13_table_features *otf = msg->data; if (msg->size < sizeof *otf) { return OFPERR_OFPBPC_BAD_LEN; } unsigned int len = ntohs(otf->length); if (len < sizeof *otf || len % 8 || len > msg->size) { return OFPERR_OFPBPC_BAD_LEN; } if (oh->version >= OFP15_VERSION) { if (!ofp15_table_features_command_is_valid(otf->command)) { return OFPERR_OFPTFFC_BAD_COMMAND; } tf->command = otf->command; } else { tf->command = OFPTFC15_REPLACE; } tf->table_id = otf->table_id; if (tf->table_id == OFPTT_ALL) { return OFPERR_OFPTFFC_BAD_TABLE; } ovs_strlcpy_arrays(tf->name, otf->name); tf->metadata_match = otf->metadata_match; tf->metadata_write = otf->metadata_write; tf->miss_config = OFPUTIL_TABLE_MISS_DEFAULT; if (oh->version >= OFP14_VERSION) { uint32_t caps = ntohl(otf->capabilities); tf->supports_eviction = (caps & OFPTC14_EVICTION) != 0; tf->supports_vacancy_events = (caps & OFPTC14_VACANCY_EVENTS) != 0; } else { tf->supports_eviction = -1; tf->supports_vacancy_events = -1; } tf->max_entries = ntohl(otf->max_entries); struct ofpbuf properties = ofpbuf_const_initializer(ofpbuf_pull(msg, len), len); ofpbuf_pull(&properties, sizeof *otf); tf->any_properties = properties.size > 0; if (raw_properties) { *raw_properties = properties; } uint32_t seen = 0; while (properties.size > 0) { struct ofpbuf payload; enum ofperr error; uint64_t type; error = pull_table_feature_property(&properties, &payload, &type); if (error) { return error; } if (type < 32) { uint32_t bit = 1u << type; if (seen & bit) { return OFPERR_OFPTFFC_BAD_FEATURES; } seen |= bit; } switch ((enum ofp13_table_feature_prop_type) type) { case OFPTFPT13_INSTRUCTIONS: error = parse_instruction_ids(&payload, loose, &tf->nonmiss.instructions); break; case OFPTFPT13_INSTRUCTIONS_MISS: error = parse_instruction_ids(&payload, loose, &tf->miss.instructions); break; case OFPTFPT13_NEXT_TABLES: error = parse_table_features_next_table(&payload, tf->nonmiss.next); break; case OFPTFPT13_NEXT_TABLES_MISS: error = parse_table_features_next_table(&payload, tf->miss.next); break; case OFPTFPT13_WRITE_ACTIONS: error = parse_action_bitmap(&payload, oh->version, &tf->nonmiss.write.ofpacts); break; case OFPTFPT13_WRITE_ACTIONS_MISS: error = parse_action_bitmap(&payload, oh->version, &tf->miss.write.ofpacts); break; case OFPTFPT13_APPLY_ACTIONS: error = parse_action_bitmap(&payload, oh->version, &tf->nonmiss.apply.ofpacts); break; case OFPTFPT13_APPLY_ACTIONS_MISS: error = parse_action_bitmap(&payload, oh->version, &tf->miss.apply.ofpacts); break; case OFPTFPT13_MATCH: error = parse_oxms(&payload, loose, &tf->match, &tf->mask); break; case OFPTFPT13_WILDCARDS: error = parse_oxms(&payload, loose, &tf->wildcard, NULL); break; case OFPTFPT13_WRITE_SETFIELD: error = parse_oxms(&payload, loose, &tf->nonmiss.write.set_fields, NULL); break; case OFPTFPT13_WRITE_SETFIELD_MISS: error = parse_oxms(&payload, loose, &tf->miss.write.set_fields, NULL); break; case OFPTFPT13_APPLY_SETFIELD: error = parse_oxms(&payload, loose, &tf->nonmiss.apply.set_fields, NULL); break; case OFPTFPT13_APPLY_SETFIELD_MISS: error = parse_oxms(&payload, loose, &tf->miss.apply.set_fields, NULL); break; case OFPTFPT13_EXPERIMENTER: case OFPTFPT13_EXPERIMENTER_MISS: default: error = OFPPROP_UNKNOWN(loose, "table features", type); break; } if (error) { return error; } } /* OpenFlow 1.3 and 1.4 always require all of the required properties. * OpenFlow 1.5 requires all of them if any property is present. */ unsigned int missing = (seen & OFPTFPT13_REQUIRED) ^ OFPTFPT13_REQUIRED; if (missing && (tf->any_properties || oh->version < OFP15_VERSION)) { VLOG_WARN_RL(&rl, "table features message missing %u required " "properties, including property %d", count_1bits(missing), rightmost_1bit_idx(missing)); return OFPERR_OFPTFFC_BAD_FEATURES; } /* Copy nonmiss to miss when appropriate. */ if (tf->any_properties) { if (!(seen & (1u << OFPTFPT13_INSTRUCTIONS_MISS))) { tf->miss.instructions = tf->nonmiss.instructions; } if (!(seen & (1u << OFPTFPT13_NEXT_TABLES_MISS))) { memcpy(tf->miss.next, tf->nonmiss.next, sizeof tf->miss.next); } if (!(seen & (1u << OFPTFPT13_WRITE_ACTIONS_MISS))) { tf->miss.write.ofpacts = tf->nonmiss.write.ofpacts; } if (!(seen & (1u << OFPTFPT13_APPLY_ACTIONS_MISS))) { tf->miss.apply.ofpacts = tf->nonmiss.apply.ofpacts; } if (!(seen & (1u << OFPTFPT13_WRITE_SETFIELD_MISS))) { tf->miss.write.set_fields = tf->nonmiss.write.set_fields; } if (!(seen & (1u << OFPTFPT13_APPLY_SETFIELD_MISS))) { tf->miss.apply.set_fields = tf->nonmiss.apply.set_fields; } } /* Fix inconsistencies: * * - Turn on 'match' bits that are set in 'mask', because maskable * fields are matchable. * * - Turn on 'wildcard' bits that are set in 'mask', because a field * that is arbitrarily maskable can be wildcarded entirely. * * - Turn off 'wildcard' bits that are not in 'match', because a field * must be matchable for it to be meaningfully wildcarded. */ bitmap_or(tf->match.bm, tf->mask.bm, MFF_N_IDS); bitmap_or(tf->wildcard.bm, tf->mask.bm, MFF_N_IDS); bitmap_and(tf->wildcard.bm, tf->match.bm, MFF_N_IDS); return 0; } /* Encodes and returns a request to obtain the table features of a switch. * The message is encoded for OpenFlow version 'ofp_version'. */ struct ofpbuf * ofputil_encode_table_features_request(enum ofp_version ofp_version) { struct ofpbuf *request = NULL; switch (ofp_version) { case OFP10_VERSION: case OFP11_VERSION: case OFP12_VERSION: ovs_fatal(0, "dump-table-features needs OpenFlow 1.3 or later " "(\'-O OpenFlow13\')"); case OFP13_VERSION: case OFP14_VERSION: case OFP15_VERSION: request = ofpraw_alloc(OFPRAW_OFPST13_TABLE_FEATURES_REQUEST, ofp_version, 0); break; default: OVS_NOT_REACHED(); } return request; } static void put_fields_property(struct ofpbuf *reply, const struct mf_bitmap *fields, const struct mf_bitmap *masks, enum ofp13_table_feature_prop_type property, enum ofp_version version) { size_t start_ofs; int field; start_ofs = ofpprop_start(reply, property); BITMAP_FOR_EACH_1 (field, MFF_N_IDS, fields->bm) { nx_put_header(reply, field, version, masks && bitmap_is_set(masks->bm, field)); } ofpprop_end(reply, start_ofs); } static void put_table_action_features(struct ofpbuf *reply, const struct ofputil_table_action_features *taf, enum ofp13_table_feature_prop_type actions_type, enum ofp13_table_feature_prop_type set_fields_type, int miss_offset, enum ofp_version version) { ofpprop_put_bitmap(reply, actions_type + miss_offset, ntohl(ofpact_bitmap_to_openflow(taf->ofpacts, version))); put_fields_property(reply, &taf->set_fields, NULL, set_fields_type + miss_offset, version); } static void put_table_instruction_features( struct ofpbuf *reply, const struct ofputil_table_instruction_features *tif, int miss_offset, enum ofp_version version) { size_t start_ofs; uint8_t table_id; ofpprop_put_bitmap(reply, OFPTFPT13_INSTRUCTIONS + miss_offset, ntohl(ovsinst_bitmap_to_openflow(tif->instructions, version))); start_ofs = ofpprop_start(reply, OFPTFPT13_NEXT_TABLES + miss_offset); BITMAP_FOR_EACH_1 (table_id, 255, tif->next) { ofpbuf_put(reply, &table_id, 1); } ofpprop_end(reply, start_ofs); put_table_action_features(reply, &tif->write, OFPTFPT13_WRITE_ACTIONS, OFPTFPT13_WRITE_SETFIELD, miss_offset, version); put_table_action_features(reply, &tif->apply, OFPTFPT13_APPLY_ACTIONS, OFPTFPT13_APPLY_SETFIELD, miss_offset, version); } /* Appends a table features record to 'msgs', which must be a * OFPT_TABLE_FEATURES request or reply. If 'raw_properties' is nonnull, then * it uses its contents verbatim as the table features properties, ignoring the * corresponding members of 'tf'. */ void ofputil_append_table_features(const struct ofputil_table_features *tf, const struct ofpbuf *raw_properties, struct ovs_list *msgs) { struct ofpbuf *msg = ofpbuf_from_list(ovs_list_back(msgs)); enum ofp_version version = ofpmp_version(msgs); size_t start_ofs = msg->size; struct ofp13_table_features *otf; otf = ofpbuf_put_zeros(msg, sizeof *otf); otf->table_id = tf->table_id; otf->command = version >= OFP15_VERSION ? tf->command : 0; ovs_strlcpy_arrays(otf->name, tf->name); otf->metadata_match = tf->metadata_match; otf->metadata_write = tf->metadata_write; if (version >= OFP14_VERSION) { if (tf->supports_eviction) { otf->capabilities |= htonl(OFPTC14_EVICTION); } if (tf->supports_vacancy_events) { otf->capabilities |= htonl(OFPTC14_VACANCY_EVENTS); } } otf->max_entries = htonl(tf->max_entries); if (raw_properties) { ofpbuf_put(msg, raw_properties->data, raw_properties->size); } else if (tf->any_properties) { put_table_instruction_features(msg, &tf->nonmiss, 0, version); put_table_instruction_features(msg, &tf->miss, 1, version); put_fields_property(msg, &tf->match, &tf->mask, OFPTFPT13_MATCH, version); put_fields_property(msg, &tf->wildcard, NULL, OFPTFPT13_WILDCARDS, version); } otf = ofpbuf_at_assert(msg, start_ofs, sizeof *otf); otf->length = htons(msg->size - start_ofs); ofpmp_postappend(msgs, start_ofs); } static enum ofperr parse_table_desc_vacancy_property(struct ofpbuf *property, struct ofputil_table_desc *td) { struct ofp14_table_mod_prop_vacancy *otv = property->data; if (property->size != sizeof *otv) { return OFPERR_OFPBPC_BAD_LEN; } td->table_vacancy.vacancy_down = otv->vacancy_down; td->table_vacancy.vacancy_up = otv->vacancy_up; td->table_vacancy.vacancy = otv->vacancy; return 0; } /* Decodes the next OpenFlow "table desc" message (of possibly several) from * 'msg' into an abstract form in '*td'. Returns 0 if successful, EOF if the * last "table desc" in 'msg' was already decoded, otherwise an OFPERR_* * value. */ int ofputil_decode_table_desc(struct ofpbuf *msg, struct ofputil_table_desc *td, enum ofp_version version) { memset(td, 0, sizeof *td); if (!msg->header) { ofpraw_pull_assert(msg); } if (!msg->size) { return EOF; } struct ofp14_table_desc *otd = ofpbuf_try_pull(msg, sizeof *otd); if (!otd) { VLOG_WARN_RL(&rl, "OFP14_TABLE_DESC reply has %"PRIu32" " "leftover bytes at end", msg->size); return OFPERR_OFPBRC_BAD_LEN; } td->table_id = otd->table_id; size_t length = ntohs(otd->length); if (length < sizeof *otd || length - sizeof *otd > msg->size) { VLOG_WARN_RL(&rl, "OFP14_TABLE_DESC reply claims invalid " "length %"PRIuSIZE, length); return OFPERR_OFPBRC_BAD_LEN; } length -= sizeof *otd; td->eviction = ofputil_decode_table_eviction(otd->config, version); td->vacancy = ofputil_decode_table_vacancy(otd->config, version); td->eviction_flags = UINT32_MAX; struct ofpbuf properties = ofpbuf_const_initializer( ofpbuf_pull(msg, length), length); while (properties.size > 0) { struct ofpbuf payload; enum ofperr error; uint64_t type; error = ofpprop_pull(&properties, &payload, &type); if (error) { return error; } switch (type) { case OFPTMPT14_EVICTION: error = ofpprop_parse_u32(&payload, &td->eviction_flags); break; case OFPTMPT14_VACANCY: error = parse_table_desc_vacancy_property(&payload, td); break; default: error = OFPPROP_UNKNOWN(true, "table_desc", type); break; } if (error) { return error; } } return 0; } /* Encodes and returns a request to obtain description of tables of a switch. * The message is encoded for OpenFlow version 'ofp_version'. */ struct ofpbuf * ofputil_encode_table_desc_request(enum ofp_version ofp_version) { struct ofpbuf *request = NULL; if (ofp_version >= OFP14_VERSION) { request = ofpraw_alloc(OFPRAW_OFPST14_TABLE_DESC_REQUEST, ofp_version, 0); } else { ovs_fatal(0, "dump-table-desc needs OpenFlow 1.4 or later " "(\'-O OpenFlow14\')"); } return request; } /* Function to append Table desc information in a reply list. */ void ofputil_append_table_desc_reply(const struct ofputil_table_desc *td, struct ovs_list *replies, enum ofp_version version) { struct ofpbuf *reply = ofpbuf_from_list(ovs_list_back(replies)); size_t start_otd; struct ofp14_table_desc *otd; start_otd = reply->size; ofpbuf_put_zeros(reply, sizeof *otd); if (td->eviction_flags != UINT32_MAX) { ofpprop_put_u32(reply, OFPTMPT14_EVICTION, td->eviction_flags); } if (td->vacancy == OFPUTIL_TABLE_VACANCY_ON) { struct ofp14_table_mod_prop_vacancy *otv; otv = ofpprop_put_zeros(reply, OFPTMPT14_VACANCY, sizeof *otv); otv->vacancy_down = td->table_vacancy.vacancy_down; otv->vacancy_up = td->table_vacancy.vacancy_up; otv->vacancy = td->table_vacancy.vacancy; } otd = ofpbuf_at_assert(reply, start_otd, sizeof *otd); otd->length = htons(reply->size - start_otd); otd->table_id = td->table_id; otd->config = ofputil_encode_table_config(OFPUTIL_TABLE_MISS_DEFAULT, td->eviction, td->vacancy, version); ofpmp_postappend(replies, start_otd); } static const char * ofputil_eviction_flag_to_string(uint32_t bit) { enum ofp14_table_mod_prop_eviction_flag eviction_flag = bit; switch (eviction_flag) { case OFPTMPEF14_OTHER: return "OTHER"; case OFPTMPEF14_IMPORTANCE: return "IMPORTANCE"; case OFPTMPEF14_LIFETIME: return "LIFETIME"; } return NULL; } /* Appends to 'string' a description of the bitmap of OFPTMPEF14_* values in * 'eviction_flags'. */ static void ofputil_put_eviction_flags(struct ds *string, uint32_t eviction_flags) { if (eviction_flags != UINT32_MAX) { ofp_print_bit_names(string, eviction_flags, ofputil_eviction_flag_to_string, '|'); } else { ds_put_cstr(string, "(default)"); } } void ofputil_table_desc_format(struct ds *s, const struct ofputil_table_desc *td, const struct ofputil_table_map *table_map) { ds_put_format(s, "\n table "); ofputil_format_table(td->table_id, table_map, s); ds_put_cstr(s, ":\n"); ds_put_format(s, " eviction=%s eviction_flags=", ofputil_table_eviction_to_string(td->eviction)); ofputil_put_eviction_flags(s, td->eviction_flags); ds_put_char(s, '\n'); ds_put_format(s, " vacancy=%s", ofputil_table_vacancy_to_string(td->vacancy)); if (td->vacancy == OFPUTIL_TABLE_VACANCY_ON) { ds_put_format(s, " vacancy_down=%"PRIu8"%%", td->table_vacancy.vacancy_down); ds_put_format(s, " vacancy_up=%"PRIu8"%%", td->table_vacancy.vacancy_up); ds_put_format(s, " vacancy=%"PRIu8"%%", td->table_vacancy.vacancy); } ds_put_char(s, '\n'); } /* This function parses Vacancy property, and decodes the * ofp14_table_mod_prop_vacancy in ofputil_table_mod. * Returns OFPERR_OFPBPC_BAD_VALUE error code when vacancy_down is * greater than vacancy_up and also when current vacancy has non-zero * value. Returns 0 on success. */ static enum ofperr parse_table_mod_vacancy_property(struct ofpbuf *property, struct ofputil_table_mod *tm) { struct ofp14_table_mod_prop_vacancy *otv = property->data; if (property->size != sizeof *otv) { return OFPERR_OFPBPC_BAD_LEN; } tm->table_vacancy.vacancy_down = otv->vacancy_down; tm->table_vacancy.vacancy_up = otv->vacancy_up; if (tm->table_vacancy.vacancy_down > tm->table_vacancy.vacancy_up) { OFPPROP_LOG(&rl, false, "Value of vacancy_down is greater than vacancy_up"); return OFPERR_OFPBPC_BAD_VALUE; } if (tm->table_vacancy.vacancy_down > 100 || tm->table_vacancy.vacancy_up > 100) { OFPPROP_LOG(&rl, false, "Vacancy threshold percentage " "should not be greater than 100"); return OFPERR_OFPBPC_BAD_VALUE; } tm->table_vacancy.vacancy = otv->vacancy; if (tm->table_vacancy.vacancy) { OFPPROP_LOG(&rl, false, "Vacancy value should be zero for table-mod messages"); return OFPERR_OFPBPC_BAD_VALUE; } return 0; } /* Given 'config', taken from an OpenFlow 'version' message that specifies * table configuration (a table mod, table stats, or table features message), * returns the table vacancy configuration that it specifies. * * Only OpenFlow 1.4 and later specify table vacancy configuration this way, * so for other 'version' this function always returns * OFPUTIL_TABLE_VACANCY_DEFAULT. */ static enum ofputil_table_vacancy ofputil_decode_table_vacancy(ovs_be32 config, enum ofp_version version) { return (version < OFP14_VERSION ? OFPUTIL_TABLE_VACANCY_DEFAULT : config & htonl(OFPTC14_VACANCY_EVENTS) ? OFPUTIL_TABLE_VACANCY_ON : OFPUTIL_TABLE_VACANCY_OFF); } /* Given 'config', taken from an OpenFlow 'version' message that specifies * table configuration (a table mod, table stats, or table features message), * returns the table eviction configuration that it specifies. * * Only OpenFlow 1.4 and later specify table eviction configuration this way, * so for other 'version' values this function always returns * OFPUTIL_TABLE_EVICTION_DEFAULT. */ static enum ofputil_table_eviction ofputil_decode_table_eviction(ovs_be32 config, enum ofp_version version) { return (version < OFP14_VERSION ? OFPUTIL_TABLE_EVICTION_DEFAULT : config & htonl(OFPTC14_EVICTION) ? OFPUTIL_TABLE_EVICTION_ON : OFPUTIL_TABLE_EVICTION_OFF); } /* Returns a bitmap of OFPTC* values suitable for 'config' fields in various * OpenFlow messages of the given 'version', based on the provided 'miss' and * 'eviction' values. */ static ovs_be32 ofputil_encode_table_config(enum ofputil_table_miss miss, enum ofputil_table_eviction eviction, enum ofputil_table_vacancy vacancy, enum ofp_version version) { uint32_t config = 0; /* Search for "OFPTC_* Table Configuration" in the documentation for more * information on the crazy evolution of this field. */ switch (version) { case OFP10_VERSION: /* OpenFlow 1.0 didn't have such a field, any value ought to do. */ return htonl(0); case OFP11_VERSION: case OFP12_VERSION: /* OpenFlow 1.1 and 1.2 define only OFPTC11_TABLE_MISS_*. */ switch (miss) { case OFPUTIL_TABLE_MISS_DEFAULT: /* Really this shouldn't be used for encoding (the caller should * provide a specific value) but I can't imagine that defaulting to * the fall-through case here will hurt. */ case OFPUTIL_TABLE_MISS_CONTROLLER: default: return htonl(OFPTC11_TABLE_MISS_CONTROLLER); case OFPUTIL_TABLE_MISS_CONTINUE: return htonl(OFPTC11_TABLE_MISS_CONTINUE); case OFPUTIL_TABLE_MISS_DROP: return htonl(OFPTC11_TABLE_MISS_DROP); } OVS_NOT_REACHED(); case OFP13_VERSION: /* OpenFlow 1.3 removed OFPTC11_TABLE_MISS_* and didn't define any new * flags, so this is correct. */ return htonl(0); case OFP14_VERSION: case OFP15_VERSION: /* OpenFlow 1.4 introduced OFPTC14_EVICTION and * OFPTC14_VACANCY_EVENTS. */ if (eviction == OFPUTIL_TABLE_EVICTION_ON) { config |= OFPTC14_EVICTION; } if (vacancy == OFPUTIL_TABLE_VACANCY_ON) { config |= OFPTC14_VACANCY_EVENTS; } return htonl(config); } OVS_NOT_REACHED(); } /* Given 'config', taken from an OpenFlow 'version' message that specifies * table configuration (a table mod, table stats, or table features message), * returns the table miss configuration that it specifies. * * Only OpenFlow 1.1 and 1.2 specify table miss configurations this way, so for * other 'version' values this function always returns * OFPUTIL_TABLE_MISS_DEFAULT. */ static enum ofputil_table_miss ofputil_decode_table_miss(ovs_be32 config_, enum ofp_version version) { uint32_t config = ntohl(config_); if (version == OFP11_VERSION || version == OFP12_VERSION) { switch (config & OFPTC11_TABLE_MISS_MASK) { case OFPTC11_TABLE_MISS_CONTROLLER: return OFPUTIL_TABLE_MISS_CONTROLLER; case OFPTC11_TABLE_MISS_CONTINUE: return OFPUTIL_TABLE_MISS_CONTINUE; case OFPTC11_TABLE_MISS_DROP: return OFPUTIL_TABLE_MISS_DROP; default: VLOG_WARN_RL(&rl, "bad table miss config %d", config); return OFPUTIL_TABLE_MISS_CONTROLLER; } } else { return OFPUTIL_TABLE_MISS_DEFAULT; } } /* Decodes the OpenFlow "table mod" message in '*oh' into an abstract form in * '*pm'. Returns 0 if successful, otherwise an OFPERR_* value. */ enum ofperr ofputil_decode_table_mod(const struct ofp_header *oh, struct ofputil_table_mod *pm) { memset(pm, 0, sizeof *pm); pm->miss = OFPUTIL_TABLE_MISS_DEFAULT; pm->eviction = OFPUTIL_TABLE_EVICTION_DEFAULT; pm->eviction_flags = UINT32_MAX; pm->vacancy = OFPUTIL_TABLE_VACANCY_DEFAULT; struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length)); enum ofpraw raw = ofpraw_pull_assert(&b); if (raw == OFPRAW_OFPT11_TABLE_MOD) { const struct ofp11_table_mod *otm = b.data; pm->table_id = otm->table_id; pm->miss = ofputil_decode_table_miss(otm->config, oh->version); } else if (raw == OFPRAW_OFPT14_TABLE_MOD) { const struct ofp14_table_mod *otm = ofpbuf_pull(&b, sizeof *otm); pm->table_id = otm->table_id; pm->miss = ofputil_decode_table_miss(otm->config, oh->version); pm->eviction = ofputil_decode_table_eviction(otm->config, oh->version); pm->vacancy = ofputil_decode_table_vacancy(otm->config, oh->version); while (b.size > 0) { struct ofpbuf property; enum ofperr error; uint64_t type; error = ofpprop_pull(&b, &property, &type); if (error) { return error; } switch (type) { case OFPTMPT14_EVICTION: error = ofpprop_parse_u32(&property, &pm->eviction); break; case OFPTMPT14_VACANCY: error = parse_table_mod_vacancy_property(&property, pm); break; default: error = OFPERR_OFPBRC_BAD_TYPE; break; } if (error) { return error; } } } else { return OFPERR_OFPBRC_BAD_TYPE; } return 0; } /* Converts the abstract form of a "table mod" message in '*tm' into an * OpenFlow message suitable for 'protocol', and returns that encoded form in a * buffer owned by the caller. */ struct ofpbuf * ofputil_encode_table_mod(const struct ofputil_table_mod *tm, enum ofputil_protocol protocol) { enum ofp_version ofp_version = ofputil_protocol_to_ofp_version(protocol); struct ofpbuf *b; switch (ofp_version) { case OFP10_VERSION: { ovs_fatal(0, "table mod needs OpenFlow 1.1 or later " "(\'-O OpenFlow11\')"); break; } case OFP11_VERSION: case OFP12_VERSION: case OFP13_VERSION: { struct ofp11_table_mod *otm; b = ofpraw_alloc(OFPRAW_OFPT11_TABLE_MOD, ofp_version, 0); otm = ofpbuf_put_zeros(b, sizeof *otm); otm->table_id = tm->table_id; otm->config = ofputil_encode_table_config(tm->miss, tm->eviction, tm->vacancy, ofp_version); break; } case OFP14_VERSION: case OFP15_VERSION: { struct ofp14_table_mod *otm; b = ofpraw_alloc(OFPRAW_OFPT14_TABLE_MOD, ofp_version, 0); otm = ofpbuf_put_zeros(b, sizeof *otm); otm->table_id = tm->table_id; otm->config = ofputil_encode_table_config(tm->miss, tm->eviction, tm->vacancy, ofp_version); if (tm->eviction_flags != UINT32_MAX) { ofpprop_put_u32(b, OFPTMPT14_EVICTION, tm->eviction_flags); } if (tm->vacancy == OFPUTIL_TABLE_VACANCY_ON) { struct ofp14_table_mod_prop_vacancy *otv; otv = ofpprop_put_zeros(b, OFPTMPT14_VACANCY, sizeof *otv); otv->vacancy_down = tm->table_vacancy.vacancy_down; otv->vacancy_up = tm->table_vacancy.vacancy_up; } break; } default: OVS_NOT_REACHED(); } return b; } void ofputil_table_mod_format(struct ds *s, const struct ofputil_table_mod *tm, const struct ofputil_table_map *table_map) { if (tm->table_id == 0xff) { ds_put_cstr(s, " table_id: ALL_TABLES"); } else { ds_put_format(s, " table_id="); ofputil_format_table(tm->table_id, table_map, s); } if (tm->miss != OFPUTIL_TABLE_MISS_DEFAULT) { ds_put_format(s, ", flow_miss_config=%s", ofputil_table_miss_to_string(tm->miss)); } if (tm->eviction != OFPUTIL_TABLE_EVICTION_DEFAULT) { ds_put_format(s, ", eviction=%s", ofputil_table_eviction_to_string(tm->eviction)); } if (tm->eviction_flags != UINT32_MAX) { ds_put_cstr(s, "eviction_flags="); ofputil_put_eviction_flags(s, tm->eviction_flags); } if (tm->vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) { ds_put_format(s, ", vacancy=%s", ofputil_table_vacancy_to_string(tm->vacancy)); if (tm->vacancy == OFPUTIL_TABLE_VACANCY_ON) { ds_put_format(s, " vacancy:%"PRIu8"" ",%"PRIu8"", tm->table_vacancy.vacancy_down, tm->table_vacancy.vacancy_up); } } } /* Convert 'setting' (as described for the "mod-table" command * in ovs-ofctl man page) into 'tm->table_vacancy->vacancy_up' and * 'tm->table_vacancy->vacancy_down' threshold values. * For the two threshold values, value of vacancy_up is always greater * than value of vacancy_down. * * 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_ofp_table_vacancy(struct ofputil_table_mod *tm, const char *setting) { char *save_ptr = NULL; char *vac_up, *vac_down; char *value = xstrdup(setting); char *ret_msg; int vacancy_up, vacancy_down; strtok_r(value, ":", &save_ptr); vac_down = strtok_r(NULL, ",", &save_ptr); if (!vac_down) { ret_msg = xasprintf("Vacancy down value missing"); goto exit; } if (!str_to_int(vac_down, 0, &vacancy_down) || vacancy_down < 0 || vacancy_down > 100) { ret_msg = xasprintf("Invalid vacancy down value \"%s\"", vac_down); goto exit; } vac_up = strtok_r(NULL, ",", &save_ptr); if (!vac_up) { ret_msg = xasprintf("Vacancy up value missing"); goto exit; } if (!str_to_int(vac_up, 0, &vacancy_up) || vacancy_up < 0 || vacancy_up > 100) { ret_msg = xasprintf("Invalid vacancy up value \"%s\"", vac_up); goto exit; } if (vacancy_down > vacancy_up) { ret_msg = xasprintf("Invalid vacancy range, vacancy up should be " "greater than vacancy down (%s)", ofperr_to_string(OFPERR_OFPBPC_BAD_VALUE)); goto exit; } free(value); tm->table_vacancy.vacancy_down = vacancy_down; tm->table_vacancy.vacancy_up = vacancy_up; return NULL; exit: free(value); return ret_msg; } /* Convert 'table_id' and 'setting' (as described for the "mod-table" command * in the ovs-ofctl man page) into 'tm' for sending a table_mod command to a * switch. If 'setting' sets the name of the table, puts the new name in * '*namep' (a suffix of 'setting'), otherwise stores NULL. * * Stores a bitmap of the OpenFlow versions that are usable for 'tm' into * '*usable_versions'. * * 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 parse_ofp_table_mod(struct ofputil_table_mod *tm, const char **namep, const char *table_id, const char *setting, const struct ofputil_table_map *table_map, uint32_t *usable_versions) { *usable_versions = 0; *namep = NULL; if (!strcasecmp(table_id, "all")) { tm->table_id = OFPTT_ALL; } else if (!ofputil_table_from_string(table_id, table_map, &tm->table_id)) { return xasprintf("unknown table \"%s\"", table_id); } tm->miss = OFPUTIL_TABLE_MISS_DEFAULT; tm->eviction = OFPUTIL_TABLE_EVICTION_DEFAULT; tm->eviction_flags = UINT32_MAX; tm->vacancy = OFPUTIL_TABLE_VACANCY_DEFAULT; tm->table_vacancy.vacancy_down = 0; tm->table_vacancy.vacancy_up = 0; tm->table_vacancy.vacancy = 0; /* Only OpenFlow 1.1 and 1.2 can configure table-miss via table_mod. * Only OpenFlow 1.4+ can configure eviction and vacancy events * via table_mod. */ if (!strcmp(setting, "controller")) { tm->miss = OFPUTIL_TABLE_MISS_CONTROLLER; *usable_versions = (1u << OFP11_VERSION) | (1u << OFP12_VERSION); } else if (!strcmp(setting, "continue")) { tm->miss = OFPUTIL_TABLE_MISS_CONTINUE; *usable_versions = (1u << OFP11_VERSION) | (1u << OFP12_VERSION); } else if (!strcmp(setting, "drop")) { tm->miss = OFPUTIL_TABLE_MISS_DROP; *usable_versions = (1u << OFP11_VERSION) | (1u << OFP12_VERSION); } else if (!strcmp(setting, "evict")) { tm->eviction = OFPUTIL_TABLE_EVICTION_ON; *usable_versions = (1 << OFP14_VERSION) | (1u << OFP15_VERSION); } else if (!strcmp(setting, "noevict")) { tm->eviction = OFPUTIL_TABLE_EVICTION_OFF; *usable_versions = (1 << OFP14_VERSION) | (1u << OFP15_VERSION); } else if (!strncmp(setting, "vacancy", strcspn(setting, ":"))) { tm->vacancy = OFPUTIL_TABLE_VACANCY_ON; *usable_versions = (1 << OFP14_VERSION) | (1u << OFP15_VERSION); char *error = parse_ofp_table_vacancy(tm, setting); if (error) { return error; } } else if (!strcmp(setting, "novacancy")) { tm->vacancy = OFPUTIL_TABLE_VACANCY_OFF; *usable_versions = (1 << OFP14_VERSION) | (1u << OFP15_VERSION); } else if (tm->table_id != OFPTT_ALL && !strncmp(setting, "name:", 5)) { *namep = setting + 5; *usable_versions = ((1 << OFP13_VERSION) | (1u << OFP14_VERSION) | (1u << OFP15_VERSION)); } else { return xasprintf("invalid table_mod setting %s", setting); } if (tm->table_id == 0xfe && tm->miss == OFPUTIL_TABLE_MISS_CONTINUE) { return xstrdup("last table's flow miss handling can not be continue"); } return NULL; } /* Returns true if 's' consists of only ASCII digits (and at least one). */ static bool is_all_digits(const char *s) { return s[0] && s[strspn(s, "0123456789")] == '\0'; } /* Returns true if 'a' and 'b' are the same except 'b' ends in a number one * larger than 'a', for example, "reg0" and "reg1" */ static bool are_names_sequential(const char *a, const char *b) { /* Skip common prefix. */ for (; *a == *b; a++, b++) { if (!*a) { /* 'a' and 'b' are the same. Weird, but not sequential. */ return false; } } return (is_all_digits(a) && is_all_digits(b) && strlen(a) < 10 && strlen(b) < 10 && atoi(a) + 1 == atoi(b)); } /* Returns the number of sequential names at the start of the 'n' strings in * 'ids'. Returns at least 1 (if 'n' > 0). */ static size_t count_sequential_suffix_run(const char *ids[], size_t n) { for (size_t i = 1; ; i++) { if (i >= n || !are_names_sequential(ids[i - 1], ids[i])) { /* "x0...x1" is worse than "x0 x1", so suppress it. */ return i == 2 ? 1 : i; } } } /* Counts the length of the longest common prefix (that ends in "_") between * strings 'a' and 'b'. Returns 0 if they have no common prefix. */ static size_t count_common_prefix(const char *a, const char *b) { size_t retval = 0; for (size_t i = 0; ; i++) { if (a[i] != b[i] || !a[i]) { return retval; } else if (a[i] == '_') { retval = i + 1; } } } /* Returns the number of strings in the longest run of strings with a common * prefix (that ends in "_") at the beginning of the 'n' strings in 'ids'. * This is at least 1, if 'n' > 0. All the strings are already known to have a * common prefix of length 'prefix_len', so that's not of interest; only an * additional common prefix is interesting. * * If this returns 'n' > 1, then '*extra_prefix_lenp' receives the length of * the additional common prefix. Otherwise '*extra_prefix_lenp' receives 0. */ static size_t count_common_prefix_run(const char *ids[], size_t n, size_t prefix_len, size_t *extra_prefix_lenp) { *extra_prefix_lenp = 0; if (n < 2) { return n; } size_t extra_prefix_len = count_common_prefix(ids[0] + prefix_len, ids[1] + prefix_len); if (!extra_prefix_len) { return 1; } size_t i = 2; while (i < n) { size_t next = count_common_prefix(ids[0] + prefix_len, ids[i] + prefix_len); if (!next) { break; } else if (next < extra_prefix_len) { next = extra_prefix_len; } i++; } *extra_prefix_lenp = extra_prefix_len; return i; } /* Appends the 'n' names in 'ids' to 's', omitting the first 'prefix_len' bytes * of each name (which should all be the same), separating them from each other * with spaces. * * Two kinds of abbreviation are implemented: * * - Common prefixes: "eth_src eth_dst eth_type" => "eth_{src,dst,type}". * * - Sequential suffixes: "reg0 reg1 reg2 reg3" => "reg0...reg3". */ static void print_names(struct ds *s, const char *ids[], size_t n, size_t prefix_len) { int group = 0; while (n > 0) { if (group++) { ds_put_char(s, prefix_len ? ',' : ' '); } /* Count the prefix and suffix runs at the beginning of 'ids'. As of * this writing we don't have any sequentially numbered fields whose * names contain "_", so we should only have one or the other at a * time. However, if we end up with something like "a_0 a_1 a_2" * someday, we want to render it as a_0...a_2, not as a_{0...2}, so * given equal suffix and prefix runs, prefer the suffix. */ size_t extra_prefix_len; size_t prefix_run = count_common_prefix_run(ids, n, prefix_len, &extra_prefix_len); size_t suffix_run = count_sequential_suffix_run(ids, n); size_t run = MAX(prefix_run, suffix_run); if (suffix_run >= prefix_run) { ds_put_format(s, "%s", ids[0] + prefix_len); if (run > 1) { ds_put_format(s, "...%s", ids[run - 1] + prefix_len); } } else { ds_put_format(s, "%.*s{", (int) extra_prefix_len, ids[0] + prefix_len); print_names(s, ids, run, prefix_len + extra_prefix_len); ds_put_char(s, '}'); } ids += run; n -= run; } } static void print_mf_bitmap(struct ds *s, const struct mf_bitmap *mfb) { const char *ids[MFF_N_IDS]; size_t n = 0; int i; BITMAP_FOR_EACH_1 (i, MFF_N_IDS, mfb->bm) { ids[n++] = mf_from_id(i)->name; } if (n > 0) { ds_put_char(s, ' '); print_names(s, ids, n, 0); } } static void print_table_action_features(struct ds *s, const struct ofputil_table_action_features *taf) { if (taf->ofpacts) { ds_put_cstr(s, " actions: "); ofpact_bitmap_format(taf->ofpacts, s); ds_put_char(s, '\n'); } if (!bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS)) { ds_put_cstr(s, " supported on Set-Field:"); print_mf_bitmap(s, &taf->set_fields); ds_put_char(s, '\n'); } } static bool table_action_features_equal(const struct ofputil_table_action_features *a, const struct ofputil_table_action_features *b) { return (a->ofpacts == b->ofpacts && bitmap_equal(a->set_fields.bm, b->set_fields.bm, MFF_N_IDS)); } static bool table_action_features_empty(const struct ofputil_table_action_features *taf) { return !taf->ofpacts && bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS); } static void print_table_instruction_features( struct ds *s, const struct ofputil_table_instruction_features *tif, const struct ofputil_table_instruction_features *prev_tif) { int start, end; if (!bitmap_is_all_zeros(tif->next, 255)) { ds_put_cstr(s, " next tables: "); for (start = bitmap_scan(tif->next, 1, 0, 255); start < 255; start = bitmap_scan(tif->next, 1, end, 255)) { end = bitmap_scan(tif->next, 0, start + 1, 255); if (end == start + 1) { ds_put_format(s, "%d,", start); } else { ds_put_format(s, "%d-%d,", start, end - 1); } } ds_chomp(s, ','); if (ds_last(s) == ' ') { ds_put_cstr(s, "none"); } ds_put_char(s, '\n'); } if (tif->instructions) { if (prev_tif && tif->instructions == prev_tif->instructions) { ds_put_cstr(s, " (same instructions)\n"); } else { ds_put_cstr(s, " instructions: "); int i; for (i = 0; i < 32; i++) { if (tif->instructions & (1u << i)) { const char *name = ovs_instruction_name_from_type(i); if (name) { ds_put_cstr(s, name); } else { ds_put_format(s, "%d", i); } ds_put_char(s, ' '); } } ds_chomp(s, ' '); ds_put_char(s, '\n'); } } if (prev_tif && table_action_features_equal(&tif->write, &prev_tif->write) && table_action_features_equal(&tif->apply, &prev_tif->apply) && !bitmap_is_all_zeros(tif->write.set_fields.bm, MFF_N_IDS)) { ds_put_cstr(s, " (same actions)\n"); } else if (!table_action_features_equal(&tif->write, &tif->apply)) { ds_put_cstr(s, " Write-Actions features:\n"); print_table_action_features(s, &tif->write); ds_put_cstr(s, " Apply-Actions features:\n"); print_table_action_features(s, &tif->apply); } else if (tif->write.ofpacts || !bitmap_is_all_zeros(tif->write.set_fields.bm, MFF_N_IDS)) { ds_put_cstr(s, " Write-Actions and Apply-Actions features:\n"); print_table_action_features(s, &tif->write); } } static void print_matches(struct ds *s, const struct ofputil_table_features *f, bool mask, bool wc, const char *title) { const struct mf_bitmap m = mask ? f->mask : mf_bitmap_not(f->mask); const struct mf_bitmap w = wc ? f->wildcard : mf_bitmap_not(f->wildcard); const struct mf_bitmap bm = mf_bitmap_and(f->match, mf_bitmap_and(m, w)); if (!bitmap_is_all_zeros(bm.bm, MFF_N_IDS)) { ds_put_format(s, " %s:", title); print_mf_bitmap(s, &bm); ds_put_char(s, '\n'); } } /* Compares bitmaps of next tables 'a' and 'b', for tables 'a_table_id' and * 'b_table_id', respectively. Returns true if the bitmaps are equal. * * The bitmaps are considered equal if b_table_id == a_table_id + 1 and the bit * for 'b_table_id' is set in 'a' but not in 'b'. This is because OpenFlow * requires that a table not be able to do a goto_table back to its own table * or an earlier one. Without considering these equivalent, every table will * be different from every one in some way, which just isn't useful in printing * table features. */ static bool table_instruction_features_next_equal(const unsigned long *a, int a_table_id, const unsigned long *b, int b_table_id) { if (b_table_id == a_table_id + 1 && bitmap_is_set(a, b_table_id) && !bitmap_is_set(b, b_table_id)) { for (size_t i = 0; i < BITMAP_N_LONGS(255); i++) { unsigned long diff = a[i] ^ b[i]; if (i == b_table_id / BITMAP_ULONG_BITS) { diff &= ~bitmap_bit__(b_table_id); } if (diff) { return false; } } return true; } else if (a_table_id == b_table_id + 1) { return table_instruction_features_next_equal(b, b_table_id, a, a_table_id); } else { return bitmap_equal(a, b, 255); } } static bool table_instruction_features_equal( const struct ofputil_table_instruction_features *a, int a_table_id, const struct ofputil_table_instruction_features *b, int b_table_id) { return (table_instruction_features_next_equal(a->next, a_table_id, b->next, b_table_id) && a->instructions == b->instructions && table_action_features_equal(&a->write, &b->write) && table_action_features_equal(&a->apply, &b->apply)); } static bool table_instruction_features_empty( const struct ofputil_table_instruction_features *tif) { return (bitmap_is_all_zeros(tif->next, 255) && !tif->instructions && table_action_features_empty(&tif->write) && table_action_features_empty(&tif->apply)); } static bool table_features_equal(const struct ofputil_table_features *a, const struct ofputil_table_features *b) { return (a->metadata_match == b->metadata_match && a->metadata_write == b->metadata_write && a->miss_config == b->miss_config && a->supports_eviction == b->supports_eviction && a->supports_vacancy_events == b->supports_vacancy_events && a->max_entries == b->max_entries && table_instruction_features_equal(&a->nonmiss, a->table_id, &b->nonmiss, b->table_id) && table_instruction_features_equal(&a->miss, a->table_id, &b->miss, b->table_id) && bitmap_equal(a->match.bm, b->match.bm, MFF_N_IDS)); } static bool table_features_empty(const struct ofputil_table_features *tf) { return (!tf->metadata_match && !tf->metadata_write && tf->miss_config == OFPUTIL_TABLE_MISS_DEFAULT && tf->supports_eviction < 0 && tf->supports_vacancy_events < 0 && !tf->max_entries && table_instruction_features_empty(&tf->nonmiss) && table_instruction_features_empty(&tf->miss) && bitmap_is_all_zeros(tf->match.bm, MFF_N_IDS)); } static bool table_stats_equal(const struct ofputil_table_stats *a, const struct ofputil_table_stats *b) { return (a->active_count == b->active_count && a->lookup_count == b->lookup_count && a->matched_count == b->matched_count); } void ofputil_table_features_format( struct ds *s, const struct ofputil_table_features *features, const struct ofputil_table_features *prev_features, const struct ofputil_table_stats *stats, const struct ofputil_table_stats *prev_stats, int *first_ditto, int *last_ditto) { if (!prev_features && features->command != OFPTFC15_REPLACE) { ds_put_format(s, "\n command: %s", ofp15_table_features_command_to_string( features->command)); } int table = features->table_id; int prev_table = prev_features ? prev_features->table_id : 0; bool same_stats = !stats || (prev_stats && table_stats_equal(stats, prev_stats)); bool same_features = prev_features && table_features_equal(features, prev_features); if (same_stats && same_features && !features->name[0]) { if (*first_ditto < 0) { *first_ditto = table; } *last_ditto = table; return; } ofputil_table_features_format_finish(s, *first_ditto, *last_ditto); *first_ditto = -1; ds_put_format(s, "\n table %d", table); if (features->name[0]) { ds_put_format(s, " (\"%s\")", features->name); } ds_put_char(s, ':'); if (same_stats && same_features) { ds_put_cstr(s, " ditto"); return; } ds_put_char(s, '\n'); if (stats) { ds_put_format(s, " active=%"PRIu32", ", stats->active_count); ds_put_format(s, "lookup=%"PRIu64", ", stats->lookup_count); ds_put_format(s, "matched=%"PRIu64"\n", stats->matched_count); } if (same_features) { if (!table_features_empty(features)) { ds_put_cstr(s, " (same features)\n"); } return; } if (features->metadata_match || features->metadata_write) { ds_put_format(s, " metadata: match=%#"PRIx64" write=%#"PRIx64"\n", ntohll(features->metadata_match), ntohll(features->metadata_write)); } if (features->miss_config != OFPUTIL_TABLE_MISS_DEFAULT) { ds_put_format(s, " config=%s\n", ofputil_table_miss_to_string(features->miss_config)); } if (features->supports_eviction >= 0) { ds_put_format(s, " eviction: %ssupported\n", features->supports_eviction ? "" : "not "); } if (features->supports_vacancy_events >= 0) { ds_put_format(s, " vacancy events: %ssupported\n", features->supports_vacancy_events ? "" : "not "); } if (features->max_entries) { ds_put_format(s, " max_entries=%"PRIu32"\n", features->max_entries); } const struct ofputil_table_instruction_features *prev_nonmiss = prev_features ? &prev_features->nonmiss : NULL; const struct ofputil_table_instruction_features *prev_miss = prev_features ? &prev_features->miss : NULL; if (prev_features && table_instruction_features_equal(&features->nonmiss, table, prev_nonmiss, prev_table) && table_instruction_features_equal(&features->miss, table, prev_miss, prev_table)) { if (!table_instruction_features_empty(&features->nonmiss)) { ds_put_cstr(s, " (same instructions)\n"); } } else if (!table_instruction_features_equal(&features->nonmiss, table, &features->miss, table)) { ds_put_cstr(s, " instructions (other than table miss):\n"); print_table_instruction_features(s, &features->nonmiss, prev_nonmiss); ds_put_cstr(s, " instructions (table miss):\n"); print_table_instruction_features(s, &features->miss, prev_miss); } else if (!table_instruction_features_empty(&features->nonmiss)) { ds_put_cstr(s, " instructions (table miss and others):\n"); print_table_instruction_features(s, &features->nonmiss, prev_nonmiss); } if (!bitmap_is_all_zeros(features->match.bm, MFF_N_IDS)) { if (prev_features && bitmap_equal(features->match.bm, prev_features->match.bm, MFF_N_IDS)) { ds_put_cstr(s, " (same matching)\n"); } else { ds_put_cstr(s, " matching:\n"); print_matches(s, features, true, true, "arbitrary mask"); print_matches(s, features, false, true, "exact match or wildcard"); print_matches(s, features, false, false, "must exact match"); } } } void ofputil_table_features_format_finish(struct ds *s, int first_ditto, int last_ditto) { if (first_ditto < 0) { return; } ds_put_char(s, '\n'); if (first_ditto == last_ditto) { ds_put_format(s, " table %d: ditto\n", first_ditto); } else { ds_put_format(s, " tables %d...%d: ditto\n", first_ditto, last_ditto); } } /* Returns true if 'super' is a superset of 'sub', false otherwise. */ static bool ofputil_table_action_features_is_superset( const struct ofputil_table_action_features *super, const struct ofputil_table_action_features *sub) { return (uint_is_superset(super->ofpacts, sub->ofpacts) && mf_bitmap_is_superset(&super->set_fields, &sub->set_fields)); } /* Returns true if 'super' is a superset of 'sub', false otherwise. */ static bool ofputil_table_instruction_features_is_superset( const struct ofputil_table_instruction_features *super, const struct ofputil_table_instruction_features *sub) { return (bitmap_is_superset(super->next, sub->next, 255) && uint_is_superset(super->instructions, sub->instructions) && ofputil_table_action_features_is_superset(&super->write, &sub->write) && ofputil_table_action_features_is_superset(&super->apply, &sub->apply)); } /* Returns true if 'super' is a superset of 'sub', false otherwise. */ bool ofputil_table_features_are_superset( const struct ofputil_table_features *super, const struct ofputil_table_features *sub) { return (be64_is_superset(super->metadata_match, sub->metadata_match) && be64_is_superset(super->metadata_write, sub->metadata_write) && super->max_entries >= sub->max_entries && super->supports_eviction >= sub->supports_eviction && super->supports_vacancy_events >= sub->supports_vacancy_events && ofputil_table_instruction_features_is_superset(&super->nonmiss, &sub->nonmiss) && ofputil_table_instruction_features_is_superset(&super->miss, &sub->miss) && mf_bitmap_is_superset(&super->match, &sub->match) && mf_bitmap_is_superset(&super->mask, &sub->mask) && mf_bitmap_is_superset(&super->wildcard, &sub->wildcard)); } /* Table stats. */ /* OpenFlow 1.0 and 1.1 don't distinguish between a field that cannot be * matched and a field that must be wildcarded. This function returns a bitmap * that contains both kinds of fields. */ static struct mf_bitmap wild_or_nonmatchable_fields(const struct ofputil_table_features *features) { struct mf_bitmap wc = features->match; bitmap_not(wc.bm, MFF_N_IDS); bitmap_or(wc.bm, features->wildcard.bm, MFF_N_IDS); return wc; } struct ofp10_wc_map { enum ofp10_flow_wildcards wc10; enum mf_field_id mf; }; static const struct ofp10_wc_map ofp10_wc_map[] = { { OFPFW10_IN_PORT, MFF_IN_PORT }, { OFPFW10_DL_VLAN, MFF_VLAN_VID }, { OFPFW10_DL_SRC, MFF_ETH_SRC }, { OFPFW10_DL_DST, MFF_ETH_DST}, { OFPFW10_DL_TYPE, MFF_ETH_TYPE }, { OFPFW10_NW_PROTO, MFF_IP_PROTO }, { OFPFW10_TP_SRC, MFF_TCP_SRC }, { OFPFW10_TP_DST, MFF_TCP_DST }, { OFPFW10_NW_SRC_MASK, MFF_IPV4_SRC }, { OFPFW10_NW_DST_MASK, MFF_IPV4_DST }, { OFPFW10_DL_VLAN_PCP, MFF_VLAN_PCP }, { OFPFW10_NW_TOS, MFF_IP_DSCP }, }; static ovs_be32 mf_bitmap_to_of10(const struct mf_bitmap *fields) { const struct ofp10_wc_map *p; uint32_t wc10 = 0; for (p = ofp10_wc_map; p < &ofp10_wc_map[ARRAY_SIZE(ofp10_wc_map)]; p++) { if (bitmap_is_set(fields->bm, p->mf)) { wc10 |= p->wc10; } } return htonl(wc10); } static struct mf_bitmap mf_bitmap_from_of10(ovs_be32 wc10_) { struct mf_bitmap fields = MF_BITMAP_INITIALIZER; const struct ofp10_wc_map *p; uint32_t wc10 = ntohl(wc10_); for (p = ofp10_wc_map; p < &ofp10_wc_map[ARRAY_SIZE(ofp10_wc_map)]; p++) { if (wc10 & p->wc10) { bitmap_set1(fields.bm, p->mf); } } return fields; } static void ofputil_put_ofp10_table_stats(const struct ofputil_table_stats *stats, const struct ofputil_table_features *features, struct ofpbuf *buf) { struct mf_bitmap wc = wild_or_nonmatchable_fields(features); struct ofp10_table_stats *out; out = ofpbuf_put_zeros(buf, sizeof *out); out->table_id = features->table_id; ovs_strlcpy_arrays(out->name, features->name); out->wildcards = mf_bitmap_to_of10(&wc); out->max_entries = htonl(features->max_entries); out->active_count = htonl(stats->active_count); put_32aligned_be64(&out->lookup_count, htonll(stats->lookup_count)); put_32aligned_be64(&out->matched_count, htonll(stats->matched_count)); } struct ofp11_wc_map { enum ofp11_flow_match_fields wc11; enum mf_field_id mf; }; static const struct ofp11_wc_map ofp11_wc_map[] = { { OFPFMF11_IN_PORT, MFF_IN_PORT }, { OFPFMF11_DL_VLAN, MFF_VLAN_VID }, { OFPFMF11_DL_VLAN_PCP, MFF_VLAN_PCP }, { OFPFMF11_DL_TYPE, MFF_ETH_TYPE }, { OFPFMF11_NW_TOS, MFF_IP_DSCP }, { OFPFMF11_NW_PROTO, MFF_IP_PROTO }, { OFPFMF11_TP_SRC, MFF_TCP_SRC }, { OFPFMF11_TP_DST, MFF_TCP_DST }, { OFPFMF11_MPLS_LABEL, MFF_MPLS_LABEL }, { OFPFMF11_MPLS_TC, MFF_MPLS_TC }, /* I don't know what OFPFMF11_TYPE means. */ { OFPFMF11_DL_SRC, MFF_ETH_SRC }, { OFPFMF11_DL_DST, MFF_ETH_DST }, { OFPFMF11_NW_SRC, MFF_IPV4_SRC }, { OFPFMF11_NW_DST, MFF_IPV4_DST }, { OFPFMF11_METADATA, MFF_METADATA }, }; static ovs_be32 mf_bitmap_to_of11(const struct mf_bitmap *fields) { const struct ofp11_wc_map *p; uint32_t wc11 = 0; for (p = ofp11_wc_map; p < &ofp11_wc_map[ARRAY_SIZE(ofp11_wc_map)]; p++) { if (bitmap_is_set(fields->bm, p->mf)) { wc11 |= p->wc11; } } return htonl(wc11); } static struct mf_bitmap mf_bitmap_from_of11(ovs_be32 wc11_) { struct mf_bitmap fields = MF_BITMAP_INITIALIZER; const struct ofp11_wc_map *p; uint32_t wc11 = ntohl(wc11_); for (p = ofp11_wc_map; p < &ofp11_wc_map[ARRAY_SIZE(ofp11_wc_map)]; p++) { if (wc11 & p->wc11) { bitmap_set1(fields.bm, p->mf); } } return fields; } static void ofputil_put_ofp11_table_stats(const struct ofputil_table_stats *stats, const struct ofputil_table_features *features, struct ofpbuf *buf) { struct mf_bitmap wc = wild_or_nonmatchable_fields(features); struct ofp11_table_stats *out; out = ofpbuf_put_zeros(buf, sizeof *out); out->table_id = features->table_id; ovs_strlcpy_arrays(out->name, features->name); out->wildcards = mf_bitmap_to_of11(&wc); out->match = mf_bitmap_to_of11(&features->match); out->instructions = ovsinst_bitmap_to_openflow( features->nonmiss.instructions, OFP11_VERSION); out->write_actions = ofpact_bitmap_to_openflow( features->nonmiss.write.ofpacts, OFP11_VERSION); out->apply_actions = ofpact_bitmap_to_openflow( features->nonmiss.apply.ofpacts, OFP11_VERSION); out->config = htonl(features->miss_config); out->max_entries = htonl(features->max_entries); out->active_count = htonl(stats->active_count); out->lookup_count = htonll(stats->lookup_count); out->matched_count = htonll(stats->matched_count); } static void ofputil_put_ofp12_table_stats(const struct ofputil_table_stats *stats, const struct ofputil_table_features *features, struct ofpbuf *buf) { struct ofp12_table_stats *out; out = ofpbuf_put_zeros(buf, sizeof *out); out->table_id = features->table_id; ovs_strlcpy_arrays(out->name, features->name); out->match = oxm_bitmap_from_mf_bitmap(&features->match, OFP12_VERSION); out->wildcards = oxm_bitmap_from_mf_bitmap(&features->wildcard, OFP12_VERSION); out->write_actions = ofpact_bitmap_to_openflow( features->nonmiss.write.ofpacts, OFP12_VERSION); out->apply_actions = ofpact_bitmap_to_openflow( features->nonmiss.apply.ofpacts, OFP12_VERSION); out->write_setfields = oxm_bitmap_from_mf_bitmap( &features->nonmiss.write.set_fields, OFP12_VERSION); out->apply_setfields = oxm_bitmap_from_mf_bitmap( &features->nonmiss.apply.set_fields, OFP12_VERSION); out->metadata_match = features->metadata_match; out->metadata_write = features->metadata_write; out->instructions = ovsinst_bitmap_to_openflow( features->nonmiss.instructions, OFP12_VERSION); out->config = ofputil_encode_table_config(features->miss_config, OFPUTIL_TABLE_EVICTION_DEFAULT, OFPUTIL_TABLE_VACANCY_DEFAULT, OFP12_VERSION); out->max_entries = htonl(features->max_entries); out->active_count = htonl(stats->active_count); out->lookup_count = htonll(stats->lookup_count); out->matched_count = htonll(stats->matched_count); } static void ofputil_put_ofp13_table_stats(const struct ofputil_table_stats *stats, struct ofpbuf *buf) { struct ofp13_table_stats *out; out = ofpbuf_put_zeros(buf, sizeof *out); out->table_id = stats->table_id; out->active_count = htonl(stats->active_count); out->lookup_count = htonll(stats->lookup_count); out->matched_count = htonll(stats->matched_count); } struct ofpbuf * ofputil_encode_table_stats_reply(const struct ofp_header *request) { return ofpraw_alloc_stats_reply(request, 0); } void ofputil_append_table_stats_reply(struct ofpbuf *reply, const struct ofputil_table_stats *stats, const struct ofputil_table_features *features) { struct ofp_header *oh = reply->header; ovs_assert(stats->table_id == features->table_id); switch ((enum ofp_version) oh->version) { case OFP10_VERSION: ofputil_put_ofp10_table_stats(stats, features, reply); break; case OFP11_VERSION: ofputil_put_ofp11_table_stats(stats, features, reply); break; case OFP12_VERSION: ofputil_put_ofp12_table_stats(stats, features, reply); break; case OFP13_VERSION: case OFP14_VERSION: case OFP15_VERSION: ofputil_put_ofp13_table_stats(stats, reply); break; default: OVS_NOT_REACHED(); } } static int ofputil_decode_ofp10_table_stats(struct ofpbuf *msg, struct ofputil_table_stats *stats, struct ofputil_table_features *features) { struct ofp10_table_stats *ots; ots = ofpbuf_try_pull(msg, sizeof *ots); if (!ots) { return OFPERR_OFPBRC_BAD_LEN; } features->table_id = ots->table_id; ovs_strlcpy_arrays(features->name, ots->name); features->max_entries = ntohl(ots->max_entries); features->match = features->wildcard = mf_bitmap_from_of10(ots->wildcards); stats->table_id = ots->table_id; stats->active_count = ntohl(ots->active_count); stats->lookup_count = ntohll(get_32aligned_be64(&ots->lookup_count)); stats->matched_count = ntohll(get_32aligned_be64(&ots->matched_count)); return 0; } static int ofputil_decode_ofp11_table_stats(struct ofpbuf *msg, struct ofputil_table_stats *stats, struct ofputil_table_features *features) { struct ofp11_table_stats *ots; ots = ofpbuf_try_pull(msg, sizeof *ots); if (!ots) { return OFPERR_OFPBRC_BAD_LEN; } features->table_id = ots->table_id; ovs_strlcpy_arrays(features->name, ots->name); features->max_entries = ntohl(ots->max_entries); features->nonmiss.instructions = ovsinst_bitmap_from_openflow( ots->instructions, OFP11_VERSION); features->nonmiss.write.ofpacts = ofpact_bitmap_from_openflow( ots->write_actions, OFP11_VERSION); features->nonmiss.apply.ofpacts = ofpact_bitmap_from_openflow( ots->write_actions, OFP11_VERSION); features->miss = features->nonmiss; features->miss_config = ofputil_decode_table_miss(ots->config, OFP11_VERSION); features->match = mf_bitmap_from_of11(ots->match); features->wildcard = mf_bitmap_from_of11(ots->wildcards); bitmap_or(features->match.bm, features->wildcard.bm, MFF_N_IDS); stats->table_id = ots->table_id; stats->active_count = ntohl(ots->active_count); stats->lookup_count = ntohll(ots->lookup_count); stats->matched_count = ntohll(ots->matched_count); return 0; } static int ofputil_decode_ofp12_table_stats(struct ofpbuf *msg, struct ofputil_table_stats *stats, struct ofputil_table_features *features) { struct ofp12_table_stats *ots; ots = ofpbuf_try_pull(msg, sizeof *ots); if (!ots) { return OFPERR_OFPBRC_BAD_LEN; } features->table_id = ots->table_id; ovs_strlcpy_arrays(features->name, ots->name); features->metadata_match = ots->metadata_match; features->metadata_write = ots->metadata_write; features->miss_config = ofputil_decode_table_miss(ots->config, OFP12_VERSION); features->max_entries = ntohl(ots->max_entries); features->nonmiss.instructions = ovsinst_bitmap_from_openflow( ots->instructions, OFP12_VERSION); features->nonmiss.write.ofpacts = ofpact_bitmap_from_openflow( ots->write_actions, OFP12_VERSION); features->nonmiss.apply.ofpacts = ofpact_bitmap_from_openflow( ots->apply_actions, OFP12_VERSION); features->nonmiss.write.set_fields = oxm_bitmap_to_mf_bitmap( ots->write_setfields, OFP12_VERSION); features->nonmiss.apply.set_fields = oxm_bitmap_to_mf_bitmap( ots->apply_setfields, OFP12_VERSION); features->miss = features->nonmiss; features->match = oxm_bitmap_to_mf_bitmap(ots->match, OFP12_VERSION); features->wildcard = oxm_bitmap_to_mf_bitmap(ots->wildcards, OFP12_VERSION); bitmap_or(features->match.bm, features->wildcard.bm, MFF_N_IDS); stats->table_id = ots->table_id; stats->active_count = ntohl(ots->active_count); stats->lookup_count = ntohll(ots->lookup_count); stats->matched_count = ntohll(ots->matched_count); return 0; } static int ofputil_decode_ofp13_table_stats(struct ofpbuf *msg, struct ofputil_table_stats *stats, struct ofputil_table_features *features) { struct ofp13_table_stats *ots; ots = ofpbuf_try_pull(msg, sizeof *ots); if (!ots) { return OFPERR_OFPBRC_BAD_LEN; } features->table_id = ots->table_id; stats->table_id = ots->table_id; stats->active_count = ntohl(ots->active_count); stats->lookup_count = ntohll(ots->lookup_count); stats->matched_count = ntohll(ots->matched_count); return 0; } int ofputil_decode_table_stats_reply(struct ofpbuf *msg, struct ofputil_table_stats *stats, struct ofputil_table_features *features) { const struct ofp_header *oh; if (!msg->header) { ofpraw_pull_assert(msg); } oh = msg->header; if (!msg->size) { return EOF; } memset(stats, 0, sizeof *stats); memset(features, 0, sizeof *features); features->supports_eviction = -1; features->supports_vacancy_events = -1; switch ((enum ofp_version) oh->version) { case OFP10_VERSION: return ofputil_decode_ofp10_table_stats(msg, stats, features); case OFP11_VERSION: return ofputil_decode_ofp11_table_stats(msg, stats, features); case OFP12_VERSION: return ofputil_decode_ofp12_table_stats(msg, stats, features); case OFP13_VERSION: case OFP14_VERSION: case OFP15_VERSION: return ofputil_decode_ofp13_table_stats(msg, stats, features); default: OVS_NOT_REACHED(); } } /* 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 OFP_ASYNC_CONFIG_REASON_BUFSIZE. */ const char * ofp_table_reason_to_string(enum ofp14_table_reason reason, char *reasonbuf, size_t bufsize) { switch (reason) { case OFPTR_VACANCY_DOWN: return "vacancy_down"; case OFPTR_VACANCY_UP: return "vacancy_up"; default: snprintf(reasonbuf, bufsize, "%d", (int) reason); return reasonbuf; } } static void ofputil_put_ofp14_table_desc(const struct ofputil_table_desc *td, struct ofpbuf *b, enum ofp_version version) { struct ofp14_table_desc *otd; struct ofp14_table_mod_prop_vacancy *otv; size_t start_otd; start_otd = b->size; ofpbuf_put_zeros(b, sizeof *otd); ofpprop_put_u32(b, OFPTMPT14_EVICTION, td->eviction_flags); otv = ofpbuf_put_zeros(b, sizeof *otv); otv->type = htons(OFPTMPT14_VACANCY); otv->length = htons(sizeof *otv); otv->vacancy_down = td->table_vacancy.vacancy_down; otv->vacancy_up = td->table_vacancy.vacancy_up; otv->vacancy = td->table_vacancy.vacancy; otd = ofpbuf_at_assert(b, start_otd, sizeof *otd); otd->length = htons(b->size - start_otd); otd->table_id = td->table_id; otd->config = ofputil_encode_table_config(OFPUTIL_TABLE_MISS_DEFAULT, td->eviction, td->vacancy, version); } /* Converts the abstract form of a "table status" message in '*ts' into an * OpenFlow message suitable for 'protocol', and returns that encoded form in * a buffer owned by the caller. */ struct ofpbuf * ofputil_encode_table_status(const struct ofputil_table_status *ts, enum ofputil_protocol protocol) { enum ofp_version version; struct ofpbuf *b; version = ofputil_protocol_to_ofp_version(protocol); if (version >= OFP14_VERSION) { enum ofpraw raw; struct ofp14_table_status *ots; raw = OFPRAW_OFPT14_TABLE_STATUS; b = ofpraw_alloc_xid(raw, version, htonl(0), 0); ots = ofpbuf_put_zeros(b, sizeof *ots); ots->reason = ts->reason; ofputil_put_ofp14_table_desc(&ts->desc, b, version); ofpmsg_update_length(b); return b; } else { return NULL; } } /* Decodes the OpenFlow "table status" message in '*ots' into an abstract form * in '*ts'. Returns 0 if successful, otherwise an OFPERR_* value. */ enum ofperr ofputil_decode_table_status(const struct ofp_header *oh, struct ofputil_table_status *ts) { const struct ofp14_table_status *ots; struct ofpbuf b; enum ofperr error; enum ofpraw raw; ofpbuf_use_const(&b, oh, ntohs(oh->length)); raw = ofpraw_pull_assert(&b); ots = ofpbuf_pull(&b, sizeof *ots); if (raw == OFPRAW_OFPT14_TABLE_STATUS) { if (ots->reason != OFPTR_VACANCY_DOWN && ots->reason != OFPTR_VACANCY_UP) { return OFPERR_OFPBPC_BAD_VALUE; } ts->reason = ots->reason; error = ofputil_decode_table_desc(&b, &ts->desc, oh->version); return error; } else { return OFPERR_OFPBRC_BAD_VERSION; } return 0; } void ofputil_format_table_status(struct ds *string, const struct ofputil_table_status *ts, const struct ofputil_table_map *table_map) { if (ts->reason == OFPTR_VACANCY_DOWN) { ds_put_format(string, " reason=VACANCY_DOWN"); } else if (ts->reason == OFPTR_VACANCY_UP) { ds_put_format(string, " reason=VACANCY_UP"); } ds_put_format(string, "\ntable_desc:-"); ofputil_table_desc_format(string, &ts->desc, table_map); }