mirror of
https://github.com/openvswitch/ovs
synced 2025-08-30 13:58:14 +00:00
Implement Openflow 1.4 Vacancy Events for OFPT_TABLE_MOD.
OpenFlow 1.4 introduces the ability to turn on vacancy events with an OFPT_TABLE_MOD message specifying OFPTC_VACANCY_EVENTS. This commit adds support for the new feature in ovs-ofctl mod-table. As per the openflow specification-1.4, vacancy event adds a mechanism enabling the controller to get an early warning based on capacity threshold chosen by the controller. With this commit, vacancy events can be configured as: ovs-ofctl -O OpenFlow14 mod-table <bridge> <table> vacancy:<low,high> <low,high> specify vacancy threshold values in percentage for vacancy_down and vacancy_up respectively. To disable vacancy events, following command should be given: ovs-ofctl -O OpenFlow14 mod-table <bridge> <table> novacancy Signed-off-by: Saloni Jain <saloni.jain@tcs.com> Co-authored-by: Shashwat Srivastava <shashwat.srivastava@tcs.com> Signed-off-by: Shashwat Srivastava <shashwat.srivastava@tcs.com> Co-authored-by: Sandeep Kumar <sandeep.kumar16@tcs.com> Signed-off-by: Sandeep Kumar <sandeep.kumar16@tcs.com> [blp@ovn.org fixed a few typos] Signed-off-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
parent
331e7aefe1
commit
de7d3c0761
1
NEWS
1
NEWS
@ -8,6 +8,7 @@ Post-v2.4.0
|
|||||||
now supported.
|
now supported.
|
||||||
* OpenFlow 1.4+ "importance" is now considered for flow eviction.
|
* OpenFlow 1.4+ "importance" is now considered for flow eviction.
|
||||||
* OpenFlow 1.4+ OFPTC_EVICTION is now implemented.
|
* OpenFlow 1.4+ OFPTC_EVICTION is now implemented.
|
||||||
|
* OpenFlow 1.4+ OFPTC_VACANCY_EVENTS is now implemented.
|
||||||
* OpenFlow 1.4+ OFPMP_TABLE_DESC is now implemented.
|
* OpenFlow 1.4+ OFPMP_TABLE_DESC is now implemented.
|
||||||
* Allow modifying the ICMPv4/ICMPv6 type and code fields.
|
* Allow modifying the ICMPv4/ICMPv6 type and code fields.
|
||||||
- ovs-ofctl:
|
- ovs-ofctl:
|
||||||
|
@ -886,6 +886,49 @@ parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string,
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
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 = strdup(setting);
|
||||||
|
int vacancy_up, vacancy_down;
|
||||||
|
|
||||||
|
strtok_r(value, ":", &save_ptr);
|
||||||
|
vac_down = strtok_r(NULL, ",", &save_ptr);
|
||||||
|
if (!vac_down) {
|
||||||
|
return xasprintf("Vacancy down value missing");
|
||||||
|
}
|
||||||
|
if (!str_to_int(vac_down, 0, &vacancy_down) ||
|
||||||
|
vacancy_down < 0 || vacancy_down > 100) {
|
||||||
|
return xasprintf("Invalid vacancy down value \"%s\"", vac_down);
|
||||||
|
}
|
||||||
|
vac_up = strtok_r(NULL, ",", &save_ptr);
|
||||||
|
if (!vac_up) {
|
||||||
|
return xasprintf("Vacancy up value missing");
|
||||||
|
}
|
||||||
|
if (!str_to_int(vac_up, 0, &vacancy_up) ||
|
||||||
|
vacancy_up < 0 || vacancy_up > 100) {
|
||||||
|
return xasprintf("Invalid vacancy up value \"%s\"", vac_up);
|
||||||
|
}
|
||||||
|
if (vacancy_down > vacancy_up) {
|
||||||
|
return xasprintf("Invalid vacancy range, vacancy up should be greater"
|
||||||
|
" than vacancy down ""(%s)",
|
||||||
|
ofperr_to_string(OFPERR_OFPBPC_BAD_VALUE));
|
||||||
|
}
|
||||||
|
tm->table_vacancy.vacancy_down = vacancy_down;
|
||||||
|
tm->table_vacancy.vacancy_up = vacancy_up;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Convert 'table_id' and 'setting' (as described for the "mod-table" command
|
/* 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
|
* in the ovs-ofctl man page) into 'tm' for sending a table_mod command to a
|
||||||
* switch.
|
* switch.
|
||||||
@ -912,13 +955,13 @@ parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id,
|
|||||||
tm->miss = OFPUTIL_TABLE_MISS_DEFAULT;
|
tm->miss = OFPUTIL_TABLE_MISS_DEFAULT;
|
||||||
tm->eviction = OFPUTIL_TABLE_EVICTION_DEFAULT;
|
tm->eviction = OFPUTIL_TABLE_EVICTION_DEFAULT;
|
||||||
tm->eviction_flags = UINT32_MAX;
|
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.1 and 1.2 can configure table-miss via table_mod.
|
||||||
* Only OpenFlow 1.4+ can configure eviction via table_mod.
|
* Only OpenFlow 1.4+ can configure eviction and vacancy events
|
||||||
*
|
* via table_mod.
|
||||||
* (OpenFlow 1.4+ can also configure vacancy events via table_mod, but OVS
|
|
||||||
* doesn't support those yet and they're also logically a per-OpenFlow
|
|
||||||
* session setting so it wouldn't make sense to support them here anyway.)
|
|
||||||
*/
|
*/
|
||||||
if (!strcmp(setting, "controller")) {
|
if (!strcmp(setting, "controller")) {
|
||||||
tm->miss = OFPUTIL_TABLE_MISS_CONTROLLER;
|
tm->miss = OFPUTIL_TABLE_MISS_CONTROLLER;
|
||||||
@ -935,6 +978,16 @@ parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id,
|
|||||||
} else if (!strcmp(setting, "noevict")) {
|
} else if (!strcmp(setting, "noevict")) {
|
||||||
tm->eviction = OFPUTIL_TABLE_EVICTION_OFF;
|
tm->eviction = OFPUTIL_TABLE_EVICTION_OFF;
|
||||||
*usable_versions = (1 << OFP14_VERSION) | (1u << OFP15_VERSION);
|
*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 {
|
} else {
|
||||||
return xasprintf("invalid table_mod setting %s", setting);
|
return xasprintf("invalid table_mod setting %s", setting);
|
||||||
}
|
}
|
||||||
|
@ -100,5 +100,8 @@ char *str_to_be64(const char *str, ovs_be64 *valuep) OVS_WARN_UNUSED_RESULT;
|
|||||||
char *str_to_mac(const char *str, struct eth_addr *mac) OVS_WARN_UNUSED_RESULT;
|
char *str_to_mac(const char *str, struct eth_addr *mac) OVS_WARN_UNUSED_RESULT;
|
||||||
char *str_to_ip(const char *str, ovs_be32 *ip) OVS_WARN_UNUSED_RESULT;
|
char *str_to_ip(const char *str, ovs_be32 *ip) OVS_WARN_UNUSED_RESULT;
|
||||||
char *str_to_connhelper(const char *str, uint16_t *alg) OVS_WARN_UNUSED_RESULT;
|
char *str_to_connhelper(const char *str, uint16_t *alg) OVS_WARN_UNUSED_RESULT;
|
||||||
|
char *parse_ofp_table_vacancy(struct ofputil_table_mod *,
|
||||||
|
const char *flow_miss_handling)
|
||||||
|
OVS_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
#endif /* ofp-parse.h */
|
#endif /* ofp-parse.h */
|
||||||
|
@ -990,6 +990,18 @@ ofputil_put_eviction_flags(struct ds *string, uint32_t eviction_flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
ofputil_table_vacancy_to_string(enum ofputil_table_vacancy vacancy)
|
||||||
|
{
|
||||||
|
switch (vacancy) {
|
||||||
|
case OFPUTIL_TABLE_VACANCY_DEFAULT: return "default";
|
||||||
|
case OFPUTIL_TABLE_VACANCY_ON: return "on";
|
||||||
|
case OFPUTIL_TABLE_VACANCY_OFF: return "off";
|
||||||
|
default: return "***error***";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
|
ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
|
||||||
{
|
{
|
||||||
@ -1020,6 +1032,15 @@ ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
|
|||||||
ds_put_cstr(string, "eviction_flags=");
|
ds_put_cstr(string, "eviction_flags=");
|
||||||
ofputil_put_eviction_flags(string, pm.eviction_flags);
|
ofputil_put_eviction_flags(string, pm.eviction_flags);
|
||||||
}
|
}
|
||||||
|
if (pm.vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) {
|
||||||
|
ds_put_format(string, ", vacancy=%s",
|
||||||
|
ofputil_table_vacancy_to_string(pm.vacancy));
|
||||||
|
if (pm.vacancy == OFPUTIL_TABLE_VACANCY_ON) {
|
||||||
|
ds_put_format(string, " vacancy:%"PRIu8""
|
||||||
|
",%"PRIu8"", pm.table_vacancy.vacancy_down,
|
||||||
|
pm.table_vacancy.vacancy_up);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function will print the Table description properties. */
|
/* This function will print the Table description properties. */
|
||||||
|
@ -53,10 +53,13 @@ VLOG_DEFINE_THIS_MODULE(ofp_util);
|
|||||||
* in the peer and so there's not much point in showing a lot of them. */
|
* in the peer and so there's not much point in showing a lot of them. */
|
||||||
static struct vlog_rate_limit bad_ofmsg_rl = VLOG_RATE_LIMIT_INIT(1, 5);
|
static struct vlog_rate_limit bad_ofmsg_rl = VLOG_RATE_LIMIT_INIT(1, 5);
|
||||||
|
|
||||||
|
static enum ofputil_table_vacancy ofputil_decode_table_vacancy(
|
||||||
|
ovs_be32 config, enum ofp_version);
|
||||||
static enum ofputil_table_eviction ofputil_decode_table_eviction(
|
static enum ofputil_table_eviction ofputil_decode_table_eviction(
|
||||||
ovs_be32 config, enum ofp_version);
|
ovs_be32 config, enum ofp_version);
|
||||||
static ovs_be32 ofputil_encode_table_config(enum ofputil_table_miss,
|
static ovs_be32 ofputil_encode_table_config(enum ofputil_table_miss,
|
||||||
enum ofputil_table_eviction,
|
enum ofputil_table_eviction,
|
||||||
|
enum ofputil_table_vacancy,
|
||||||
enum ofp_version);
|
enum ofp_version);
|
||||||
|
|
||||||
struct ofp_prop_header {
|
struct ofp_prop_header {
|
||||||
@ -4999,10 +5002,62 @@ ofputil_append_table_desc_reply(const struct ofputil_table_desc *td,
|
|||||||
otd->length = htons(reply->size - start_otd);
|
otd->length = htons(reply->size - start_otd);
|
||||||
otd->table_id = td->table_id;
|
otd->table_id = td->table_id;
|
||||||
otd->config = ofputil_encode_table_config(OFPUTIL_TABLE_MISS_DEFAULT,
|
otd->config = ofputil_encode_table_config(OFPUTIL_TABLE_MISS_DEFAULT,
|
||||||
td->eviction, version);
|
td->eviction, td->vacancy,
|
||||||
|
version);
|
||||||
ofpmp_postappend(replies, start_otd);
|
ofpmp_postappend(replies, start_otd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
log_property(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) {
|
||||||
|
log_property(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) {
|
||||||
|
log_property(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);
|
||||||
|
}
|
||||||
|
|
||||||
static enum ofperr
|
static enum ofperr
|
||||||
parse_table_mod_eviction_property(struct ofpbuf *property,
|
parse_table_mod_eviction_property(struct ofpbuf *property,
|
||||||
struct ofputil_table_mod *tm)
|
struct ofputil_table_mod *tm)
|
||||||
@ -5038,8 +5093,10 @@ ofputil_decode_table_eviction(ovs_be32 config, enum ofp_version version)
|
|||||||
static ovs_be32
|
static ovs_be32
|
||||||
ofputil_encode_table_config(enum ofputil_table_miss miss,
|
ofputil_encode_table_config(enum ofputil_table_miss miss,
|
||||||
enum ofputil_table_eviction eviction,
|
enum ofputil_table_eviction eviction,
|
||||||
|
enum ofputil_table_vacancy vacancy,
|
||||||
enum ofp_version version)
|
enum ofp_version version)
|
||||||
{
|
{
|
||||||
|
uint32_t config = 0;
|
||||||
/* See the section "OFPTC_* Table Configuration" in DESIGN.md for more
|
/* See the section "OFPTC_* Table Configuration" in DESIGN.md for more
|
||||||
* information on the crazy evolution of this field. */
|
* information on the crazy evolution of this field. */
|
||||||
switch (version) {
|
switch (version) {
|
||||||
@ -5072,10 +5129,15 @@ ofputil_encode_table_config(enum ofputil_table_miss miss,
|
|||||||
|
|
||||||
case OFP14_VERSION:
|
case OFP14_VERSION:
|
||||||
case OFP15_VERSION:
|
case OFP15_VERSION:
|
||||||
/* OpenFlow 1.4 introduced OFPTC14_EVICTION and OFPTC14_VACANCY_EVENTS
|
/* OpenFlow 1.4 introduced OFPTC14_EVICTION and
|
||||||
* and we don't support the latter yet. */
|
* OFPTC14_VACANCY_EVENTS. */
|
||||||
return htonl(eviction == OFPUTIL_TABLE_EVICTION_ON
|
if (eviction == OFPUTIL_TABLE_EVICTION_ON) {
|
||||||
? OFPTC14_EVICTION : 0);
|
config |= OFPTC14_EVICTION;
|
||||||
|
}
|
||||||
|
if (vacancy == OFPUTIL_TABLE_VACANCY_ON) {
|
||||||
|
config |= OFPTC14_VACANCY_EVENTS;
|
||||||
|
}
|
||||||
|
return htonl(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
OVS_NOT_REACHED();
|
OVS_NOT_REACHED();
|
||||||
@ -5126,6 +5188,7 @@ ofputil_decode_table_mod(const struct ofp_header *oh,
|
|||||||
pm->miss = OFPUTIL_TABLE_MISS_DEFAULT;
|
pm->miss = OFPUTIL_TABLE_MISS_DEFAULT;
|
||||||
pm->eviction = OFPUTIL_TABLE_EVICTION_DEFAULT;
|
pm->eviction = OFPUTIL_TABLE_EVICTION_DEFAULT;
|
||||||
pm->eviction_flags = UINT32_MAX;
|
pm->eviction_flags = UINT32_MAX;
|
||||||
|
pm->vacancy = OFPUTIL_TABLE_VACANCY_DEFAULT;
|
||||||
ofpbuf_use_const(&b, oh, ntohs(oh->length));
|
ofpbuf_use_const(&b, oh, ntohs(oh->length));
|
||||||
raw = ofpraw_pull_assert(&b);
|
raw = ofpraw_pull_assert(&b);
|
||||||
|
|
||||||
@ -5140,6 +5203,7 @@ ofputil_decode_table_mod(const struct ofp_header *oh,
|
|||||||
pm->table_id = otm->table_id;
|
pm->table_id = otm->table_id;
|
||||||
pm->miss = ofputil_decode_table_miss(otm->config, oh->version);
|
pm->miss = ofputil_decode_table_miss(otm->config, oh->version);
|
||||||
pm->eviction = ofputil_decode_table_eviction(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) {
|
while (b.size > 0) {
|
||||||
struct ofpbuf property;
|
struct ofpbuf property;
|
||||||
enum ofperr error;
|
enum ofperr error;
|
||||||
@ -5155,6 +5219,10 @@ ofputil_decode_table_mod(const struct ofp_header *oh,
|
|||||||
error = parse_table_mod_eviction_property(&property, pm);
|
error = parse_table_mod_eviction_property(&property, pm);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OFPTMPT14_VACANCY:
|
||||||
|
error = parse_table_mod_vacancy_property(&property, pm);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error = OFPERR_OFPBRC_BAD_TYPE;
|
error = OFPERR_OFPBRC_BAD_TYPE;
|
||||||
break;
|
break;
|
||||||
@ -5196,19 +5264,20 @@ ofputil_encode_table_mod(const struct ofputil_table_mod *tm,
|
|||||||
otm = ofpbuf_put_zeros(b, sizeof *otm);
|
otm = ofpbuf_put_zeros(b, sizeof *otm);
|
||||||
otm->table_id = tm->table_id;
|
otm->table_id = tm->table_id;
|
||||||
otm->config = ofputil_encode_table_config(tm->miss, tm->eviction,
|
otm->config = ofputil_encode_table_config(tm->miss, tm->eviction,
|
||||||
ofp_version);
|
tm->vacancy, ofp_version);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OFP14_VERSION:
|
case OFP14_VERSION:
|
||||||
case OFP15_VERSION: {
|
case OFP15_VERSION: {
|
||||||
struct ofp14_table_mod *otm;
|
struct ofp14_table_mod *otm;
|
||||||
struct ofp14_table_mod_prop_eviction *ote;
|
struct ofp14_table_mod_prop_eviction *ote;
|
||||||
|
struct ofp14_table_mod_prop_vacancy *otv;
|
||||||
|
|
||||||
b = ofpraw_alloc(OFPRAW_OFPT14_TABLE_MOD, ofp_version, 0);
|
b = ofpraw_alloc(OFPRAW_OFPT14_TABLE_MOD, ofp_version, 0);
|
||||||
otm = ofpbuf_put_zeros(b, sizeof *otm);
|
otm = ofpbuf_put_zeros(b, sizeof *otm);
|
||||||
otm->table_id = tm->table_id;
|
otm->table_id = tm->table_id;
|
||||||
otm->config = ofputil_encode_table_config(tm->miss, tm->eviction,
|
otm->config = ofputil_encode_table_config(tm->miss, tm->eviction,
|
||||||
ofp_version);
|
tm->vacancy, ofp_version);
|
||||||
|
|
||||||
if (tm->eviction_flags != UINT32_MAX) {
|
if (tm->eviction_flags != UINT32_MAX) {
|
||||||
ote = ofpbuf_put_zeros(b, sizeof *ote);
|
ote = ofpbuf_put_zeros(b, sizeof *ote);
|
||||||
@ -5216,6 +5285,13 @@ ofputil_encode_table_mod(const struct ofputil_table_mod *tm,
|
|||||||
ote->length = htons(sizeof *ote);
|
ote->length = htons(sizeof *ote);
|
||||||
ote->flags = htonl(tm->eviction_flags);
|
ote->flags = htonl(tm->eviction_flags);
|
||||||
}
|
}
|
||||||
|
if (tm->vacancy == OFPUTIL_TABLE_VACANCY_ON) {
|
||||||
|
otv = ofpbuf_put_zeros(b, sizeof *otv);
|
||||||
|
otv->type = htons(OFPTMPT14_VACANCY);
|
||||||
|
otv->length = htons(sizeof *otv);
|
||||||
|
otv->vacancy_down = tm->table_vacancy.vacancy_down;
|
||||||
|
otv->vacancy_up = tm->table_vacancy.vacancy_up;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -5682,6 +5758,7 @@ ofputil_put_ofp12_table_stats(const struct ofputil_table_stats *stats,
|
|||||||
features->nonmiss.instructions, OFP12_VERSION);
|
features->nonmiss.instructions, OFP12_VERSION);
|
||||||
out->config = ofputil_encode_table_config(features->miss_config,
|
out->config = ofputil_encode_table_config(features->miss_config,
|
||||||
OFPUTIL_TABLE_EVICTION_DEFAULT,
|
OFPUTIL_TABLE_EVICTION_DEFAULT,
|
||||||
|
OFPUTIL_TABLE_VACANCY_DEFAULT,
|
||||||
OFP12_VERSION);
|
OFP12_VERSION);
|
||||||
out->max_entries = htonl(features->max_entries);
|
out->max_entries = htonl(features->max_entries);
|
||||||
out->active_count = htonl(stats->active_count);
|
out->active_count = htonl(stats->active_count);
|
||||||
|
@ -628,6 +628,33 @@ enum ofputil_table_eviction {
|
|||||||
OFPUTIL_TABLE_EVICTION_OFF /* Disable eviction. */
|
OFPUTIL_TABLE_EVICTION_OFF /* Disable eviction. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Abstract version of OFPTC14_VACANCY_EVENTS.
|
||||||
|
*
|
||||||
|
* OpenFlow 1.0 through 1.3 don't know anything about vacancy events, so
|
||||||
|
* decoding a message for one of these protocols always yields
|
||||||
|
* OFPUTIL_TABLE_VACANCY_DEFAULT. */
|
||||||
|
enum ofputil_table_vacancy {
|
||||||
|
OFPUTIL_TABLE_VACANCY_DEFAULT, /* No value. */
|
||||||
|
OFPUTIL_TABLE_VACANCY_ON, /* Enable vacancy events. */
|
||||||
|
OFPUTIL_TABLE_VACANCY_OFF /* Disable vacancy events. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Abstract version of OFPTMPT_VACANCY.
|
||||||
|
*
|
||||||
|
* Openflow 1.4+ defines vacancy events.
|
||||||
|
* The fields vacancy_down and vacancy_up are the threshold for generating
|
||||||
|
* vacancy events that should be configured on the flow table, expressed as
|
||||||
|
* a percent.
|
||||||
|
* The vacancy field is only used when this property in included in a
|
||||||
|
* OFPMP_TABLE_DESC multipart reply or a OFPT_TABLE_STATUS message and
|
||||||
|
* represent the current vacancy of the table, expressed as a percent. In
|
||||||
|
* OFP_TABLE_MOD requests, this field must be set to 0 */
|
||||||
|
struct ofputil_table_mod_prop_vacancy {
|
||||||
|
uint8_t vacancy_down; /* Vacancy threshold when space decreases (%). */
|
||||||
|
uint8_t vacancy_up; /* Vacancy threshold when space increases (%). */
|
||||||
|
uint8_t vacancy; /* Current vacancy (%). */
|
||||||
|
};
|
||||||
|
|
||||||
/* Abstract ofp_table_mod. */
|
/* Abstract ofp_table_mod. */
|
||||||
struct ofputil_table_mod {
|
struct ofputil_table_mod {
|
||||||
uint8_t table_id; /* ID of the table, 0xff indicates all tables. */
|
uint8_t table_id; /* ID of the table, 0xff indicates all tables. */
|
||||||
@ -644,6 +671,16 @@ struct ofputil_table_mod {
|
|||||||
* absence. For other versions, ignored on encoding, decoded to
|
* absence. For other versions, ignored on encoding, decoded to
|
||||||
* UINT32_MAX.*/
|
* UINT32_MAX.*/
|
||||||
uint32_t eviction_flags; /* OFPTMPEF14_*. */
|
uint32_t eviction_flags; /* OFPTMPEF14_*. */
|
||||||
|
|
||||||
|
/* OpenFlow 1.4+ only. For other versions, ignored on encoding, decoded to
|
||||||
|
* OFPUTIL_TABLE_VACANCY_DEFAULT. */
|
||||||
|
enum ofputil_table_vacancy vacancy;
|
||||||
|
|
||||||
|
/* Openflow 1.4+ only. Defines threshold values of vacancy expressed as
|
||||||
|
* percent, value of current vacancy is set to zero for table-mod.
|
||||||
|
* For other versions, ignored on encoding, all values decoded to
|
||||||
|
* zero. */
|
||||||
|
struct ofputil_table_mod_prop_vacancy table_vacancy;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Abstract ofp14_table_desc. */
|
/* Abstract ofp14_table_desc. */
|
||||||
@ -651,6 +688,8 @@ struct ofputil_table_desc {
|
|||||||
uint8_t table_id; /* ID of the table. */
|
uint8_t table_id; /* ID of the table. */
|
||||||
enum ofputil_table_eviction eviction;
|
enum ofputil_table_eviction eviction;
|
||||||
uint32_t eviction_flags; /* UINT32_MAX if not present. */
|
uint32_t eviction_flags; /* UINT32_MAX if not present. */
|
||||||
|
enum ofputil_table_vacancy vacancy;
|
||||||
|
struct ofputil_table_mod_prop_vacancy table_vacancy;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ofperr ofputil_decode_table_mod(const struct ofp_header *,
|
enum ofperr ofputil_decode_table_mod(const struct ofp_header *,
|
||||||
|
@ -256,6 +256,15 @@ struct oftable {
|
|||||||
#define EVICTION_OPENFLOW (1 << 1) /* Set to 1 if OpenFlow enables eviction. */
|
#define EVICTION_OPENFLOW (1 << 1) /* Set to 1 if OpenFlow enables eviction. */
|
||||||
unsigned int eviction;
|
unsigned int eviction;
|
||||||
|
|
||||||
|
/* If true, vacancy events are enabled; otherwise they are disabled. */
|
||||||
|
bool vacancy_enabled;
|
||||||
|
|
||||||
|
/* Non-zero values for vacancy_up and vacancy_down indicates that vacancy
|
||||||
|
* is enabled by table-mod, else these values are set to zero when
|
||||||
|
* vacancy is disabled */
|
||||||
|
uint8_t vacancy_down; /* Vacancy threshold when space decreases (%). */
|
||||||
|
uint8_t vacancy_up; /* Vacancy threshold when space increases (%). */
|
||||||
|
|
||||||
atomic_ulong n_matched;
|
atomic_ulong n_matched;
|
||||||
atomic_ulong n_missed;
|
atomic_ulong n_missed;
|
||||||
};
|
};
|
||||||
|
@ -6646,20 +6646,20 @@ ofproto_table_get_miss_config(const struct ofproto *ofproto, uint8_t table_id)
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
table_mod__(struct oftable *oftable,
|
table_mod__(struct oftable *oftable,
|
||||||
enum ofputil_table_miss miss, enum ofputil_table_eviction eviction)
|
const struct ofputil_table_mod *tm)
|
||||||
{
|
{
|
||||||
if (miss == OFPUTIL_TABLE_MISS_DEFAULT) {
|
if (tm->miss == OFPUTIL_TABLE_MISS_DEFAULT) {
|
||||||
/* This is how an OFPT_TABLE_MOD decodes if it doesn't specify any
|
/* This is how an OFPT_TABLE_MOD decodes if it doesn't specify any
|
||||||
* table-miss configuration (because the protocol used doesn't have
|
* table-miss configuration (because the protocol used doesn't have
|
||||||
* such a concept), so there's nothing to do. */
|
* such a concept), so there's nothing to do. */
|
||||||
} else {
|
} else {
|
||||||
atomic_store_relaxed(&oftable->miss_config, miss);
|
atomic_store_relaxed(&oftable->miss_config, tm->miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int new_eviction = oftable->eviction;
|
unsigned int new_eviction = oftable->eviction;
|
||||||
if (eviction == OFPUTIL_TABLE_EVICTION_ON) {
|
if (tm->eviction == OFPUTIL_TABLE_EVICTION_ON) {
|
||||||
new_eviction |= EVICTION_OPENFLOW;
|
new_eviction |= EVICTION_OPENFLOW;
|
||||||
} else if (eviction == OFPUTIL_TABLE_EVICTION_OFF) {
|
} else if (tm->eviction == OFPUTIL_TABLE_EVICTION_OFF) {
|
||||||
new_eviction &= ~EVICTION_OPENFLOW;
|
new_eviction &= ~EVICTION_OPENFLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6670,6 +6670,16 @@ table_mod__(struct oftable *oftable,
|
|||||||
oftable->n_eviction_fields);
|
oftable->n_eviction_fields);
|
||||||
ovs_mutex_unlock(&ofproto_mutex);
|
ovs_mutex_unlock(&ofproto_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tm->vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) {
|
||||||
|
ovs_mutex_lock(&ofproto_mutex);
|
||||||
|
oftable->vacancy_enabled = (tm->vacancy == OFPUTIL_TABLE_VACANCY_ON
|
||||||
|
? OFPTC14_VACANCY_EVENTS
|
||||||
|
: 0);
|
||||||
|
oftable->vacancy_down = tm->table_vacancy.vacancy_down;
|
||||||
|
oftable->vacancy_up = tm->table_vacancy.vacancy_up;
|
||||||
|
ovs_mutex_unlock(&ofproto_mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum ofperr
|
static enum ofperr
|
||||||
@ -6693,7 +6703,7 @@ table_mod(struct ofproto *ofproto, const struct ofputil_table_mod *tm)
|
|||||||
struct oftable *oftable;
|
struct oftable *oftable;
|
||||||
OFPROTO_FOR_EACH_TABLE (oftable, ofproto) {
|
OFPROTO_FOR_EACH_TABLE (oftable, ofproto) {
|
||||||
if (!(oftable->flags & (OFTABLE_HIDDEN | OFTABLE_READONLY))) {
|
if (!(oftable->flags & (OFTABLE_HIDDEN | OFTABLE_READONLY))) {
|
||||||
table_mod__(oftable, tm->miss, tm->eviction);
|
table_mod__(oftable, tm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -6701,7 +6711,7 @@ table_mod(struct ofproto *ofproto, const struct ofputil_table_mod *tm)
|
|||||||
if (oftable->flags & OFTABLE_READONLY) {
|
if (oftable->flags & OFTABLE_READONLY) {
|
||||||
return OFPERR_OFPTMFC_EPERM;
|
return OFPERR_OFPTMFC_EPERM;
|
||||||
}
|
}
|
||||||
table_mod__(oftable, tm->miss, tm->eviction);
|
table_mod__(oftable, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1132,7 +1132,7 @@ AT_KEYWORDS([ofp-print])
|
|||||||
AT_CHECK([ovs-ofctl ofp-print "\
|
AT_CHECK([ovs-ofctl ofp-print "\
|
||||||
05 11 00 10 00 00 00 02 02 00 00 00 00 00 00 00 \
|
05 11 00 10 00 00 00 02 02 00 00 00 00 00 00 00 \
|
||||||
" 3], [0], [dnl
|
" 3], [0], [dnl
|
||||||
OFPT_TABLE_MOD (OF1.4) (xid=0x2): table_id=2, eviction=off
|
OFPT_TABLE_MOD (OF1.4) (xid=0x2): table_id=2, eviction=off, vacancy=off
|
||||||
])
|
])
|
||||||
AT_CLEANUP
|
AT_CLEANUP
|
||||||
|
|
||||||
|
@ -1936,6 +1936,13 @@ mv expout orig-expout
|
|||||||
sed -e '2s/eviction=off/eviction=on/' <orig-expout > expout
|
sed -e '2s/eviction=off/eviction=on/' <orig-expout > expout
|
||||||
AT_CHECK([ovs-ofctl -O OpenFlow14 dump-table-desc br0 | sed '/^$/d
|
AT_CHECK([ovs-ofctl -O OpenFlow14 dump-table-desc br0 | sed '/^$/d
|
||||||
/^OFPST_TABLE_DESC/d'], [0], [expout])
|
/^OFPST_TABLE_DESC/d'], [0], [expout])
|
||||||
|
|
||||||
|
AT_CHECK([ovs-ofctl -O Openflow14 mod-table br0 0 vacancy:20,80])
|
||||||
|
# Check that the configuration was updated.
|
||||||
|
mv expout orig-expout
|
||||||
|
sed -e '3s/vacancy=off/vacancy=on vacancy_down=20% vacancy_up=80% vacancy=0%/' <orig-expout > expout
|
||||||
|
AT_CHECK([ovs-ofctl -O OpenFlow14 dump-table-desc br0 | sed '/^$/d
|
||||||
|
/^OFPST_TABLE_DESC/d'], [0], [expout])
|
||||||
OVS_VSWITCHD_STOP
|
OVS_VSWITCHD_STOP
|
||||||
AT_CLEANUP
|
AT_CLEANUP
|
||||||
|
|
||||||
|
@ -96,8 +96,13 @@ algorithm described for the \fBFlow_Table\fR table in
|
|||||||
\fBovs-vswitchd.conf.db\fR(5).
|
\fBovs-vswitchd.conf.db\fR(5).
|
||||||
.IP \fBnoevict\fR
|
.IP \fBnoevict\fR
|
||||||
Refuse to add the new flow. (Eviction might still be enabled through
|
Refuse to add the new flow. (Eviction might still be enabled through
|
||||||
the \fBoverflow_policy\fR oclumn in the \fBFlow_Table\fR table
|
the \fBoverflow_policy\fR column in the \fBFlow_Table\fR table
|
||||||
documented in \fBovs-vswitchd.conf.db\fR(5).)
|
documented in \fBovs-vswitchd.conf.db\fR(5).)
|
||||||
|
.IP \fBvacancy:\fIlow\fB,\fIhigh\fR
|
||||||
|
Enables sending vacancy events to controllers using \fBTABLE_STATUS\fR
|
||||||
|
messages, based on percentage thresholds \fIlow\fR and \fIhigh\fR.
|
||||||
|
.IP \fBnovacancy\fR
|
||||||
|
Disables vacancy events.
|
||||||
.RE
|
.RE
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
|
@ -347,7 +347,7 @@ usage(void)
|
|||||||
" mod-port SWITCH IFACE ACT modify port behavior\n"
|
" mod-port SWITCH IFACE ACT modify port behavior\n"
|
||||||
" mod-table SWITCH MOD modify flow table behavior\n"
|
" mod-table SWITCH MOD modify flow table behavior\n"
|
||||||
" OF1.1/1.2 MOD: controller, continue, drop\n"
|
" OF1.1/1.2 MOD: controller, continue, drop\n"
|
||||||
" OF1.4+ MOD: evict, noevict\n"
|
" OF1.4+ MOD: evict, noevict, vacancy:low,high, novacancy\n"
|
||||||
" get-frags SWITCH print fragment handling behavior\n"
|
" get-frags SWITCH print fragment handling behavior\n"
|
||||||
" set-frags SWITCH FRAG_MODE set fragment handling behavior\n"
|
" set-frags SWITCH FRAG_MODE set fragment handling behavior\n"
|
||||||
" FRAG_MODE: normal, drop, reassemble, nx-match\n"
|
" FRAG_MODE: normal, drop, reassemble, nx-match\n"
|
||||||
@ -1934,6 +1934,70 @@ found:
|
|||||||
vconn_close(vconn);
|
vconn_close(vconn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This function uses OFPMP14_TABLE_DESC request to get the current
|
||||||
|
* table configuration from switch. The function then modifies
|
||||||
|
* only that table-config property, which has been requested. */
|
||||||
|
static void
|
||||||
|
fetch_table_desc(struct vconn *vconn, struct ofputil_table_mod *tm,
|
||||||
|
struct ofputil_table_desc *td)
|
||||||
|
{
|
||||||
|
struct ofpbuf *request;
|
||||||
|
ovs_be32 send_xid;
|
||||||
|
bool done = false;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
request = ofputil_encode_table_desc_request(vconn_get_version(vconn));
|
||||||
|
send_xid = ((struct ofp_header *) request->data)->xid;
|
||||||
|
send_openflow_buffer(vconn, request);
|
||||||
|
while (!done) {
|
||||||
|
ovs_be32 recv_xid;
|
||||||
|
struct ofpbuf *reply;
|
||||||
|
|
||||||
|
run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed");
|
||||||
|
recv_xid = ((struct ofp_header *) reply->data)->xid;
|
||||||
|
if (send_xid == recv_xid) {
|
||||||
|
struct ofp_header *oh = reply->data;
|
||||||
|
enum ofptype type;
|
||||||
|
struct ofpbuf b;
|
||||||
|
uint16_t flags;
|
||||||
|
|
||||||
|
ofpbuf_use_const(&b, oh, ntohs(oh->length));
|
||||||
|
if (ofptype_pull(&type, &b)
|
||||||
|
|| type != OFPTYPE_TABLE_DESC_REPLY) {
|
||||||
|
ovs_fatal(0, "received bad reply: %s",
|
||||||
|
ofp_to_string(reply->data, reply->size,
|
||||||
|
verbosity + 1));
|
||||||
|
}
|
||||||
|
flags = ofpmp_flags(oh);
|
||||||
|
done = !(flags & OFPSF_REPLY_MORE);
|
||||||
|
if (found) {
|
||||||
|
/* We've already found the table desc consisting of current
|
||||||
|
* table configuration, but we need to drain the queue of
|
||||||
|
* any other replies for this request. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while (!ofputil_decode_table_desc(&b, td, oh->version)) {
|
||||||
|
if (td->table_id == tm->table_id) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
VLOG_DBG("received reply with xid %08"PRIx32" "
|
||||||
|
"!= expected %08"PRIx32, recv_xid, send_xid);
|
||||||
|
}
|
||||||
|
ofpbuf_delete(reply);
|
||||||
|
}
|
||||||
|
if (tm->eviction != OFPUTIL_TABLE_EVICTION_DEFAULT) {
|
||||||
|
tm->vacancy = td->vacancy;
|
||||||
|
tm->table_vacancy.vacancy_down = td->table_vacancy.vacancy_down;
|
||||||
|
tm->table_vacancy.vacancy_up = td->table_vacancy.vacancy_up;
|
||||||
|
} else if (tm->vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) {
|
||||||
|
tm->eviction = td->eviction;
|
||||||
|
tm->eviction_flags = td->eviction_flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ofctl_mod_table(struct ovs_cmdl_context *ctx)
|
ofctl_mod_table(struct ovs_cmdl_context *ctx)
|
||||||
{
|
{
|
||||||
@ -1941,6 +2005,7 @@ ofctl_mod_table(struct ovs_cmdl_context *ctx)
|
|||||||
struct ofputil_table_mod tm;
|
struct ofputil_table_mod tm;
|
||||||
struct vconn *vconn;
|
struct vconn *vconn;
|
||||||
char *error;
|
char *error;
|
||||||
|
int i;
|
||||||
|
|
||||||
error = parse_ofp_table_mod(&tm, ctx->argv[2], ctx->argv[3],
|
error = parse_ofp_table_mod(&tm, ctx->argv[2], ctx->argv[3],
|
||||||
&usable_versions);
|
&usable_versions);
|
||||||
@ -1951,15 +2016,36 @@ ofctl_mod_table(struct ovs_cmdl_context *ctx)
|
|||||||
uint32_t allowed_versions = get_allowed_ofp_versions();
|
uint32_t allowed_versions = get_allowed_ofp_versions();
|
||||||
if (!(allowed_versions & usable_versions)) {
|
if (!(allowed_versions & usable_versions)) {
|
||||||
struct ds versions = DS_EMPTY_INITIALIZER;
|
struct ds versions = DS_EMPTY_INITIALIZER;
|
||||||
ofputil_format_version_bitmap_names(&versions, allowed_versions);
|
ofputil_format_version_bitmap_names(&versions, usable_versions);
|
||||||
ovs_fatal(0, "table_mod '%s' requires one of the OpenFlow "
|
ovs_fatal(0, "table_mod '%s' requires one of the OpenFlow "
|
||||||
"versions %s but none is enabled (use -O)",
|
"versions %s",
|
||||||
ctx->argv[3], ds_cstr(&versions));
|
ctx->argv[3], ds_cstr(&versions));
|
||||||
}
|
}
|
||||||
mask_allowed_ofp_versions(usable_versions);
|
mask_allowed_ofp_versions(usable_versions);
|
||||||
|
|
||||||
enum ofputil_protocol protocol = open_vconn(ctx->argv[1], &vconn);
|
enum ofputil_protocol protocol = open_vconn(ctx->argv[1], &vconn);
|
||||||
transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
|
|
||||||
|
/* 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;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
|
||||||
|
}
|
||||||
vconn_close(vconn);
|
vconn_close(vconn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user