summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--include/openvswitch/meta-flow.h3
-rw-r--r--include/openvswitch/ofp-errors.h3
-rw-r--r--include/openvswitch/ofp-msgs.h2
-rw-r--r--include/openvswitch/ofp-table.h17
-rw-r--r--include/openvswitch/vconn.h2
-rw-r--r--lib/bitmap.h15
-rw-r--r--lib/meta-flow.c9
-rw-r--r--lib/ofp-print.c5
-rw-r--r--lib/ofp-table.c118
-rw-r--r--lib/util.h46
-rw-r--r--lib/vconn.c40
-rw-r--r--ofproto/ofproto-dpif.c1
-rw-r--r--ofproto/ofproto-provider.h19
-rw-r--r--ofproto/ofproto.c284
-rw-r--r--tests/ofproto.at90
-rw-r--r--utilities/ovs-ofctl.8.in12
-rw-r--r--utilities/ovs-ofctl.c128
18 files changed, 719 insertions, 79 deletions
diff --git a/NEWS b/NEWS
index 2de844f30..18c371d25 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ Post-v2.10.0
---------------------
- Linux datapath:
* Support for the kernel versions 4.16.x and 4.17.x.
+ - OpenFlow:
+ * OFPMP_TABLE_FEATURES_REQUEST can now modify table features.
+ - ovs-ofctl:
+ * "mod-table" command can now change OpenFlow table names.
- The environment variable OVS_SYSLOG_METHOD, if set, is now used
as the default syslog method.
- The environment variable OVS_CTL_TIMEOUT, if set, is now used
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index ffd89452c..4a5a126ba 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -1913,6 +1913,9 @@ struct mf_bitmap {
};
#define MF_BITMAP_INITIALIZER { { [0] = 0 } }
+bool mf_bitmap_is_superset(const struct mf_bitmap *super,
+ const struct mf_bitmap *sub);
+
/* Use this macro as CASE_MFF_REGS: in a switch statement to choose all of the
* MFF_REGn cases. */
#if FLOW_N_REGS ==16
diff --git a/include/openvswitch/ofp-errors.h b/include/openvswitch/ofp-errors.h
index 6e8e55ab4..2f00851f1 100644
--- a/include/openvswitch/ofp-errors.h
+++ b/include/openvswitch/ofp-errors.h
@@ -674,6 +674,9 @@ enum ofperr {
/* OF1.5+(13,10). Can't handle this many flow tables. */
OFPERR_OFPTFFC_TOO_MANY,
+ /* NX1.3+(44). Table specified multiple times. */
+ OFPERR_NXTFFC_DUP_TABLE,
+
/* ## ------------------ ## */
/* ## OFPET_BAD_PROPERTY ## */
diff --git a/include/openvswitch/ofp-msgs.h b/include/openvswitch/ofp-msgs.h
index a32d04bbb..ecdb547de 100644
--- a/include/openvswitch/ofp-msgs.h
+++ b/include/openvswitch/ofp-msgs.h
@@ -426,7 +426,7 @@ enum ofpraw {
/* OFPST 1.3+ (11): struct ofp13_meter_features. */
OFPRAW_OFPST13_METER_FEATURES_REPLY,
- /* OFPST 1.3+ (12): void. */
+ /* OFPST 1.3+ (12): uint8_t[8][]. */
OFPRAW_OFPST13_TABLE_FEATURES_REQUEST,
/* OFPST 1.3+ (12): struct ofp13_table_features, uint8_t[8][]. */
diff --git a/include/openvswitch/ofp-table.h b/include/openvswitch/ofp-table.h
index 370ec85ae..8e0a1cbe2 100644
--- a/include/openvswitch/ofp-table.h
+++ b/include/openvswitch/ofp-table.h
@@ -155,7 +155,7 @@ struct ofpbuf *ofputil_encode_table_mod(const struct ofputil_table_mod *,
enum ofputil_protocol);
void ofputil_table_mod_format(struct ds *, const struct ofputil_table_mod *,
const struct ofputil_table_map *);
-char *parse_ofp_table_mod(struct ofputil_table_mod *,
+char *parse_ofp_table_mod(struct ofputil_table_mod *, const char **namep,
const char *table_id, const char *flow_miss_handling,
const struct ofputil_table_map *,
uint32_t *usable_versions)
@@ -271,15 +271,18 @@ struct ofputil_table_features {
struct mf_bitmap wildcard; /* Subset of 'match' that may be wildcarded. */
};
-int ofputil_decode_table_features(struct ofpbuf *,
- struct ofputil_table_features *, bool loose);
+int ofputil_decode_table_features(
+ struct ofpbuf *, struct ofputil_table_features *,
+ struct ofpbuf *raw_properties);
struct ofpbuf *ofputil_encode_table_features_request(enum ofp_version);
struct ofpbuf *ofputil_encode_table_desc_request(enum ofp_version);
-void ofputil_append_table_features_reply(
- const struct ofputil_table_features *tf, struct ovs_list *replies);
+void ofputil_append_table_features(
+ const struct ofputil_table_features *tf,
+ const struct ofpbuf *raw_properties,
+ struct ovs_list *msgs);
void ofputil_table_features_format(
struct ds *, const struct ofputil_table_features *features,
@@ -290,6 +293,10 @@ void ofputil_table_features_format(
void ofputil_table_features_format_finish(struct ds *,
int first_ditto, int last_ditto);
+bool ofputil_table_features_are_superset(
+ const struct ofputil_table_features *super,
+ const struct ofputil_table_features *sub);
+
/* Abstract table stats.
*
* This corresponds to the OpenFlow 1.3 table statistics structure, which only
diff --git a/include/openvswitch/vconn.h b/include/openvswitch/vconn.h
index dfb7f7522..5f69c732b 100644
--- a/include/openvswitch/vconn.h
+++ b/include/openvswitch/vconn.h
@@ -58,6 +58,8 @@ int vconn_transact(struct vconn *, struct ofpbuf *, struct ofpbuf **);
int vconn_transact_noreply(struct vconn *, struct ofpbuf *, struct ofpbuf **);
int vconn_transact_multiple_noreply(struct vconn *, struct ovs_list *requests,
struct ofpbuf **replyp);
+int vconn_transact_multipart(struct vconn *, struct ovs_list *request,
+ struct ovs_list *replies);
int vconn_dump_flows(struct vconn *, const struct ofputil_flow_stats_request *,
enum ofputil_protocol,
diff --git a/lib/bitmap.h b/lib/bitmap.h
index 39b759719..eb469339e 100644
--- a/lib/bitmap.h
+++ b/lib/bitmap.h
@@ -251,6 +251,21 @@ found:
return end;
}
+/* Returns true if the 1-bits in 'super' are a superset of the 1-bits in 'sub',
+ * false otherwise. 'super' and 'sub' both have 'n_bits' bits. */
+static inline bool
+bitmap_is_superset(const unsigned long int *super,
+ const unsigned long int *sub, size_t n_bits)
+{
+ size_t n_longs = bitmap_n_longs(n_bits);
+ for (size_t i = 0; i < n_longs; i++) {
+ if (!uint_is_superset(super[i], sub[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
/* Returns true if all of the 'n' bits in 'bitmap' are 0,
* false if at least one bit is a 1.*/
static inline bool
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index b6d9e92b6..bc48d6304 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -3528,3 +3528,12 @@ mf_vl_mff_mf_from_nxm_header(uint32_t header,
mf_vl_mff_set_tlv_bitmap(*field, tlv_bitmap);
return 0;
}
+
+/* Returns true if the 1-bits in 'super' are a superset of the 1-bits in 'sub',
+ * false otherwise. */
+bool
+mf_bitmap_is_superset(const struct mf_bitmap *super,
+ const struct mf_bitmap *sub)
+{
+ return bitmap_is_superset(super->bm, sub->bm, MFF_N_IDS);
+}
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 789b40a3c..9c8851590 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -234,9 +234,8 @@ ofp_print_table_features_reply(struct ds *s, const struct ofp_header *oh)
int first_ditto = -1, last_ditto = -1;
for (int i = 0; ; i++) {
struct ofputil_table_features tf;
- int retval;
-
- retval = ofputil_decode_table_features(&b, &tf, true);
+ struct ofpbuf raw_properties;
+ int retval = ofputil_decode_table_features(&b, &tf, &raw_properties);
if (retval) {
ofputil_table_features_format_finish(s, first_ditto, last_ditto);
return retval != EOF ? retval : 0;
diff --git a/lib/ofp-table.c b/lib/ofp-table.c
index 38c005213..88a1533fc 100644
--- a/lib/ofp-table.c
+++ b/lib/ofp-table.c
@@ -350,11 +350,16 @@ parse_oxms(struct ofpbuf *payload, bool loose,
/* 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.
+ * 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
@@ -365,8 +370,11 @@ parse_oxms(struct ofpbuf *payload, bool loose,
* a positive "enum ofperr" value. */
int
ofputil_decode_table_features(struct ofpbuf *msg,
- struct ofputil_table_features *tf, bool loose)
+ struct ofputil_table_features *tf,
+ struct ofpbuf *raw_properties)
{
+ bool loose = raw_properties != NULL;
+
memset(tf, 0, sizeof *tf);
if (!msg->header) {
@@ -420,6 +428,9 @@ ofputil_decode_table_features(struct ofpbuf *msg,
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;
@@ -642,16 +653,21 @@ put_table_instruction_features(
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_reply(const struct ofputil_table_features *tf,
- struct ovs_list *replies)
+ofputil_append_table_features(const struct ofputil_table_features *tf,
+ const struct ofpbuf *raw_properties,
+ struct ovs_list *msgs)
{
- struct ofpbuf *reply = ofpbuf_from_list(ovs_list_back(replies));
- enum ofp_version version = ofpmp_version(replies);
- size_t start_ofs = reply->size;
+ 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(reply, sizeof *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);
@@ -667,17 +683,21 @@ ofputil_append_table_features_reply(const struct ofputil_table_features *tf,
}
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);
+ 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(reply, &tf->match, &tf->mask,
- OFPTFPT13_MATCH, version);
- put_fields_property(reply, &tf->wildcard, NULL,
- OFPTFPT13_WILDCARDS, 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(reply, start_ofs, sizeof *otf);
- otf->length = htons(reply->size - start_ofs);
- ofpmp_postappend(replies, start_ofs);
+ otf = ofpbuf_at_assert(msg, start_ofs, sizeof *otf);
+ otf->length = htons(msg->size - start_ofs);
+ ofpmp_postappend(msgs, start_ofs);
}
static enum ofperr
@@ -1236,7 +1256,8 @@ exit:
/* 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.
+ * 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'.
@@ -1244,12 +1265,13 @@ exit:
* 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,
+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,
@@ -1293,6 +1315,10 @@ parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id,
} 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);
}
@@ -1644,6 +1670,50 @@ ofputil_table_features_format_finish(struct ds *s,
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. */
diff --git a/lib/util.h b/lib/util.h
index 6e29751bf..53354f1c6 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -436,6 +436,38 @@ static inline ovs_be32 be32_prefix_mask(int plen)
return htonl((uint64_t)UINT32_MAX << (32 - plen));
}
+/* Returns true if the 1-bits in 'super' are a superset of the 1-bits in 'sub',
+ * false otherwise. */
+static inline bool
+uint_is_superset(uintmax_t super, uintmax_t sub)
+{
+ return (super & sub) == sub;
+}
+
+/* Returns true if the 1-bits in 'super' are a superset of the 1-bits in 'sub',
+ * false otherwise. */
+static inline bool
+be16_is_superset(ovs_be16 super, ovs_be16 sub)
+{
+ return (super & sub) == sub;
+}
+
+/* Returns true if the 1-bits in 'super' are a superset of the 1-bits in 'sub',
+ * false otherwise. */
+static inline bool
+be32_is_superset(ovs_be32 super, ovs_be32 sub)
+{
+ return (super & sub) == sub;
+}
+
+/* Returns true if the 1-bits in 'super' are a superset of the 1-bits in 'sub',
+ * false otherwise. */
+static inline bool
+be64_is_superset(ovs_be64 super, ovs_be64 sub)
+{
+ return (super & sub) == sub;
+}
+
bool is_all_zeros(const void *, size_t);
bool is_all_ones(const void *, size_t);
bool is_all_byte(const void *, size_t, uint8_t byte);
@@ -509,6 +541,20 @@ ovs_u128_and(const ovs_u128 a, const ovs_u128 b)
return dst;
}
+static inline bool
+ovs_be128_is_superset(ovs_be128 super, ovs_be128 sub)
+{
+ return (be64_is_superset(super.be64.hi, sub.be64.hi) &&
+ be64_is_superset(super.be64.lo, sub.be64.lo));
+}
+
+static inline bool
+ovs_u128_is_superset(ovs_u128 super, ovs_u128 sub)
+{
+ return (uint_is_superset(super.u64.hi, sub.u64.hi) &&
+ uint_is_superset(super.u64.lo, sub.u64.lo));
+}
+
void xsleep(unsigned int seconds);
void xnanosleep(uint64_t nanoseconds);
diff --git a/lib/vconn.c b/lib/vconn.c
index 403461662..7415e6291 100644
--- a/lib/vconn.c
+++ b/lib/vconn.c
@@ -946,6 +946,46 @@ vconn_transact_multiple_noreply(struct vconn *vconn, struct ovs_list *requests,
return 0;
}
+/* Sends 'requests' (which should be a multipart request) on 'vconn' and waits
+ * for the replies, which are put into 'replies'. Returns 0 if successful,
+ * otherwise an errno value. */
+int
+vconn_transact_multipart(struct vconn *vconn,
+ struct ovs_list *requests,
+ struct ovs_list *replies)
+{
+ struct ofpbuf *rq = ofpbuf_from_list(ovs_list_front(requests));
+ ovs_be32 send_xid = ((struct ofp_header *) rq->data)->xid;
+
+ ovs_list_init(replies);
+
+ /* Send all the requests. */
+ struct ofpbuf *b, *next;
+ LIST_FOR_EACH_SAFE (b, next, list_node, requests) {
+ ovs_list_remove(&b->list_node);
+ int error = vconn_send_block(vconn, b);
+ if (error) {
+ ofpbuf_delete(b);
+ }
+ }
+
+ /* Receive all the replies. */
+ bool more;
+ do {
+ struct ofpbuf *reply;
+ int error = vconn_recv_xid__(vconn, send_xid, &reply, NULL);
+ if (error) {
+ ofpbuf_list_delete(replies);
+ return error;
+ }
+
+ ovs_list_push_back(replies, &reply->list_node);
+ more = ofpmsg_is_stat_reply(reply->data) && ofpmp_more(reply->data);
+ } while (more);
+
+ return 0;
+}
+
static int
recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
struct ofpbuf **replyp,
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 2dc5778e1..14fe6fc8a 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -6029,6 +6029,7 @@ const struct ofproto_class ofproto_dpif_class = {
type_get_memory_usage,
flush,
query_tables,
+ NULL, /* modify_tables */
set_tables_version,
port_alloc,
port_construct,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 4fd8cb14e..074edfc11 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -221,6 +221,7 @@ struct oftable {
enum oftable_flags flags;
struct classifier cls; /* Contains "struct rule"s. */
char *name; /* Table name exposed via OpenFlow, or NULL. */
+ int name_level; /* 0=name unset, 1=via OF, 2=via OVSDB. */
/* Maximum number of flows or UINT_MAX if there is no limit besides any
* limit imposed by resource limitations. */
@@ -934,6 +935,24 @@ struct ofproto_class {
struct ofputil_table_features *features,
struct ofputil_table_stats *stats);
+ /* Helper for the OFPT_TABLE_FEATURES request.
+ *
+ * A controller is requesting that the table features be updated from 'old'
+ * to 'new', where 'old' is the features currently in use as previously
+ * initialized by 'query_tables'.
+ *
+ * If this function is nonnull, then it should either update the table
+ * features or return an OpenFlow error. The update should be
+ * all-or-nothing.
+ *
+ * If this function is null, then only updates that eliminate table
+ * features will be allowed. Such updates have no actual effect. This
+ * implementation is acceptable because OpenFlow says that a table's
+ * features may be a superset of those requested. */
+ enum ofperr (*modify_tables)(struct ofproto *ofproto,
+ const struct ofputil_table_features *old,
+ const struct ofputil_table_features *new);
+
/* Sets the current tables version the provider should use for classifier
* lookups. This must be called with a new version number after each set
* of flow table changes has been completed, so that datapath revalidation
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index bdc3ac684..d60decad8 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -85,7 +85,9 @@ const enum mf_field_id default_prefix_fields[2] =
static void oftable_init(struct oftable *);
static void oftable_destroy(struct oftable *);
-static void oftable_set_name(struct oftable *, const char *name);
+static void oftable_set_name(struct oftable *, const char *name, int level);
+static bool oftable_may_set_name(const struct oftable *,
+ const char *name, int level);
static enum ofperr evict_rules_from_table(struct oftable *)
OVS_REQUIRES(ofproto_mutex);
@@ -1473,7 +1475,7 @@ ofproto_configure_table(struct ofproto *ofproto, int table_id,
ovs_assert(table_id >= 0 && table_id < ofproto->n_tables);
table = &ofproto->tables[table_id];
- oftable_set_name(table, s->name);
+ oftable_set_name(table, s->name, 2);
if (table->flags & OFTABLE_READONLY) {
return;
@@ -3249,6 +3251,8 @@ query_tables(struct ofproto *ofproto,
atomic_read_relaxed(&ofproto->tables[i].miss_config, &f->miss_config);
f->max_entries = 1000000;
+ f->any_properties = true;
+
bool more_tables = false;
for (int j = i + 1; j < ofproto->n_tables; j++) {
if (!(ofproto->tables[j].flags & OFTABLE_HIDDEN)) {
@@ -3784,33 +3788,237 @@ handle_table_stats_request(struct ofconn *ofconn,
return 0;
}
-static enum ofperr
-handle_table_features_request(struct ofconn *ofconn,
- const struct ofp_header *request)
+/* OpenFlow 1.5.1 section 7.3.5.18.1 "Table Features request and reply"
+ * says:
+ *
+ * If a table feature included in the request has an empty list of
+ * properties, the list of properties for that flow table is unchanged and
+ * only the other features of that flow table are updated.
+ *
+ * This function copies the "list of properties" from '*src' to '*dst'. */
+static void
+copy_properties(struct ofputil_table_features *dst,
+ const struct ofputil_table_features *src)
+{
+ dst->any_properties = src->any_properties;
+ if (src->any_properties) {
+ dst->nonmiss = src->nonmiss;
+ dst->miss = src->miss;
+ dst->match = src->match;
+ dst->mask = src->mask;
+ dst->wildcard = src->wildcard;
+ }
+}
+
+/* Attempts to change the table features of the ofproto backing 'ofconn' to
+ * those specified in the table features request in 'msgs', given that the
+ * features are currently those in 'old'.
+ *
+ * Returns 0 if successful, an OpenFlow error if the caller should send an
+ * error message for the request as a whole, or -1 if the function already sent
+ * an error message for some message in 'msgs'. */
+static int
+handle_table_features_change(struct ofconn *ofconn,
+ const struct ovs_list *msgs,
+ const struct ofputil_table_features old[])
{
struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
- struct ofpbuf msg = ofpbuf_const_initializer(request,
- ntohs(request->length));
- ofpraw_pull_assert(&msg);
- if (msg.size || ofpmp_more(request)) {
+
+ enum ofp15_table_features_command command = OFPTFC15_REPLACE;
+ struct ofputil_table_features new[255];
+
+ unsigned long int seen[BITMAP_N_LONGS(255)];
+ memset(seen, 0, sizeof seen);
+
+ struct ofpbuf *msg;
+ int n = 0;
+ LIST_FOR_EACH (msg, list_node, msgs) {
+ for (;;) {
+ struct ofputil_table_features tf;
+ int retval = ofputil_decode_table_features(msg, &tf, NULL);
+ if (retval == EOF) {
+ break;
+ } else if (retval) {
+ ofconn_send_error(ofconn, msg->header, retval);
+ return -1;
+ }
+
+ /* Get command from first request. */
+ if (!n) {
+ command = tf.command;
+ }
+ n++;
+
+ /* Avoid duplicate tables. */
+ if (bitmap_is_set(seen, tf.table_id)) {
+ VLOG_INFO_RL(&rl, "duplicate table %"PRIu8, tf.table_id);
+ ofconn_send_error(ofconn, msg->header,
+ OFPERR_NXTFFC_DUP_TABLE);
+ return -1;
+ }
+ bitmap_set1(seen, tf.table_id);
+
+ /* Save table. */
+ new[tf.table_id] = tf;
+ }
+ }
+
+ if (!n) {
+ return 0;
+ }
+
+ for (size_t i = 0; i < ofproto->n_tables; i++) {
+ if (ofproto->tables[i].flags & OFTABLE_HIDDEN) {
+ if (bitmap_is_set(seen, i)) {
+ VLOG_INFO_RL(&rl, "can't modify hidden table %"PRIuSIZE, i);
+ return OFPERR_OFPTFFC_EPERM;
+ }
+
+ new[i] = old[i];
+ bitmap_set1(seen, i);
+ }
+ }
+
+ switch (command) {
+ case OFPTFC15_REPLACE:
+ break;
+
+ case OFPTFC15_MODIFY:
+ for (size_t i = 0; i < ofproto->n_tables; i++) {
+ if (!bitmap_is_set(seen, i)) {
+ new[i] = old[i];
+ bitmap_set1(seen, i);
+ } else if (!new[i].any_properties) {
+ copy_properties(&new[i], &old[i]);
+ }
+ }
+ break;
+
+ case OFPTFC15_ENABLE:
+ case OFPTFC15_DISABLE:
+ /* It really isn't clear what these commands are supposed to do in an
+ * Open vSwitch context. OVS doesn't have a concept of tables that
+ * exist but are not in the pipeline, and OVS table ids are always
+ * sequential from 0. */
return OFPERR_OFPTFFC_EPERM;
}
+ /* Make sure that the new number of tables is the same as the old number,
+ * because we don't support changing the number of tables or disabling
+ * tables. */
+ int n_tables = bitmap_scan(seen, 0, 0, 255);
+ bool skipped_tables = bitmap_scan(seen, 1, n_tables, 255) != 255;
+ if (n_tables != ofproto->n_tables || skipped_tables) {
+ if (skipped_tables) {
+ VLOG_INFO_RL(&rl, "can't disable table %d", n_tables);
+ } else {
+ VLOG_INFO_RL(&rl, "can't change number of tables from %d to %d",
+ ofproto->n_tables, n_tables);
+ }
+ return (n_tables > ofproto->n_tables
+ ? OFPERR_OFPTFFC_TOO_MANY
+ : OFPERR_OFPTFFC_EPERM);
+ }
+
+ /* OpenFlow 1.5.1 section 7.3.5.18.1 "Table Features request and reply"
+ * says:
+ *
+ * "All fields in ofp_table_features may be requested to be changed by
+ * the controller with the exception of the max_entries field, this is
+ * read only and returned by the switch."
+ *
+ * so forbid the controller from attempting to change it.
+ *
+ * (This seems like a particularly arbitrary prohibition since OVS could
+ * easily implement such a feature. Whatever.) */
+ for (size_t i = 0; i < n_tables; i++) {
+ if (old[i].max_entries != new[i].max_entries) {
+ VLOG_INFO_RL(&rl, "can't change max_entries");
+ return OFPERR_OFPTFFC_EPERM;
+ }
+ }
+
+ /* Check that we can set table names. */
+ for (size_t i = 0; i < n_tables; i++) {
+ if (!oftable_may_set_name(&ofproto->tables[i], new[i].name, 1)) {
+ const char *name = ofproto->tables[i].name;
+ VLOG_INFO_RL(&rl, "can't change name of table %"PRIuSIZE" "
+ "to %s because it is already set to %s via OVSDB",
+ i, new[i].name, name ? name : "\"\"");
+ return OFPERR_OFPTFFC_EPERM;
+ }
+ }
+
+ /* Ask the provider to update its features.
+ *
+ * If the provider can't update features, just make sure that the
+ * controller isn't asking to enable new features. OpenFlow says it's OK
+ * if a superset of the requested features are actually enabled. */
+ if (ofproto->ofproto_class->modify_tables) {
+ enum ofperr error = ofproto->ofproto_class->modify_tables(ofproto,
+ old, new);
+ if (error) {
+ VLOG_INFO_RL(&rl, "can't change table features");
+ return error;
+ }
+ } else {
+ for (size_t i = 0; i < n_tables; i++) {
+ if (!ofputil_table_features_are_superset(&old[i], &new[i])) {
+ VLOG_INFO_RL(&rl, "can't increase table features "
+ "for table %"PRIuSIZE, i);
+ return OFPERR_OFPTFFC_EPERM;
+ }
+ }
+ }
+
+ /* Update table names. */
+ for (size_t i = 0; i < n_tables; i++) {
+ oftable_set_name(&ofproto->tables[i], new[i].name, 1);
+ }
+
+ return 0;
+}
+
+static void
+handle_table_features_request(struct ofconn *ofconn,
+ const struct ovs_list *msgs)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+
+ struct ofpbuf *msg = ofpbuf_from_list(ovs_list_back(msgs));
+ const struct ofp_header *request = msg->data;
+ ofpraw_pull_assert(msg);
+
+ /* Update the table features configuration, if requested. */
struct ofputil_table_features *features;
query_tables(ofproto, &features, NULL);
+ if (!ovs_list_is_singleton(msgs) || msg->size) {
+ int retval = handle_table_features_change(ofconn, msgs, features);
+ free(features);
+ if (retval) {
+ if (retval < 0) {
+ /* handle_table_features_change() already sent an error. */
+ } else {
+ ofconn_send_error(ofconn, request, retval);
+ }
+ return;
+ }
+
+ /* Features may have changed, re-query. */
+ query_tables(ofproto, &features, NULL);
+ }
+ /* Reply the controller with the table configuration. */
struct ovs_list replies;
ofpmp_init(&replies, request);
for (size_t i = 0; i < ofproto->n_tables; i++) {
if (!(ofproto->tables[i].flags & OFTABLE_HIDDEN)) {
- ofputil_append_table_features_reply(&features[i], &replies);
+ ofputil_append_table_features(&features[i], NULL, &replies);
}
}
ofconn_send_replies(ofconn, &replies);
free(features);
-
- return 0;
}
/* Returns the vacancy of 'oftable', a number that ranges from 0 (if the table
@@ -8195,7 +8403,7 @@ handle_single_part_openflow(struct ofconn *ofconn, const struct ofp_header *oh,
return handle_table_stats_request(ofconn, oh);
case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
- return handle_table_features_request(ofconn, oh);
+ OVS_NOT_REACHED();
case OFPTYPE_TABLE_DESC_REQUEST:
return handle_table_desc_request(ofconn, oh);
@@ -8306,7 +8514,9 @@ handle_openflow(struct ofconn *ofconn, const struct ovs_list *msgs)
enum ofptype type;
enum ofperr error = ofptype_decode(&type, msg->data);
if (!error) {
- if (!ovs_list_is_short(msgs)) {
+ if (type == OFPTYPE_TABLE_FEATURES_STATS_REQUEST) {
+ handle_table_features_request(ofconn, msgs);
+ } else if (!ovs_list_is_short(msgs)) {
error = OFPERR_OFPBRC_BAD_STAT;
} else {
error = handle_single_part_openflow(ofconn, msg->data, type);
@@ -8631,26 +8841,50 @@ oftable_destroy(struct oftable *table)
free(table->name);
}
-/* Changes the name of 'table' to 'name'. If 'name' is NULL or the empty
- * string, then 'table' will use its default name.
+/* Changes the name of 'table' to 'name'. Null or empty string 'name' unsets
+ * the name.
+ *
+ * 'level' should be 1 if the name is being set via OpenFlow, or 2 if the name
+ * is being set via OVSDB. Higher levels get precedence.
*
* This only affects the name exposed for a table exposed through the OpenFlow
* OFPST_TABLE (as printed by "ovs-ofctl dump-tables"). */
static void
-oftable_set_name(struct oftable *table, const char *name)
-{
- if (name && name[0]) {
- int len = strnlen(name, OFP_MAX_TABLE_NAME_LEN);
- if (!table->name || strncmp(name, table->name, len)) {
+oftable_set_name(struct oftable *table, const char *name, int level)
+{
+ int len = name ? strnlen(name, OFP_MAX_TABLE_NAME_LEN) : 0;
+ if (level >= table->name_level) {
+ if (name) {
+ if (name[0]) {
+ if (!table->name || strncmp(name, table->name, len)) {
+ free(table->name);
+ table->name = xmemdup0(name, len);
+ }
+ } else {
+ free(table->name);
+ table->name = NULL;
+ }
+ table->name_level = level;
+ } else if (table->name_level == level) {
free(table->name);
- table->name = xmemdup0(name, len);
+ table->name = NULL;
+ table->name_level = 0;
}
- } else {
- free(table->name);
- table->name = NULL;
}
}
+/* Returns true if oftable_set_name(table, name, level) would be effective,
+ * false otherwise. */
+static bool
+oftable_may_set_name(const struct oftable *table, const char *name, int level)
+{
+ return (level >= table->name_level
+ || !name
+ || !table->name
+ || !strncmp(name, table->name,
+ strnlen(name, OFP_MAX_TABLE_NAME_LEN)));
+}
+
/* oftables support a choice of two policies when adding a rule would cause the
* number of flows in the table to exceed the configured maximum number: either
* they can refuse to add the new flow or they can evict some existing flow.
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 15a291483..3021f93bc 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2686,6 +2686,19 @@ AT_CLEANUP
AT_SETUP([ofproto - flow table names])
OVS_VSWITCHD_START
add_of_ports br0 1 2
+
+# Set a table name via OpenFlow 1.3 and one via OpenFlow 1.5.
+AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 0 name:xyzzy])
+AT_CHECK([ovs-ofctl -O OpenFlow15 mod-table br0 1 name:quux])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("xyzzy"):
+ table 1 ("quux"): ditto
+ tables 2...252: ditto
+ table 253:
+])
+
+# Set some table names via OVSDB.
AT_CHECK(
[ovs-vsctl \
-- --id=@t0 create Flow_Table name=zero \
@@ -2697,6 +2710,16 @@ AT_CHECK(
<1>
<2>
])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("zero"):
+ table 1 ("one"): ditto
+ table 2 ("two"): ditto
+ tables 3...252: ditto
+ table 253:
+])
+
+# Check that flow table parsing and dumping uses the names.
AT_DATA([flows.txt], [dnl
table=zero in_port=p2 actions=p1,resubmit(,one)
table=one,in_port=p1,ip,actions=ct(table=two)
@@ -2713,6 +2736,73 @@ AT_CHECK([ovs-ofctl --no-names --no-stats dump-flows br0], [0], [dnl
table=1, ip,in_port=1 actions=ct(table=2)
table=1, arp,in_port=1 actions=resubmit(,2)
])
+
+# Setting the same table names via OpenFlow 1.3 or OpenFlow 1.5 is a no-op.
+AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 0 name:zero])
+AT_CHECK([ovs-ofctl -O OpenFlow15 mod-table br0 1 name:one])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("zero"):
+ table 1 ("one"): ditto
+ table 2 ("two"): ditto
+ tables 3...252: ditto
+ table 253:
+])
+
+# Setting different tables names via OpenFlow 1.3 or OpenFlow 1.5 yield errors.
+AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 0 name:xyzzy], 1, [], [stderr])
+AT_CHECK([head -1 stderr], [0], [OFPT_ERROR (OF1.3) (xid=0x5): OFPTFFC_EPERM
+])
+AT_CHECK([ovs-ofctl -O OpenFlow15 mod-table br0 1 name:quux], 1, [], [stderr])
+AT_CHECK([head -1 stderr], [0], [OFPT_ERROR (OF1.5) (xid=0x5): OFPTFFC_EPERM
+])
+
+# But we can still set table names for those not set via OVSDB.
+AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 3 name:three])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("zero"):
+ table 1 ("one"): ditto
+ table 2 ("two"): ditto
+ table 3 ("three"): ditto
+ tables 4...252: ditto
+ table 253:
+])
+
+# Unsetting names via OVSDB then setting them via OpenFlow works too.
+AT_CHECK([ovs-vsctl remove bridge br0 Flow_Table 2])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("zero"):
+ table 1 ("one"): ditto
+ table 2: ditto
+ table 3 ("three"): ditto
+ tables 4...252: ditto
+ table 253:
+])
+AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 2 name:foobar])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("zero"):
+ table 1 ("one"): ditto
+ table 2 ("foobar"): ditto
+ table 3 ("three"): ditto
+ tables 4...252: ditto
+ table 253:
+])
+
+# We can clear names via OpenFlow, at least if they were set that way.
+AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 2 name:])
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0 |grep '^ table'],
+ [0], [dnl
+ table 0 ("zero"):
+ table 1 ("one"): ditto
+ table 2: ditto
+ table 3 ("three"): ditto
+ tables 4...252: ditto
+ table 253:
+])
+
OVS_VSWITCHD_STOP
AT_CLEANUP
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 81827b1ec..c7b156571 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -84,6 +84,18 @@ Send to controller. (This is how an OpenFlow 1.0 switch always
handles packets that do not match any flow in the last table.)
.RE
.IP
+In OpenFlow 1.3 and later (which must be enabled with the \fB\-O\fR
+option) and Open vSwitch 2.11 and later only, \fBmod\-table\fR can
+change the name of a table:
+.RS
+.IP \fBname:\fInew-name\fR
+Changes the name of the table to \fInew-name\fR. Use an empty
+\fInew-name\fR to clear the name. (This will be ineffective if the
+name is set via the \fBname\fR column in the \fBFlow_Table\fR table in
+the \fBOpen_vSwitch\fR database as described in
+\fBovs\-vswitchd.conf.db\fR(5).)
+.RE
+.IP
In OpenFlow 1.4 and later (which must be enabled with the \fB\-O\fR
option) only, \fBmod\-table\fR configures the behavior when a
controller attempts to add a flow to a flow table that is full. The
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 0ff4ed259..aab5a5cd9 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -913,9 +913,9 @@ ofctl_dump_table_features(struct ovs_cmdl_context *ctx)
done = !ofpmp_more(reply->data);
for (;;) {
struct ofputil_table_features tf;
- int retval;
-
- retval = ofputil_decode_table_features(reply, &tf, true);
+ struct ofpbuf raw_properties;
+ int retval = ofputil_decode_table_features(
+ reply, &tf, &raw_properties);
if (retval) {
if (retval != EOF) {
ovs_fatal(0, "decode error: %s",
@@ -1207,6 +1207,7 @@ struct table_iterator {
bool more;
struct ofputil_table_features features;
+ struct ofpbuf raw_properties;
};
/* Initializes 'ti' to prepare for iterating through all of the tables on the
@@ -1248,7 +1249,7 @@ table_iterator_next(struct table_iterator *ti)
ovs_assert(ti->variant == TI_FEATURES);
retval = ofputil_decode_table_features(ti->reply,
&ti->features,
- true);
+ &ti->raw_properties);
}
if (!retval) {
return &ti->features;
@@ -2581,15 +2582,94 @@ fetch_table_desc(struct vconn *vconn, struct ofputil_table_mod *tm,
}
static void
+change_table_name(struct vconn *vconn, uint8_t table_id, const char *new_name)
+{
+ /* Get all tables' features and properties. */
+ struct table {
+ struct ofputil_table_features tf;
+ struct ofpbuf *raw_properties;
+ } *tables[256];
+ memset(tables, 0, sizeof tables);
+
+ struct table_iterator ti;
+ table_iterator_init(&ti, vconn);
+ while (table_iterator_next(&ti)) {
+ struct table *t = tables[ti.features.table_id] = xmalloc(sizeof *t);
+ t->tf = ti.features;
+ t->raw_properties = ofpbuf_clone(&ti.raw_properties);
+ }
+ table_iterator_destroy(&ti);
+
+ /* Change the name for table 'table_id'. */
+ struct table *t = tables[table_id];
+ if (!t) {
+ ovs_fatal(0, "switch does not have table %"PRIu8, table_id);
+ }
+ ovs_strlcpy(t->tf.name, new_name, OFP_MAX_TABLE_NAME_LEN);
+
+ /* Compose the transaction. */
+ enum ofp_version version = vconn_get_version(vconn);
+ struct ovs_list requests = OVS_LIST_INITIALIZER(&requests);
+ struct ofpbuf *tfr = ofputil_encode_table_features_request(version);
+ ovs_list_push_back(&requests, &tfr->list_node);
+ if (version >= OFP15_VERSION) {
+ /* For OpenFlow 1.5, we can use a single OFPTFC15_MODIFY without any
+ * properties. */
+ t->tf.command = OFPTFC15_MODIFY;
+ t->tf.any_properties = false;
+ ofputil_append_table_features(&t->tf, NULL, &requests);
+ } else {
+ /* For OpenFlow 1.3 and 1.4, we have to regurgitate all of the tables
+ * and their properties. */
+ for (size_t i = 0; i < 256; i++) {
+ if (tables[i]) {
+ ofputil_append_table_features(&tables[i]->tf,
+ tables[i]->raw_properties,
+ &requests);
+ }
+ }
+ }
+
+ /* Transact.
+ *
+ * The reply repeats the entire new configuration of the tables, so we
+ * don't bother printing it unless there's an error. */
+ struct ovs_list replies;
+ struct ofpbuf *reply;
+ vconn_transact_multipart(vconn, &requests, &replies);
+ LIST_FOR_EACH (reply, list_node, &replies) {
+ enum ofptype type;
+ enum ofperr error = ofptype_decode(&type, reply->data);
+ if (error) {
+ ovs_fatal(0, "decode error: %s", ofperr_get_name(error));
+ } else if (type == OFPTYPE_ERROR) {
+ ofp_print(stderr, reply->data, reply->size, NULL, NULL,
+ verbosity + 1);
+ exit(1);
+ }
+ }
+ ofpbuf_list_delete(&replies);
+
+ /* Clean up. */
+ for (size_t i = 0; i < ARRAY_SIZE(tables); i++) {
+ if (tables[i]) {
+ ofpbuf_delete(tables[i]->raw_properties);
+ free(tables[i]);
+ }
+ }
+}
+
+static void
ofctl_mod_table(struct ovs_cmdl_context *ctx)
{
uint32_t usable_versions;
struct ofputil_table_mod tm;
+ const char *name;
struct vconn *vconn;
char *error;
int i;
- error = parse_ofp_table_mod(&tm, ctx->argv[2], ctx->argv[3],
+ error = parse_ofp_table_mod(&tm, &name, ctx->argv[2], ctx->argv[3],
tables_to_accept(ctx->argv[1]),
&usable_versions);
if (error) {
@@ -2607,27 +2687,33 @@ ofctl_mod_table(struct ovs_cmdl_context *ctx)
mask_allowed_ofp_versions(usable_versions);
enum ofputil_protocol protocol = open_vconn(ctx->argv[1], &vconn);
- /* For OpenFlow 1.4+, ovs-ofctl mod-table should not affect table-config
- * properties that the user didn't ask to change, so it is necessary to
- * restore the current configuration of table-config parameters using
- * OFPMP14_TABLE_DESC request. */
- if ((allowed_versions & (1u << OFP14_VERSION)) ||
- (allowed_versions & (1u << OFP15_VERSION))) {
- struct ofputil_table_desc td;
-
- if (tm.table_id == OFPTT_ALL) {
- for (i = 0; i < OFPTT_MAX; i++) {
- tm.table_id = i;
+ if (name) {
+ change_table_name(vconn, tm.table_id, name);
+ } else {
+ /* For OpenFlow 1.4+, ovs-ofctl mod-table should not affect
+ * table-config properties that the user didn't ask to change, so it is
+ * necessary to restore the current configuration of table-config
+ * parameters using OFPMP14_TABLE_DESC request. */
+ if (allowed_versions & ((1u << OFP14_VERSION) |
+ (1u << OFP15_VERSION) |
+ (1u << OFP16_VERSION))) {
+ struct ofputil_table_desc td;
+
+ if (tm.table_id == OFPTT_ALL) {
+ for (i = 0; i < OFPTT_MAX; i++) {
+ tm.table_id = i;
+ fetch_table_desc(vconn, &tm, &td);
+ transact_noreply(vconn,
+ ofputil_encode_table_mod(&tm, protocol));
+ }
+ } else {
fetch_table_desc(vconn, &tm, &td);
- transact_noreply(vconn,
- ofputil_encode_table_mod(&tm, protocol));
+ transact_noreply(vconn, ofputil_encode_table_mod(&tm,
+ protocol));
}
} else {
- fetch_table_desc(vconn, &tm, &td);
transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
}
- } else {
- transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
}
vconn_close(vconn);
}