2
0
mirror of https://github.com/openvswitch/ovs synced 2025-10-23 14:57:06 +00:00
Files
openvswitch/lib/ofp-table.c
Ben Pfaff dfc77282c5 ofp-print: Move much of the printing code into message-specific files.
Until now, the ofp-print code has had a lot of logic specific to
individual messages.  This code is better put with the other code specific
to those messages, so this commit starts to migrate it.

There is more work of a similar type to do, but this is a reasonable start.

Signed-off-by: Ben Pfaff <blp@ovn.org>
Acked-by: Justin Pettit <jpettit@ovn.org>
2018-03-14 11:41:22 -07:00

2000 lines
68 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <config.h>
#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***";
}
}
/* 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 'loose' is true, 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. If 'loose' is false, 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, bool loose)
{
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;
}
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);
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;
}
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;
}
}
/* 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:
case OFP16_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);
}
void
ofputil_append_table_features_reply(const struct ofputil_table_features *tf,
struct ovs_list *replies)
{
struct ofpbuf *reply = ofpbuf_from_list(ovs_list_back(replies));
enum ofp_version version = ofpmp_version(replies);
size_t start_ofs = reply->size;
struct ofp13_table_features *otf;
otf = ofpbuf_put_zeros(reply, sizeof *otf);
otf->table_id = tf->table_id;
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);
put_table_instruction_features(reply, &tf->nonmiss, 0, version);
put_table_instruction_features(reply, &tf->miss, 1, version);
put_fields_property(reply, &tf->match, &tf->mask,
OFPTFPT13_MATCH, version);
put_fields_property(reply, &tf->wildcard, NULL,
OFPTFPT13_WILDCARDS, version);
otf = ofpbuf_at_assert(reply, start_ofs, sizeof *otf);
otf->length = htons(reply->size - start_ofs);
ofpmp_postappend(replies, 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:
case OFP16_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:
case OFP16_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.
*
* 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 *table_id,
const char *setting,
const struct ofputil_table_map *table_map,
uint32_t *usable_versions)
{
*usable_versions = 0;
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 {
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;
}
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)) {
int i;
ds_put_cstr(s, " supported on Set-Field:");
BITMAP_FOR_EACH_1 (i, MFF_N_IDS, taf->set_fields.bm) {
ds_put_format(s, " %s", mf_from_id(i)->name);
}
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 bool
table_instruction_features_equal(
const struct ofputil_table_instruction_features *a,
const struct ofputil_table_instruction_features *b)
{
return (bitmap_equal(a->next, b->next, 255)
&& 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, &b->nonmiss)
&& table_instruction_features_equal(&a->miss, &b->miss)
&& 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,
const struct ofputil_table_map *table_map)
{
int i;
ds_put_format(s, " table ");
ofputil_format_table(features->table_id, table_map, s);
if (features->name[0]) {
ds_put_format(s, " (\"%s\")", features->name);
}
ds_put_char(s, ':');
bool same_stats = prev_stats && table_stats_equal(stats, prev_stats);
bool same_features = prev_features && table_features_equal(features,
prev_features);
if ((!stats || 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, prev_nonmiss)
&& table_instruction_features_equal(&features->miss, prev_miss)) {
if (!table_instruction_features_empty(&features->nonmiss)) {
ds_put_cstr(s, " (same instructions)\n");
}
} else if (!table_instruction_features_equal(&features->nonmiss,
&features->miss)) {
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");
BITMAP_FOR_EACH_1 (i, MFF_N_IDS, features->match.bm) {
const struct mf_field *f = mf_from_id(i);
bool mask = bitmap_is_set(features->mask.bm, i);
bool wildcard = bitmap_is_set(features->wildcard.bm, i);
ds_put_format(s, " %s: %s\n",
f->name,
(mask ? "arbitrary mask"
: wildcard ? "exact match or wildcard"
: "must exact match"));
}
}
}
}
/* 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:
case OFP16_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:
case OFP16_VERSION:
return ofputil_decode_ofp13_table_stats(msg, stats, features);
default:
OVS_NOT_REACHED();
}
}
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;
}