mirror of
https://github.com/openvswitch/ovs
synced 2025-10-19 14:37:21 +00:00
Currently fast-failover group does not support checking liveness of controller port (OFPP_CONTROLLER). However this feature can be useful for selecting alternate pipeline when controller connection itself is down for e.g. by using local DHCP server to reply for any DHCP request originating from VMs. This patch adds the support for watching controller port liveness in fast- failover group. Controller port is considered live when atleast one of-connection is alive. Example usage: ovs-ofctl add-group br-int 'group_id=1234,type=ff, bucket=watch_port:CONTROLLER,actions:<A>, bucket=watch_port:1,actions:<B> Signed-off-by: Vishal Deep Ajmera <vishal.deep.ajmera@ericsson.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
2391 lines
76 KiB
C
2391 lines
76 KiB
C
/*
|
||
* Copyright (c) 2008-2017, 2019 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-group.h"
|
||
#include <errno.h>
|
||
#include "byte-order.h"
|
||
#include "id-pool.h"
|
||
#include "nx-match.h"
|
||
#include "openvswitch/ofp-actions.h"
|
||
#include "openvswitch/dynamic-string.h"
|
||
#include "openvswitch/ofp-msgs.h"
|
||
#include "openvswitch/ofp-parse.h"
|
||
#include "openvswitch/ofp-port.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_group);
|
||
|
||
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
|
||
|
||
/* Stores the group id represented by 's' into '*group_idp'. 's' may be an
|
||
* integer or, for reserved group IDs, the standard OpenFlow name for the group
|
||
* (either "ANY" or "ALL").
|
||
*
|
||
* Returns true if successful, false if 's' is not a valid OpenFlow group ID or
|
||
* name. */
|
||
bool
|
||
ofputil_group_from_string(const char *s, uint32_t *group_idp)
|
||
{
|
||
if (!strcasecmp(s, "any")) {
|
||
*group_idp = OFPG_ANY;
|
||
} else if (!strcasecmp(s, "all")) {
|
||
*group_idp = OFPG_ALL;
|
||
} else if (!str_to_uint(s, 10, group_idp)) {
|
||
VLOG_WARN("%s is not a valid group ID. (Valid group IDs are "
|
||
"32-bit nonnegative integers or the keywords ANY or "
|
||
"ALL.)", s);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/* Appends to 's' a string representation of the OpenFlow group ID 'group_id'.
|
||
* Most groups' string representation is just the number, but for special
|
||
* groups, e.g. OFPG_ALL, it is the name, e.g. "ALL". */
|
||
void
|
||
ofputil_format_group(uint32_t group_id, struct ds *s)
|
||
{
|
||
char name[MAX_GROUP_NAME_LEN];
|
||
|
||
ofputil_group_to_string(group_id, name, sizeof name);
|
||
ds_put_cstr(s, name);
|
||
}
|
||
|
||
|
||
/* Puts in the 'bufsize' byte in 'namebuf' a null-terminated string
|
||
* representation of OpenFlow group ID 'group_id'. Most group are represented
|
||
* as just their number, but special groups, e.g. OFPG_ALL, are represented
|
||
* by name, e.g. "ALL". */
|
||
void
|
||
ofputil_group_to_string(uint32_t group_id,
|
||
char namebuf[MAX_GROUP_NAME_LEN + 1], size_t bufsize)
|
||
{
|
||
switch (group_id) {
|
||
case OFPG_ALL:
|
||
ovs_strlcpy(namebuf, "ALL", bufsize);
|
||
break;
|
||
|
||
case OFPG_ANY:
|
||
ovs_strlcpy(namebuf, "ANY", bufsize);
|
||
break;
|
||
|
||
default:
|
||
snprintf(namebuf, bufsize, "%"PRIu32, group_id);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Frees all of the "struct ofputil_bucket"s in the 'buckets' list. */
|
||
void
|
||
ofputil_bucket_list_destroy(struct ovs_list *buckets)
|
||
{
|
||
struct ofputil_bucket *bucket;
|
||
|
||
LIST_FOR_EACH_POP (bucket, list_node, buckets) {
|
||
free(bucket->ofpacts);
|
||
free(bucket);
|
||
}
|
||
}
|
||
|
||
/* Clones 'bucket' and its ofpacts data */
|
||
static struct ofputil_bucket *
|
||
ofputil_bucket_clone_data(const struct ofputil_bucket *bucket)
|
||
{
|
||
struct ofputil_bucket *new;
|
||
|
||
new = xmemdup(bucket, sizeof *bucket);
|
||
new->ofpacts = xmemdup(bucket->ofpacts, bucket->ofpacts_len);
|
||
|
||
return new;
|
||
}
|
||
|
||
/* Clones each of the buckets in the list 'src' appending them
|
||
* in turn to 'dest' which should be an initialised list.
|
||
* An exception is that if the pointer value of a bucket in 'src'
|
||
* matches 'skip' then it is not cloned or appended to 'dest'.
|
||
* This allows all of 'src' or 'all of 'src' except 'skip' to
|
||
* be cloned and appended to 'dest'. */
|
||
void
|
||
ofputil_bucket_clone_list(struct ovs_list *dest, const struct ovs_list *src,
|
||
const struct ofputil_bucket *skip)
|
||
{
|
||
struct ofputil_bucket *bucket;
|
||
|
||
LIST_FOR_EACH (bucket, list_node, src) {
|
||
struct ofputil_bucket *new_bucket;
|
||
|
||
if (bucket == skip) {
|
||
continue;
|
||
}
|
||
|
||
new_bucket = ofputil_bucket_clone_data(bucket);
|
||
ovs_list_push_back(dest, &new_bucket->list_node);
|
||
}
|
||
}
|
||
|
||
/* Find a bucket in the list 'buckets' whose bucket id is 'bucket_id'
|
||
* Returns the first bucket found or NULL if no buckets are found. */
|
||
struct ofputil_bucket *
|
||
ofputil_bucket_find(const struct ovs_list *buckets, uint32_t bucket_id)
|
||
{
|
||
struct ofputil_bucket *bucket;
|
||
|
||
if (bucket_id > OFPG15_BUCKET_MAX) {
|
||
return NULL;
|
||
}
|
||
|
||
LIST_FOR_EACH (bucket, list_node, buckets) {
|
||
if (bucket->bucket_id == bucket_id) {
|
||
return bucket;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* Returns true if more than one bucket in the list 'buckets'
|
||
* have the same bucket id. Returns false otherwise. */
|
||
bool
|
||
ofputil_bucket_check_duplicate_id(const struct ovs_list *buckets)
|
||
{
|
||
struct ofputil_bucket *i, *j;
|
||
|
||
LIST_FOR_EACH (i, list_node, buckets) {
|
||
LIST_FOR_EACH_REVERSE (j, list_node, buckets) {
|
||
if (i == j) {
|
||
break;
|
||
}
|
||
if (i->bucket_id == j->bucket_id) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Returns the bucket at the front of the list 'buckets'.
|
||
* Undefined if 'buckets is empty. */
|
||
struct ofputil_bucket *
|
||
ofputil_bucket_list_front(const struct ovs_list *buckets)
|
||
{
|
||
static struct ofputil_bucket *bucket;
|
||
|
||
ASSIGN_CONTAINER(bucket, ovs_list_front(buckets), list_node);
|
||
|
||
return bucket;
|
||
}
|
||
|
||
/* Returns the bucket at the back of the list 'buckets'.
|
||
* Undefined if 'buckets is empty. */
|
||
struct ofputil_bucket *
|
||
ofputil_bucket_list_back(const struct ovs_list *buckets)
|
||
{
|
||
static struct ofputil_bucket *bucket;
|
||
|
||
ASSIGN_CONTAINER(bucket, ovs_list_back(buckets), list_node);
|
||
|
||
return bucket;
|
||
}
|
||
|
||
/* Returns an OpenFlow group stats request for OpenFlow version 'ofp_version',
|
||
* that requests stats for group 'group_id'. (Use OFPG_ALL to request stats
|
||
* for all groups.)
|
||
*
|
||
* Group statistics include packet and byte counts for each group. */
|
||
struct ofpbuf *
|
||
ofputil_encode_group_stats_request(enum ofp_version ofp_version,
|
||
uint32_t group_id)
|
||
{
|
||
struct ofpbuf *msg = ofpraw_alloc((ofp_version == OFP10_VERSION
|
||
? OFPRAW_NXST_GROUP_REQUEST
|
||
: OFPRAW_OFPST11_GROUP_REQUEST),
|
||
ofp_version, 0);
|
||
struct ofp11_group_stats_request *req = ofpbuf_put_zeros(msg, sizeof *req);
|
||
req->group_id = htonl(group_id);
|
||
|
||
return msg;
|
||
}
|
||
|
||
void
|
||
ofputil_uninit_group_desc(struct ofputil_group_desc *gd)
|
||
{
|
||
ofputil_bucket_list_destroy(&gd->buckets);
|
||
ofputil_group_properties_destroy(&gd->props);
|
||
}
|
||
|
||
/* Decodes the OpenFlow group description request in 'oh', returning the group
|
||
* whose description is requested, or OFPG_ALL if stats for all groups was
|
||
* requested. */
|
||
uint32_t
|
||
ofputil_decode_group_desc_request(const struct ofp_header *oh)
|
||
{
|
||
struct ofpbuf request = ofpbuf_const_initializer(oh, ntohs(oh->length));
|
||
enum ofpraw raw = ofpraw_pull_assert(&request);
|
||
if (raw == OFPRAW_OFPST11_GROUP_DESC_REQUEST) {
|
||
return OFPG_ALL;
|
||
} else if (raw == OFPRAW_NXST_GROUP_DESC_REQUEST ||
|
||
raw == OFPRAW_OFPST15_GROUP_DESC_REQUEST) {
|
||
ovs_be32 *group_id = ofpbuf_pull(&request, sizeof *group_id);
|
||
return ntohl(*group_id);
|
||
} else {
|
||
OVS_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
/* Returns an OpenFlow group description request for OpenFlow version
|
||
* 'ofp_version', that requests stats for group 'group_id'. Use OFPG_ALL to
|
||
* request stats for all groups (OpenFlow 1.4 and earlier always request all
|
||
* groups).
|
||
*
|
||
* Group descriptions include the bucket and action configuration for each
|
||
* group. */
|
||
struct ofpbuf *
|
||
ofputil_encode_group_desc_request(enum ofp_version ofp_version,
|
||
uint32_t group_id)
|
||
{
|
||
struct ofpbuf *request;
|
||
|
||
switch (ofp_version) {
|
||
case OFP11_VERSION:
|
||
case OFP12_VERSION:
|
||
case OFP13_VERSION:
|
||
case OFP14_VERSION:
|
||
request = ofpraw_alloc(OFPRAW_OFPST11_GROUP_DESC_REQUEST,
|
||
ofp_version, 0);
|
||
break;
|
||
case OFP10_VERSION:
|
||
case OFP15_VERSION: {
|
||
struct ofp15_group_desc_request *req;
|
||
request = ofpraw_alloc((ofp_version == OFP10_VERSION
|
||
? OFPRAW_NXST_GROUP_DESC_REQUEST
|
||
: OFPRAW_OFPST15_GROUP_DESC_REQUEST),
|
||
ofp_version, 0);
|
||
req = ofpbuf_put_zeros(request, sizeof *req);
|
||
req->group_id = htonl(group_id);
|
||
break;
|
||
}
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
|
||
return request;
|
||
}
|
||
|
||
|
||
enum ofperr
|
||
ofputil_group_desc_request_format(struct ds *string,
|
||
const struct ofp_header *oh)
|
||
{
|
||
uint32_t group_id = ofputil_decode_group_desc_request(oh);
|
||
ds_put_cstr(string, " group_id=");
|
||
ofputil_format_group(group_id, string);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
ofputil_group_bucket_counters_to_ofp11(const struct ofputil_group_stats *gs,
|
||
struct ofp11_bucket_counter bucket_cnts[])
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < gs->n_buckets; i++) {
|
||
bucket_cnts[i].packet_count = htonll(gs->bucket_stats[i].packet_count);
|
||
bucket_cnts[i].byte_count = htonll(gs->bucket_stats[i].byte_count);
|
||
}
|
||
}
|
||
|
||
static void
|
||
ofputil_group_stats_to_ofp11(const struct ofputil_group_stats *gs,
|
||
struct ofp11_group_stats *gs11, size_t length,
|
||
struct ofp11_bucket_counter bucket_cnts[])
|
||
{
|
||
memset(gs11, 0, sizeof *gs11);
|
||
gs11->length = htons(length);
|
||
gs11->group_id = htonl(gs->group_id);
|
||
gs11->ref_count = htonl(gs->ref_count);
|
||
gs11->packet_count = htonll(gs->packet_count);
|
||
gs11->byte_count = htonll(gs->byte_count);
|
||
ofputil_group_bucket_counters_to_ofp11(gs, bucket_cnts);
|
||
}
|
||
|
||
static void
|
||
ofputil_group_stats_to_ofp13(const struct ofputil_group_stats *gs,
|
||
struct ofp13_group_stats *gs13, size_t length,
|
||
struct ofp11_bucket_counter bucket_cnts[])
|
||
{
|
||
ofputil_group_stats_to_ofp11(gs, &gs13->gs, length, bucket_cnts);
|
||
gs13->duration_sec = htonl(gs->duration_sec);
|
||
gs13->duration_nsec = htonl(gs->duration_nsec);
|
||
|
||
}
|
||
|
||
/* Encodes 'gs' properly for the format of the list of group statistics
|
||
* replies already begun in 'replies' and appends it to the list. 'replies'
|
||
* must have originally been initialized with ofpmp_init(). */
|
||
void
|
||
ofputil_append_group_stats(struct ovs_list *replies,
|
||
const struct ofputil_group_stats *gs)
|
||
{
|
||
size_t bucket_counter_size;
|
||
struct ofp11_bucket_counter *bucket_counters;
|
||
size_t length;
|
||
|
||
bucket_counter_size = gs->n_buckets * sizeof(struct ofp11_bucket_counter);
|
||
|
||
switch (ofpmp_version(replies)) {
|
||
case OFP11_VERSION:
|
||
case OFP12_VERSION:{
|
||
struct ofp11_group_stats *gs11;
|
||
|
||
length = sizeof *gs11 + bucket_counter_size;
|
||
gs11 = ofpmp_append(replies, length);
|
||
bucket_counters = (struct ofp11_bucket_counter *)(gs11 + 1);
|
||
ofputil_group_stats_to_ofp11(gs, gs11, length, bucket_counters);
|
||
break;
|
||
}
|
||
|
||
case OFP10_VERSION:
|
||
case OFP13_VERSION:
|
||
case OFP14_VERSION:
|
||
case OFP15_VERSION: {
|
||
struct ofp13_group_stats *gs13;
|
||
|
||
length = sizeof *gs13 + bucket_counter_size;
|
||
gs13 = ofpmp_append(replies, length);
|
||
bucket_counters = (struct ofp11_bucket_counter *)(gs13 + 1);
|
||
ofputil_group_stats_to_ofp13(gs, gs13, length, bucket_counters);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
/* Returns an OpenFlow group features request for OpenFlow version
|
||
* 'ofp_version'. */
|
||
struct ofpbuf *
|
||
ofputil_encode_group_features_request(enum ofp_version ofp_version)
|
||
{
|
||
return ofpraw_alloc((ofp_version < OFP12_VERSION
|
||
? OFPRAW_NXST_GROUP_FEATURES_REQUEST
|
||
: OFPRAW_OFPST12_GROUP_FEATURES_REQUEST),
|
||
ofp_version, 0);
|
||
}
|
||
|
||
/* Returns a OpenFlow message that encodes 'features' properly as a reply to
|
||
* group features request 'request'. */
|
||
struct ofpbuf *
|
||
ofputil_encode_group_features_reply(
|
||
const struct ofputil_group_features *features,
|
||
const struct ofp_header *request)
|
||
{
|
||
struct ofpbuf *reply = ofpraw_alloc_stats_reply(request, 0);
|
||
struct ofp12_group_features_stats *ogf
|
||
= ofpbuf_put_zeros(reply, sizeof *ogf);
|
||
ogf->types = htonl(features->types);
|
||
ogf->capabilities = htonl(features->capabilities);
|
||
for (int i = 0; i < OFPGT12_N_TYPES; i++) {
|
||
ogf->max_groups[i] = htonl(features->max_groups[i]);
|
||
ogf->actions[i] = ofpact_bitmap_to_openflow(features->ofpacts[i],
|
||
request->version);
|
||
}
|
||
|
||
return reply;
|
||
}
|
||
|
||
/* Decodes group features reply 'oh' into 'features'. */
|
||
void
|
||
ofputil_decode_group_features_reply(const struct ofp_header *oh,
|
||
struct ofputil_group_features *features)
|
||
{
|
||
const struct ofp12_group_features_stats *ogf = ofpmsg_body(oh);
|
||
int i;
|
||
|
||
features->types = ntohl(ogf->types);
|
||
features->capabilities = ntohl(ogf->capabilities);
|
||
for (i = 0; i < OFPGT12_N_TYPES; i++) {
|
||
features->max_groups[i] = ntohl(ogf->max_groups[i]);
|
||
features->ofpacts[i] = ofpact_bitmap_from_openflow(
|
||
ogf->actions[i], oh->version);
|
||
}
|
||
}
|
||
|
||
static const char *
|
||
group_type_to_string(enum ofp11_group_type type)
|
||
{
|
||
switch (type) {
|
||
case OFPGT11_ALL: return "all";
|
||
case OFPGT11_SELECT: return "select";
|
||
case OFPGT11_INDIRECT: return "indirect";
|
||
case OFPGT11_FF: return "fast failover";
|
||
default: OVS_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
enum ofperr
|
||
ofputil_group_features_format(struct ds *string, const struct ofp_header *oh)
|
||
{
|
||
struct ofputil_group_features features;
|
||
int i;
|
||
|
||
ofputil_decode_group_features_reply(oh, &features);
|
||
|
||
ds_put_format(string, "\n Group table:\n");
|
||
ds_put_format(string, " Types: 0x%"PRIx32"\n", features.types);
|
||
ds_put_format(string, " Capabilities: 0x%"PRIx32"\n",
|
||
features.capabilities);
|
||
|
||
for (i = 0; i < OFPGT12_N_TYPES; i++) {
|
||
if (features.types & (1u << i)) {
|
||
ds_put_format(string, " %s group:\n", group_type_to_string(i));
|
||
ds_put_format(string, " max_groups=%#"PRIx32"\n",
|
||
features.max_groups[i]);
|
||
ds_put_format(string, " actions: ");
|
||
ofpact_bitmap_format(features.ofpacts[i], string);
|
||
ds_put_char(string, '\n');
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Parse a group status request message into a 32 bit OpenFlow 1.1
|
||
* group ID and stores the latter in '*group_id'.
|
||
* Returns 0 if successful, otherwise an OFPERR_* number. */
|
||
enum ofperr
|
||
ofputil_decode_group_stats_request(const struct ofp_header *request,
|
||
uint32_t *group_id)
|
||
{
|
||
const struct ofp11_group_stats_request *gsr11 = ofpmsg_body(request);
|
||
*group_id = ntohl(gsr11->group_id);
|
||
return 0;
|
||
}
|
||
|
||
/* Converts a group stats reply in 'msg' into an abstract ofputil_group_stats
|
||
* in 'gs'. Assigns freshly allocated memory to gs->bucket_stats for the
|
||
* caller to eventually free.
|
||
*
|
||
* Multiple group stats replies can be packed into a single OpenFlow message.
|
||
* Calling this function multiple times for a single 'msg' iterates through the
|
||
* replies. The caller must initially leave 'msg''s layer pointers null and
|
||
* not modify them between calls.
|
||
*
|
||
* Returns 0 if successful, EOF if no replies were left in this 'msg',
|
||
* otherwise a positive errno value. */
|
||
int
|
||
ofputil_decode_group_stats_reply(struct ofpbuf *msg,
|
||
struct ofputil_group_stats *gs)
|
||
{
|
||
struct ofp11_bucket_counter *obc;
|
||
struct ofp11_group_stats *ogs11;
|
||
enum ofpraw raw;
|
||
enum ofperr error;
|
||
size_t base_len;
|
||
size_t length;
|
||
size_t i;
|
||
|
||
gs->bucket_stats = NULL;
|
||
error = (msg->header ? ofpraw_decode(&raw, msg->header)
|
||
: ofpraw_pull(&raw, msg));
|
||
if (error) {
|
||
return error;
|
||
}
|
||
|
||
if (!msg->size) {
|
||
return EOF;
|
||
}
|
||
|
||
if (raw == OFPRAW_OFPST11_GROUP_REPLY) {
|
||
base_len = sizeof *ogs11;
|
||
ogs11 = ofpbuf_try_pull(msg, sizeof *ogs11);
|
||
gs->duration_sec = gs->duration_nsec = UINT32_MAX;
|
||
} else if (raw == OFPRAW_NXST_GROUP_REPLY ||
|
||
raw == OFPRAW_OFPST13_GROUP_REPLY) {
|
||
struct ofp13_group_stats *ogs13;
|
||
|
||
base_len = sizeof *ogs13;
|
||
ogs13 = ofpbuf_try_pull(msg, sizeof *ogs13);
|
||
if (ogs13) {
|
||
ogs11 = &ogs13->gs;
|
||
gs->duration_sec = ntohl(ogs13->duration_sec);
|
||
gs->duration_nsec = ntohl(ogs13->duration_nsec);
|
||
} else {
|
||
ogs11 = NULL;
|
||
}
|
||
} else {
|
||
OVS_NOT_REACHED();
|
||
}
|
||
|
||
if (!ogs11) {
|
||
VLOG_WARN_RL(&rl, "%s reply has %"PRIu32" leftover bytes at end",
|
||
ofpraw_get_name(raw), msg->size);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
length = ntohs(ogs11->length);
|
||
if (length < sizeof base_len) {
|
||
VLOG_WARN_RL(&rl, "%s reply claims invalid length %"PRIuSIZE,
|
||
ofpraw_get_name(raw), length);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
|
||
gs->group_id = ntohl(ogs11->group_id);
|
||
gs->ref_count = ntohl(ogs11->ref_count);
|
||
gs->packet_count = ntohll(ogs11->packet_count);
|
||
gs->byte_count = ntohll(ogs11->byte_count);
|
||
|
||
gs->n_buckets = (length - base_len) / sizeof *obc;
|
||
obc = ofpbuf_try_pull(msg, gs->n_buckets * sizeof *obc);
|
||
if (!obc) {
|
||
VLOG_WARN_RL(&rl, "%s reply has %"PRIu32" leftover bytes at end",
|
||
ofpraw_get_name(raw), msg->size);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
|
||
gs->bucket_stats = xmalloc(gs->n_buckets * sizeof *gs->bucket_stats);
|
||
for (i = 0; i < gs->n_buckets; i++) {
|
||
gs->bucket_stats[i].packet_count = ntohll(obc[i].packet_count);
|
||
gs->bucket_stats[i].byte_count = ntohll(obc[i].byte_count);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
enum ofperr
|
||
ofputil_group_stats_request_format(struct ds *string,
|
||
const struct ofp_header *oh)
|
||
{
|
||
enum ofperr error;
|
||
uint32_t group_id;
|
||
|
||
error = ofputil_decode_group_stats_request(oh, &group_id);
|
||
if (error) {
|
||
return error;
|
||
}
|
||
|
||
ds_put_cstr(string, " group_id=");
|
||
ofputil_format_group(group_id, string);
|
||
return 0;
|
||
}
|
||
|
||
enum ofperr
|
||
ofputil_group_stats_format(struct ds *s, const struct ofp_header *oh)
|
||
{
|
||
struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
|
||
for (;;) {
|
||
struct ofputil_group_stats gs;
|
||
int retval;
|
||
|
||
retval = ofputil_decode_group_stats_reply(&b, &gs);
|
||
if (retval) {
|
||
if (retval != EOF) {
|
||
ds_put_cstr(s, " ***parse error***");
|
||
return retval;
|
||
}
|
||
break;
|
||
}
|
||
|
||
ds_put_char(s, '\n');
|
||
|
||
ds_put_char(s, ' ');
|
||
ds_put_format(s, "group_id=%"PRIu32",", gs.group_id);
|
||
|
||
if (gs.duration_sec != UINT32_MAX) {
|
||
ds_put_cstr(s, "duration=");
|
||
ofp_print_duration(s, gs.duration_sec, gs.duration_nsec);
|
||
ds_put_char(s, ',');
|
||
}
|
||
ds_put_format(s, "ref_count=%"PRIu32",", gs.ref_count);
|
||
ds_put_format(s, "packet_count=%"PRIu64",", gs.packet_count);
|
||
ds_put_format(s, "byte_count=%"PRIu64"", gs.byte_count);
|
||
|
||
for (uint32_t bucket_i = 0; bucket_i < gs.n_buckets; bucket_i++) {
|
||
if (gs.bucket_stats[bucket_i].packet_count != UINT64_MAX) {
|
||
ds_put_format(s, ",bucket%"PRIu32":", bucket_i);
|
||
ds_put_format(s, "packet_count=%"PRIu64",", gs.bucket_stats[bucket_i].packet_count);
|
||
ds_put_format(s, "byte_count=%"PRIu64"", gs.bucket_stats[bucket_i].byte_count);
|
||
}
|
||
}
|
||
|
||
free(gs.bucket_stats);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static char * OVS_WARN_UNUSED_RESULT
|
||
parse_bucket_str(struct ofputil_bucket *bucket, char *str_,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map,
|
||
uint8_t group_type, enum ofputil_protocol *usable_protocols)
|
||
{
|
||
char *pos, *key, *value;
|
||
struct ofpbuf ofpacts;
|
||
struct ds actions;
|
||
char *error;
|
||
|
||
bucket->weight = group_type == OFPGT11_SELECT ? 1 : 0;
|
||
bucket->bucket_id = OFPG15_BUCKET_ALL;
|
||
bucket->watch_port = OFPP_ANY;
|
||
bucket->watch_group = OFPG_ANY;
|
||
|
||
ds_init(&actions);
|
||
|
||
pos = str_;
|
||
error = NULL;
|
||
while (ofputil_parse_key_value(&pos, &key, &value)) {
|
||
if (!strcasecmp(key, "weight")) {
|
||
error = str_to_u16(value, "weight", &bucket->weight);
|
||
} else if (!strcasecmp(key, "watch_port")) {
|
||
if (!ofputil_port_from_string(value, port_map, &bucket->watch_port)
|
||
|| (ofp_to_u16(bucket->watch_port) >= ofp_to_u16(OFPP_MAX)
|
||
&& bucket->watch_port != OFPP_ANY
|
||
&& bucket->watch_port != OFPP_CONTROLLER)) {
|
||
error = xasprintf("%s: invalid watch_port", value);
|
||
}
|
||
} else if (!strcasecmp(key, "watch_group")) {
|
||
error = str_to_u32(value, &bucket->watch_group);
|
||
if (!error && bucket->watch_group > OFPG_MAX) {
|
||
error = xasprintf("invalid watch_group id %"PRIu32,
|
||
bucket->watch_group);
|
||
}
|
||
} else if (!strcasecmp(key, "bucket_id")) {
|
||
error = str_to_u32(value, &bucket->bucket_id);
|
||
if (!error && bucket->bucket_id > OFPG15_BUCKET_MAX) {
|
||
error = xasprintf("invalid bucket_id id %"PRIu32,
|
||
bucket->bucket_id);
|
||
}
|
||
*usable_protocols &= OFPUTIL_P_OF10_ANY | OFPUTIL_P_OF15_UP;
|
||
} else if (!strcasecmp(key, "action") || !strcasecmp(key, "actions")) {
|
||
ds_put_format(&actions, "%s,", value);
|
||
} else {
|
||
ds_put_format(&actions, "%s(%s),", key, value);
|
||
}
|
||
|
||
if (error) {
|
||
ds_destroy(&actions);
|
||
return error;
|
||
}
|
||
}
|
||
|
||
if (!actions.length) {
|
||
return xstrdup("bucket must specify actions");
|
||
}
|
||
if (group_type == OFPGT11_FF && !ofputil_bucket_has_liveness(bucket)) {
|
||
return xstrdup("fast failover bucket requires watch_port or "
|
||
"watch_group");
|
||
}
|
||
ds_chomp(&actions, ',');
|
||
|
||
ofpbuf_init(&ofpacts, 0);
|
||
struct ofpact_parse_params pp = {
|
||
.port_map = port_map,
|
||
.table_map = table_map,
|
||
.ofpacts = &ofpacts,
|
||
.usable_protocols = usable_protocols,
|
||
};
|
||
error = ofpacts_parse_actions(ds_cstr(&actions), &pp);
|
||
ds_destroy(&actions);
|
||
if (error) {
|
||
ofpbuf_uninit(&ofpacts);
|
||
return error;
|
||
}
|
||
bucket->ofpacts = ofpacts.data;
|
||
bucket->ofpacts_len = ofpacts.size;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static char * OVS_WARN_UNUSED_RESULT
|
||
parse_select_group_field(char *s, const struct ofputil_port_map *port_map,
|
||
struct field_array *fa,
|
||
enum ofputil_protocol *usable_protocols)
|
||
{
|
||
char *name, *value_str;
|
||
|
||
while (ofputil_parse_key_value(&s, &name, &value_str)) {
|
||
const struct mf_field *mf = mf_from_name(name);
|
||
|
||
if (mf) {
|
||
char *error;
|
||
union mf_value value;
|
||
|
||
if (bitmap_is_set(fa->used.bm, mf->id)) {
|
||
return xasprintf("%s: duplicate field", name);
|
||
}
|
||
|
||
if (*value_str) {
|
||
error = mf_parse_value(mf, value_str, port_map, &value);
|
||
if (error) {
|
||
return error;
|
||
}
|
||
|
||
/* The mask cannot be all-zeros */
|
||
if (!mf_is_tun_metadata(mf) &&
|
||
is_all_zeros(&value, mf->n_bytes)) {
|
||
return xasprintf("%s: values are wildcards here "
|
||
"and must not be all-zeros", s);
|
||
}
|
||
|
||
/* The values parsed are masks for fields used
|
||
* by the selection method */
|
||
if (!mf_is_mask_valid(mf, &value)) {
|
||
return xasprintf("%s: invalid mask for field %s",
|
||
value_str, mf->name);
|
||
}
|
||
} else {
|
||
memset(&value, 0xff, mf->n_bytes);
|
||
}
|
||
|
||
field_array_set(mf->id, &value, fa);
|
||
|
||
if (is_all_ones(&value, mf->n_bytes)) {
|
||
*usable_protocols &= mf->usable_protocols_exact;
|
||
} else if (mf->usable_protocols_bitwise == mf->usable_protocols_cidr
|
||
|| ip_is_cidr(value.be32)) {
|
||
*usable_protocols &= mf->usable_protocols_cidr;
|
||
} else {
|
||
*usable_protocols &= mf->usable_protocols_bitwise;
|
||
}
|
||
} else {
|
||
return xasprintf("%s: unknown field %s", s, name);
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static char * OVS_WARN_UNUSED_RESULT
|
||
parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, int command,
|
||
char *string,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map,
|
||
enum ofputil_protocol *usable_protocols)
|
||
{
|
||
enum {
|
||
F_GROUP_TYPE = 1 << 0,
|
||
F_BUCKETS = 1 << 1,
|
||
F_COMMAND_BUCKET_ID = 1 << 2,
|
||
F_COMMAND_BUCKET_ID_ALL = 1 << 3,
|
||
} fields;
|
||
bool had_type = false;
|
||
bool had_command_bucket_id = false;
|
||
struct ofputil_bucket *bucket;
|
||
char *error = NULL;
|
||
|
||
*usable_protocols = OFPUTIL_P_ANY;
|
||
|
||
if (command == -2) {
|
||
size_t len;
|
||
|
||
string += strspn(string, " \t\r\n"); /* Skip white space. */
|
||
len = strcspn(string, ", \t\r\n"); /* Get length of the first token. */
|
||
|
||
if (!strncmp(string, "add", len)) {
|
||
command = OFPGC11_ADD;
|
||
} else if (!strncmp(string, "delete", len)) {
|
||
command = OFPGC11_DELETE;
|
||
} else if (!strncmp(string, "modify", len)) {
|
||
command = OFPGC11_MODIFY;
|
||
} else if (!strncmp(string, "add_or_mod", len)) {
|
||
command = OFPGC11_ADD_OR_MOD;
|
||
} else if (!strncmp(string, "insert_bucket", len)) {
|
||
command = OFPGC15_INSERT_BUCKET;
|
||
} else if (!strncmp(string, "remove_bucket", len)) {
|
||
command = OFPGC15_REMOVE_BUCKET;
|
||
} else {
|
||
len = 0;
|
||
command = OFPGC11_ADD;
|
||
}
|
||
string += len;
|
||
}
|
||
|
||
switch (command) {
|
||
case OFPGC11_ADD:
|
||
fields = F_GROUP_TYPE | F_BUCKETS;
|
||
break;
|
||
|
||
case OFPGC11_DELETE:
|
||
fields = 0;
|
||
break;
|
||
|
||
case OFPGC11_MODIFY:
|
||
fields = F_GROUP_TYPE | F_BUCKETS;
|
||
break;
|
||
|
||
case OFPGC11_ADD_OR_MOD:
|
||
fields = F_GROUP_TYPE | F_BUCKETS;
|
||
break;
|
||
|
||
case OFPGC15_INSERT_BUCKET:
|
||
fields = F_BUCKETS | F_COMMAND_BUCKET_ID;
|
||
*usable_protocols &= OFPUTIL_P_OF10_ANY | OFPUTIL_P_OF15_UP;
|
||
break;
|
||
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
fields = F_COMMAND_BUCKET_ID | F_COMMAND_BUCKET_ID_ALL;
|
||
*usable_protocols &= OFPUTIL_P_OF10_ANY | OFPUTIL_P_OF15_UP;
|
||
break;
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
|
||
memset(gm, 0, sizeof *gm);
|
||
gm->command = command;
|
||
gm->group_id = OFPG_ANY;
|
||
gm->command_bucket_id = OFPG15_BUCKET_ALL;
|
||
ovs_list_init(&gm->buckets);
|
||
if (command == OFPGC11_DELETE && string[0] == '\0') {
|
||
gm->group_id = OFPG_ALL;
|
||
return NULL;
|
||
}
|
||
|
||
/* Strip the buckets off the end of 'string', if there are any, saving a
|
||
* pointer for later. We want to parse the buckets last because the bucket
|
||
* type influences bucket defaults. */
|
||
char *bkt_str = strstr(string, "bucket=");
|
||
if (bkt_str) {
|
||
if (!(fields & F_BUCKETS)) {
|
||
error = xstrdup("bucket is not needed");
|
||
goto out;
|
||
}
|
||
*bkt_str = '\0';
|
||
}
|
||
|
||
/* Parse everything before the buckets. */
|
||
char *pos = string;
|
||
char *name, *value;
|
||
while (ofputil_parse_key_value(&pos, &name, &value)) {
|
||
if (!strcmp(name, "command_bucket_id")) {
|
||
if (!(fields & F_COMMAND_BUCKET_ID)) {
|
||
error = xstrdup("command bucket id is not needed");
|
||
goto out;
|
||
}
|
||
if (!strcmp(value, "all")) {
|
||
gm->command_bucket_id = OFPG15_BUCKET_ALL;
|
||
} else if (!strcmp(value, "first")) {
|
||
gm->command_bucket_id = OFPG15_BUCKET_FIRST;
|
||
} else if (!strcmp(value, "last")) {
|
||
gm->command_bucket_id = OFPG15_BUCKET_LAST;
|
||
} else {
|
||
error = str_to_u32(value, &gm->command_bucket_id);
|
||
if (error) {
|
||
goto out;
|
||
}
|
||
if (gm->command_bucket_id > OFPG15_BUCKET_MAX
|
||
&& (gm->command_bucket_id != OFPG15_BUCKET_FIRST
|
||
&& gm->command_bucket_id != OFPG15_BUCKET_LAST
|
||
&& gm->command_bucket_id != OFPG15_BUCKET_ALL)) {
|
||
error = xasprintf("invalid command bucket id %"PRIu32,
|
||
gm->command_bucket_id);
|
||
goto out;
|
||
}
|
||
}
|
||
if (gm->command_bucket_id == OFPG15_BUCKET_ALL
|
||
&& !(fields & F_COMMAND_BUCKET_ID_ALL)) {
|
||
error = xstrdup("command_bucket_id=all is not permitted");
|
||
goto out;
|
||
}
|
||
had_command_bucket_id = true;
|
||
} else if (!strcmp(name, "group_id")) {
|
||
if(!strcmp(value, "all")) {
|
||
gm->group_id = OFPG_ALL;
|
||
} else {
|
||
error = str_to_u32(value, &gm->group_id);
|
||
if (error) {
|
||
goto out;
|
||
}
|
||
if (gm->group_id != OFPG_ALL && gm->group_id > OFPG_MAX) {
|
||
error = xasprintf("invalid group id %"PRIu32,
|
||
gm->group_id);
|
||
goto out;
|
||
}
|
||
}
|
||
} else if (!strcmp(name, "type")){
|
||
if (!(fields & F_GROUP_TYPE)) {
|
||
error = xstrdup("type is not needed");
|
||
goto out;
|
||
}
|
||
if (!strcmp(value, "all")) {
|
||
gm->type = OFPGT11_ALL;
|
||
} else if (!strcmp(value, "select")) {
|
||
gm->type = OFPGT11_SELECT;
|
||
} else if (!strcmp(value, "indirect")) {
|
||
gm->type = OFPGT11_INDIRECT;
|
||
} else if (!strcmp(value, "ff") ||
|
||
!strcmp(value, "fast_failover")) {
|
||
gm->type = OFPGT11_FF;
|
||
} else {
|
||
error = xasprintf("invalid group type %s", value);
|
||
goto out;
|
||
}
|
||
had_type = true;
|
||
} else if (!strcmp(name, "selection_method")) {
|
||
if (!(fields & F_GROUP_TYPE)) {
|
||
error = xstrdup("selection method is not needed");
|
||
goto out;
|
||
}
|
||
if (strlen(value) >= NTR_MAX_SELECTION_METHOD_LEN) {
|
||
error = xasprintf("selection method is longer than %u"
|
||
" bytes long",
|
||
NTR_MAX_SELECTION_METHOD_LEN - 1);
|
||
goto out;
|
||
}
|
||
memset(gm->props.selection_method, '\0',
|
||
NTR_MAX_SELECTION_METHOD_LEN);
|
||
strcpy(gm->props.selection_method, value);
|
||
*usable_protocols &= OFPUTIL_P_OF10_ANY | OFPUTIL_P_OF15_UP;
|
||
} else if (!strcmp(name, "selection_method_param")) {
|
||
if (!(fields & F_GROUP_TYPE)) {
|
||
error = xstrdup("selection method param is not needed");
|
||
goto out;
|
||
}
|
||
error = str_to_u64(value, &gm->props.selection_method_param);
|
||
if (error) {
|
||
goto out;
|
||
}
|
||
*usable_protocols &= OFPUTIL_P_OF10_ANY | OFPUTIL_P_OF15_UP;
|
||
} else if (!strcmp(name, "fields")) {
|
||
if (!(fields & F_GROUP_TYPE)) {
|
||
error = xstrdup("fields are not needed");
|
||
goto out;
|
||
}
|
||
error = parse_select_group_field(value, port_map,
|
||
&gm->props.fields,
|
||
usable_protocols);
|
||
if (error) {
|
||
goto out;
|
||
}
|
||
*usable_protocols &= OFPUTIL_P_OF10_ANY | OFPUTIL_P_OF15_UP;
|
||
} else {
|
||
error = xasprintf("unknown keyword %s", name);
|
||
goto out;
|
||
}
|
||
}
|
||
if (gm->group_id == OFPG_ANY) {
|
||
error = xstrdup("must specify a group_id");
|
||
goto out;
|
||
}
|
||
if (fields & F_GROUP_TYPE && !had_type) {
|
||
error = xstrdup("must specify a type");
|
||
goto out;
|
||
}
|
||
|
||
/* Exclude fields for non "hash" selection method. */
|
||
if (strcmp(gm->props.selection_method, "hash") &&
|
||
gm->props.fields.values_size) {
|
||
error = xstrdup("fields may only be specified with "
|
||
"\"selection_method=hash\"");
|
||
goto out;
|
||
}
|
||
/* Exclude selection_method_param if no selection_method is given. */
|
||
if (gm->props.selection_method[0] == 0
|
||
&& gm->props.selection_method_param != 0) {
|
||
error = xstrdup("selection_method_param is only allowed with "
|
||
"\"selection_method\"");
|
||
goto out;
|
||
}
|
||
if (fields & F_COMMAND_BUCKET_ID) {
|
||
if (!(fields & F_COMMAND_BUCKET_ID_ALL || had_command_bucket_id)) {
|
||
error = xstrdup("must specify a command bucket id");
|
||
goto out;
|
||
}
|
||
} else if (had_command_bucket_id) {
|
||
error = xstrdup("command bucket id is not needed");
|
||
goto out;
|
||
}
|
||
|
||
/* Now parse the buckets, if any. */
|
||
while (bkt_str) {
|
||
char *next_bkt_str;
|
||
|
||
bkt_str = strchr(bkt_str + 1, '=');
|
||
if (!bkt_str) {
|
||
error = xstrdup("must specify bucket content");
|
||
goto out;
|
||
}
|
||
bkt_str++;
|
||
|
||
next_bkt_str = strstr(bkt_str, "bucket=");
|
||
if (next_bkt_str) {
|
||
*next_bkt_str = '\0';
|
||
}
|
||
|
||
bucket = xzalloc(sizeof(struct ofputil_bucket));
|
||
error = parse_bucket_str(bucket, bkt_str, port_map, table_map,
|
||
gm->type, usable_protocols);
|
||
if (error) {
|
||
free(bucket);
|
||
goto out;
|
||
}
|
||
ovs_list_push_back(&gm->buckets, &bucket->list_node);
|
||
|
||
if (gm->command != OFPGC15_INSERT_BUCKET
|
||
&& gm->type != OFPGT11_SELECT && bucket->weight) {
|
||
error = xstrdup("Only select groups can have bucket weights.");
|
||
goto out;
|
||
}
|
||
|
||
bkt_str = next_bkt_str;
|
||
}
|
||
if (gm->type == OFPGT11_INDIRECT && !ovs_list_is_short(&gm->buckets)) {
|
||
error = xstrdup("Indirect groups can have at most one bucket.");
|
||
goto out;
|
||
}
|
||
|
||
return NULL;
|
||
out:
|
||
ofputil_uninit_group_mod(gm);
|
||
return error;
|
||
}
|
||
|
||
/* If 'command' is given as -2, each line may start with a command name ("add",
|
||
* "modify", "add_or_mod", "delete", "insert_bucket", or "remove_bucket"). A
|
||
* missing command name is treated as "add".
|
||
*/
|
||
char * OVS_WARN_UNUSED_RESULT
|
||
parse_ofp_group_mod_str(struct ofputil_group_mod *gm, int command,
|
||
const char *str_,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map,
|
||
enum ofputil_protocol *usable_protocols)
|
||
{
|
||
char *string = xstrdup(str_);
|
||
char *error = parse_ofp_group_mod_str__(gm, command, string, port_map,
|
||
table_map, usable_protocols);
|
||
free(string);
|
||
return error;
|
||
}
|
||
|
||
/* If 'command' is given as -2, each line may start with a command name ("add",
|
||
* "modify", "add_or_mod", "delete", "insert_bucket", or "remove_bucket"). A
|
||
* missing command name is treated as "add".
|
||
*/
|
||
char * OVS_WARN_UNUSED_RESULT
|
||
parse_ofp_group_mod_file(const char *file_name,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map,
|
||
int command,
|
||
struct ofputil_group_mod **gms, size_t *n_gms,
|
||
enum ofputil_protocol *usable_protocols)
|
||
{
|
||
size_t allocated_gms;
|
||
int line_number;
|
||
FILE *stream;
|
||
struct ds s;
|
||
|
||
*gms = NULL;
|
||
*n_gms = 0;
|
||
|
||
stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
|
||
if (stream == NULL) {
|
||
return xasprintf("%s: open failed (%s)",
|
||
file_name, ovs_strerror(errno));
|
||
}
|
||
|
||
allocated_gms = *n_gms;
|
||
ds_init(&s);
|
||
line_number = 0;
|
||
*usable_protocols = OFPUTIL_P_ANY;
|
||
while (!ds_get_preprocessed_line(&s, stream, &line_number)) {
|
||
enum ofputil_protocol usable;
|
||
char *error;
|
||
|
||
if (*n_gms >= allocated_gms) {
|
||
struct ofputil_group_mod *new_gms;
|
||
size_t i;
|
||
|
||
new_gms = x2nrealloc(*gms, &allocated_gms, sizeof **gms);
|
||
for (i = 0; i < *n_gms; i++) {
|
||
ovs_list_moved(&new_gms[i].buckets, &(*gms)[i].buckets);
|
||
}
|
||
*gms = new_gms;
|
||
}
|
||
error = parse_ofp_group_mod_str(&(*gms)[*n_gms], command, ds_cstr(&s),
|
||
port_map, table_map, &usable);
|
||
if (error) {
|
||
size_t i;
|
||
|
||
for (i = 0; i < *n_gms; i++) {
|
||
ofputil_uninit_group_mod(&(*gms)[i]);
|
||
}
|
||
free(*gms);
|
||
*gms = NULL;
|
||
*n_gms = 0;
|
||
|
||
ds_destroy(&s);
|
||
if (stream != stdin) {
|
||
fclose(stream);
|
||
}
|
||
|
||
char *ret = xasprintf("%s:%d: %s", file_name, line_number, error);
|
||
free(error);
|
||
return ret;
|
||
}
|
||
*usable_protocols &= usable;
|
||
*n_gms += 1;
|
||
}
|
||
|
||
ds_destroy(&s);
|
||
if (stream != stdin) {
|
||
fclose(stream);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
ofputil_put_ofp11_bucket(const struct ofputil_bucket *bucket,
|
||
struct ofpbuf *openflow, enum ofp_version ofp_version)
|
||
{
|
||
struct ofp11_bucket *ob;
|
||
size_t start;
|
||
|
||
start = openflow->size;
|
||
ofpbuf_put_zeros(openflow, sizeof *ob);
|
||
ofpacts_put_openflow_actions(bucket->ofpacts, bucket->ofpacts_len,
|
||
openflow, ofp_version);
|
||
ob = ofpbuf_at_assert(openflow, start, sizeof *ob);
|
||
ob->len = htons(openflow->size - start);
|
||
ob->weight = htons(bucket->weight);
|
||
ob->watch_port = ofputil_port_to_ofp11(bucket->watch_port);
|
||
ob->watch_group = htonl(bucket->watch_group);
|
||
}
|
||
|
||
static void
|
||
ofputil_put_ofp15_bucket(const struct ofputil_bucket *bucket,
|
||
uint32_t bucket_id, enum ofp11_group_type group_type,
|
||
struct ofpbuf *openflow, enum ofp_version ofp_version)
|
||
{
|
||
struct ofp15_bucket *ob;
|
||
size_t start, actions_start, actions_len;
|
||
|
||
start = openflow->size;
|
||
ofpbuf_put_zeros(openflow, sizeof *ob);
|
||
|
||
actions_start = openflow->size;
|
||
ofpacts_put_openflow_actions(bucket->ofpacts, bucket->ofpacts_len,
|
||
openflow, ofp_version);
|
||
actions_len = openflow->size - actions_start;
|
||
|
||
if (group_type == OFPGT11_SELECT || bucket->weight) {
|
||
ofpprop_put_u16(openflow, OFPGBPT15_WEIGHT, bucket->weight);
|
||
}
|
||
if (bucket->watch_port != OFPP_ANY) {
|
||
ofpprop_put_be32(openflow, OFPGBPT15_WATCH_PORT,
|
||
ofputil_port_to_ofp11(bucket->watch_port));
|
||
}
|
||
if (bucket->watch_group != OFPG_ANY) {
|
||
ofpprop_put_u32(openflow, OFPGBPT15_WATCH_GROUP, bucket->watch_group);
|
||
}
|
||
|
||
ob = ofpbuf_at_assert(openflow, start, sizeof *ob);
|
||
ob->len = htons(openflow->size - start);
|
||
ob->action_array_len = htons(actions_len);
|
||
ob->bucket_id = htonl(bucket_id);
|
||
}
|
||
|
||
static void
|
||
ofputil_put_group_prop_ntr_selection_method(enum ofp_version ofp_version,
|
||
const struct ofputil_group_props *gp,
|
||
struct ofpbuf *openflow)
|
||
{
|
||
struct ntr_group_prop_selection_method *prop;
|
||
size_t start;
|
||
|
||
start = openflow->size;
|
||
ofpbuf_put_zeros(openflow, sizeof *prop);
|
||
oxm_put_field_array(openflow, &gp->fields, ofp_version);
|
||
prop = ofpbuf_at_assert(openflow, start, sizeof *prop);
|
||
prop->type = htons(OFPGPT15_EXPERIMENTER);
|
||
prop->experimenter = htonl(NTR_VENDOR_ID);
|
||
prop->exp_type = htonl(NTRT_SELECTION_METHOD);
|
||
strcpy(prop->selection_method, gp->selection_method);
|
||
prop->selection_method_param = htonll(gp->selection_method_param);
|
||
ofpprop_end(openflow, start);
|
||
}
|
||
|
||
static void
|
||
ofputil_append_ofp11_group_desc_reply(const struct ofputil_group_desc *gds,
|
||
const struct ovs_list *buckets,
|
||
struct ovs_list *replies,
|
||
enum ofp_version version)
|
||
{
|
||
struct ofpbuf *reply = ofpbuf_from_list(ovs_list_back(replies));
|
||
struct ofp11_group_desc_stats *ogds;
|
||
struct ofputil_bucket *bucket;
|
||
size_t start_ogds;
|
||
|
||
start_ogds = reply->size;
|
||
ofpbuf_put_zeros(reply, sizeof *ogds);
|
||
LIST_FOR_EACH (bucket, list_node, buckets) {
|
||
ofputil_put_ofp11_bucket(bucket, reply, version);
|
||
}
|
||
ogds = ofpbuf_at_assert(reply, start_ogds, sizeof *ogds);
|
||
ogds->length = htons(reply->size - start_ogds);
|
||
ogds->type = gds->type;
|
||
ogds->group_id = htonl(gds->group_id);
|
||
|
||
ofpmp_postappend(replies, start_ogds);
|
||
}
|
||
|
||
static void
|
||
ofputil_append_ofp15_group_desc_reply(const struct ofputil_group_desc *gds,
|
||
const struct ovs_list *buckets,
|
||
struct ovs_list *replies,
|
||
enum ofp_version version)
|
||
{
|
||
struct ofpbuf *reply = ofpbuf_from_list(ovs_list_back(replies));
|
||
struct ofp15_group_desc_stats *ogds;
|
||
struct ofputil_bucket *bucket;
|
||
size_t start_ogds, start_buckets;
|
||
|
||
start_ogds = reply->size;
|
||
ofpbuf_put_zeros(reply, sizeof *ogds);
|
||
start_buckets = reply->size;
|
||
LIST_FOR_EACH (bucket, list_node, buckets) {
|
||
ofputil_put_ofp15_bucket(bucket, bucket->bucket_id,
|
||
gds->type, reply, version);
|
||
}
|
||
ogds = ofpbuf_at_assert(reply, start_ogds, sizeof *ogds);
|
||
ogds->type = gds->type;
|
||
ogds->group_id = htonl(gds->group_id);
|
||
ogds->bucket_list_len = htons(reply->size - start_buckets);
|
||
|
||
/* Add group properties */
|
||
if (gds->props.selection_method[0]) {
|
||
ofputil_put_group_prop_ntr_selection_method(version, &gds->props,
|
||
reply);
|
||
}
|
||
ogds = ofpbuf_at_assert(reply, start_ogds, sizeof *ogds);
|
||
ogds->length = htons(reply->size - start_ogds);
|
||
|
||
ofpmp_postappend(replies, start_ogds);
|
||
}
|
||
|
||
/* Appends a group stats reply that contains the data in 'gds' to those already
|
||
* present in the list of ofpbufs in 'replies'. 'replies' should have been
|
||
* initialized with ofpmp_init(). */
|
||
void
|
||
ofputil_append_group_desc_reply(const struct ofputil_group_desc *gds,
|
||
const struct ovs_list *buckets,
|
||
struct ovs_list *replies)
|
||
{
|
||
enum ofp_version version = ofpmp_version(replies);
|
||
|
||
switch (version)
|
||
{
|
||
case OFP11_VERSION:
|
||
case OFP12_VERSION:
|
||
case OFP13_VERSION:
|
||
case OFP14_VERSION:
|
||
ofputil_append_ofp11_group_desc_reply(gds, buckets, replies, version);
|
||
break;
|
||
|
||
case OFP10_VERSION:
|
||
case OFP15_VERSION:
|
||
ofputil_append_ofp15_group_desc_reply(gds, buckets, replies, version);
|
||
break;
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
static enum ofperr
|
||
ofputil_pull_ofp11_buckets(struct ofpbuf *msg, size_t buckets_length,
|
||
enum ofp_version version, struct ovs_list *buckets)
|
||
{
|
||
struct ofp11_bucket *ob;
|
||
uint32_t bucket_id = 0;
|
||
|
||
ovs_list_init(buckets);
|
||
while (buckets_length > 0) {
|
||
struct ofputil_bucket *bucket;
|
||
struct ofpbuf ofpacts;
|
||
enum ofperr error;
|
||
size_t ob_len;
|
||
|
||
ob = (buckets_length >= sizeof *ob
|
||
? ofpbuf_try_pull(msg, sizeof *ob)
|
||
: NULL);
|
||
if (!ob) {
|
||
VLOG_WARN_RL(&rl, "buckets end with %"PRIuSIZE" leftover bytes",
|
||
buckets_length);
|
||
ofputil_bucket_list_destroy(buckets);
|
||
return OFPERR_OFPGMFC_BAD_BUCKET;
|
||
}
|
||
|
||
ob_len = ntohs(ob->len);
|
||
if (ob_len < sizeof *ob) {
|
||
VLOG_WARN_RL(&rl, "OpenFlow message bucket length "
|
||
"%"PRIuSIZE" is not valid", ob_len);
|
||
ofputil_bucket_list_destroy(buckets);
|
||
return OFPERR_OFPGMFC_BAD_BUCKET;
|
||
} else if (ob_len > buckets_length) {
|
||
VLOG_WARN_RL(&rl, "OpenFlow message bucket length %"PRIuSIZE" "
|
||
"exceeds remaining buckets data size %"PRIuSIZE,
|
||
ob_len, buckets_length);
|
||
ofputil_bucket_list_destroy(buckets);
|
||
return OFPERR_OFPGMFC_BAD_BUCKET;
|
||
}
|
||
buckets_length -= ob_len;
|
||
|
||
ofpbuf_init(&ofpacts, 0);
|
||
error = ofpacts_pull_openflow_actions(msg, ob_len - sizeof *ob,
|
||
version, NULL, NULL, &ofpacts);
|
||
if (error) {
|
||
ofpbuf_uninit(&ofpacts);
|
||
ofputil_bucket_list_destroy(buckets);
|
||
return error;
|
||
}
|
||
|
||
bucket = xzalloc(sizeof *bucket);
|
||
bucket->weight = ntohs(ob->weight);
|
||
error = ofputil_port_from_ofp11(ob->watch_port, &bucket->watch_port);
|
||
if (error) {
|
||
ofpbuf_uninit(&ofpacts);
|
||
ofputil_bucket_list_destroy(buckets);
|
||
free(bucket);
|
||
return OFPERR_OFPGMFC_BAD_WATCH;
|
||
}
|
||
bucket->watch_group = ntohl(ob->watch_group);
|
||
bucket->bucket_id = bucket_id++;
|
||
|
||
bucket->ofpacts = ofpbuf_steal_data(&ofpacts);
|
||
bucket->ofpacts_len = ofpacts.size;
|
||
ovs_list_push_back(buckets, &bucket->list_node);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static enum ofperr
|
||
ofputil_pull_ofp15_buckets(struct ofpbuf *msg, size_t buckets_length,
|
||
enum ofp_version version, uint8_t group_type,
|
||
struct ovs_list *buckets)
|
||
{
|
||
ovs_list_init(buckets);
|
||
while (buckets_length > 0) {
|
||
struct ofputil_bucket *bucket = NULL;
|
||
struct ofpbuf ofpacts;
|
||
enum ofperr err = OFPERR_OFPGMFC_BAD_BUCKET;
|
||
size_t ob_len, actions_len, properties_len;
|
||
ovs_be32 watch_port = ofputil_port_to_ofp11(OFPP_ANY);
|
||
ovs_be32 watch_group = htonl(OFPG_ANY);
|
||
ovs_be16 weight = htons(group_type == OFPGT11_SELECT ? 1 : 0);
|
||
|
||
ofpbuf_init(&ofpacts, 0);
|
||
|
||
struct ofp15_bucket *ob = ofpbuf_try_pull(msg, sizeof *ob);
|
||
if (!ob) {
|
||
VLOG_WARN_RL(&rl, "buckets end with %"PRIuSIZE
|
||
" leftover bytes", buckets_length);
|
||
goto err;
|
||
}
|
||
|
||
ob_len = ntohs(ob->len);
|
||
actions_len = ntohs(ob->action_array_len);
|
||
|
||
if (ob_len < sizeof *ob) {
|
||
VLOG_WARN_RL(&rl, "OpenFlow message bucket length "
|
||
"%"PRIuSIZE" is not valid", ob_len);
|
||
goto err;
|
||
} else if (ob_len > buckets_length) {
|
||
VLOG_WARN_RL(&rl, "OpenFlow message bucket length "
|
||
"%"PRIuSIZE" exceeds remaining buckets data size %"
|
||
PRIuSIZE, ob_len, buckets_length);
|
||
goto err;
|
||
} else if (actions_len > ob_len - sizeof *ob) {
|
||
VLOG_WARN_RL(&rl, "OpenFlow message bucket actions "
|
||
"length %"PRIuSIZE" exceeds remaining bucket "
|
||
"data size %"PRIuSIZE, actions_len,
|
||
ob_len - sizeof *ob);
|
||
goto err;
|
||
}
|
||
buckets_length -= ob_len;
|
||
|
||
err = ofpacts_pull_openflow_actions(msg, actions_len, version,
|
||
NULL, NULL, &ofpacts);
|
||
if (err) {
|
||
goto err;
|
||
}
|
||
|
||
properties_len = ob_len - sizeof *ob - actions_len;
|
||
struct ofpbuf properties = ofpbuf_const_initializer(
|
||
ofpbuf_pull(msg, properties_len), properties_len);
|
||
while (properties.size > 0) {
|
||
struct ofpbuf payload;
|
||
uint64_t type;
|
||
|
||
err = ofpprop_pull(&properties, &payload, &type);
|
||
if (err) {
|
||
goto err;
|
||
}
|
||
|
||
switch (type) {
|
||
case OFPGBPT15_WEIGHT:
|
||
err = ofpprop_parse_be16(&payload, &weight);
|
||
break;
|
||
|
||
case OFPGBPT15_WATCH_PORT:
|
||
err = ofpprop_parse_be32(&payload, &watch_port);
|
||
break;
|
||
|
||
case OFPGBPT15_WATCH_GROUP:
|
||
err = ofpprop_parse_be32(&payload, &watch_group);
|
||
break;
|
||
|
||
default:
|
||
err = OFPPROP_UNKNOWN(false, "group bucket", type);
|
||
break;
|
||
}
|
||
|
||
if (err) {
|
||
goto err;
|
||
}
|
||
}
|
||
|
||
bucket = xzalloc(sizeof *bucket);
|
||
|
||
bucket->weight = ntohs(weight);
|
||
err = ofputil_port_from_ofp11(watch_port, &bucket->watch_port);
|
||
if (err) {
|
||
err = OFPERR_OFPGMFC_BAD_WATCH;
|
||
goto err;
|
||
}
|
||
bucket->watch_group = ntohl(watch_group);
|
||
bucket->bucket_id = ntohl(ob->bucket_id);
|
||
if (bucket->bucket_id > OFPG15_BUCKET_MAX) {
|
||
VLOG_WARN_RL(&rl, "bucket id (%u) is out of range",
|
||
bucket->bucket_id);
|
||
err = OFPERR_OFPGMFC_BAD_BUCKET;
|
||
goto err;
|
||
}
|
||
|
||
bucket->ofpacts = ofpbuf_steal_data(&ofpacts);
|
||
bucket->ofpacts_len = ofpacts.size;
|
||
ovs_list_push_back(buckets, &bucket->list_node);
|
||
|
||
continue;
|
||
|
||
err:
|
||
free(bucket);
|
||
ofpbuf_uninit(&ofpacts);
|
||
ofputil_bucket_list_destroy(buckets);
|
||
return err;
|
||
}
|
||
|
||
if (ofputil_bucket_check_duplicate_id(buckets)) {
|
||
VLOG_WARN_RL(&rl, "Duplicate bucket id");
|
||
ofputil_bucket_list_destroy(buckets);
|
||
return OFPERR_OFPGMFC_BAD_BUCKET;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
ofputil_init_group_properties(struct ofputil_group_props *gp)
|
||
{
|
||
memset(gp, 0, sizeof *gp);
|
||
}
|
||
|
||
void
|
||
ofputil_group_properties_copy(struct ofputil_group_props *to,
|
||
const struct ofputil_group_props *from)
|
||
{
|
||
*to = *from;
|
||
to->fields.values = xmemdup(from->fields.values, from->fields.values_size);
|
||
}
|
||
|
||
void
|
||
ofputil_group_properties_destroy(struct ofputil_group_props *gp)
|
||
{
|
||
free(gp->fields.values);
|
||
}
|
||
|
||
static enum ofperr
|
||
parse_group_prop_ntr_selection_method(struct ofpbuf *payload,
|
||
enum ofp11_group_type group_type,
|
||
enum ofp15_group_mod_command group_cmd,
|
||
struct ofputil_group_props *gp)
|
||
{
|
||
struct ntr_group_prop_selection_method *prop = payload->data;
|
||
size_t fields_len, method_len;
|
||
enum ofperr error;
|
||
|
||
switch (group_type) {
|
||
case OFPGT11_SELECT:
|
||
break;
|
||
case OFPGT11_ALL:
|
||
case OFPGT11_INDIRECT:
|
||
case OFPGT11_FF:
|
||
OFPPROP_LOG(&rl, false, "ntr selection method property is "
|
||
"only allowed for select groups");
|
||
return OFPERR_OFPBPC_BAD_VALUE;
|
||
default:
|
||
return OFPERR_OFPGMFC_BAD_TYPE;
|
||
}
|
||
|
||
switch (group_cmd) {
|
||
case OFPGC15_ADD:
|
||
case OFPGC15_MODIFY:
|
||
case OFPGC15_ADD_OR_MOD:
|
||
break;
|
||
case OFPGC15_DELETE:
|
||
case OFPGC15_INSERT_BUCKET:
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
OFPPROP_LOG(&rl, false, "ntr selection method property is "
|
||
"only allowed for add and delete group modifications");
|
||
return OFPERR_OFPBPC_BAD_VALUE;
|
||
default:
|
||
return OFPERR_OFPGMFC_BAD_COMMAND;
|
||
}
|
||
|
||
if (payload->size < sizeof *prop) {
|
||
OFPPROP_LOG(&rl, false, "ntr selection method property "
|
||
"length %u is not valid", payload->size);
|
||
return OFPERR_OFPBPC_BAD_LEN;
|
||
}
|
||
|
||
method_len = strnlen(prop->selection_method, NTR_MAX_SELECTION_METHOD_LEN);
|
||
|
||
if (method_len == NTR_MAX_SELECTION_METHOD_LEN) {
|
||
OFPPROP_LOG(&rl, false,
|
||
"ntr selection method is not null terminated");
|
||
return OFPERR_OFPBPC_BAD_VALUE;
|
||
}
|
||
|
||
if (strcmp("hash", prop->selection_method)
|
||
&& strcmp("dp_hash", prop->selection_method)) {
|
||
OFPPROP_LOG(&rl, false,
|
||
"ntr selection method '%s' is not supported",
|
||
prop->selection_method);
|
||
return OFPERR_OFPBPC_BAD_VALUE;
|
||
}
|
||
/* 'method_len' is now non-zero. */
|
||
|
||
strcpy(gp->selection_method, prop->selection_method);
|
||
gp->selection_method_param = ntohll(prop->selection_method_param);
|
||
|
||
ofpbuf_pull(payload, sizeof *prop);
|
||
|
||
fields_len = ntohs(prop->length) - sizeof *prop;
|
||
if (fields_len && strcmp("hash", gp->selection_method)) {
|
||
OFPPROP_LOG(&rl, false, "ntr selection method %s "
|
||
"does not support fields", gp->selection_method);
|
||
return OFPERR_OFPBPC_BAD_VALUE;
|
||
}
|
||
|
||
if (fields_len > 0) {
|
||
error = oxm_pull_field_array(payload->data, fields_len,
|
||
&gp->fields);
|
||
if (error) {
|
||
OFPPROP_LOG(&rl, false,
|
||
"ntr selection method fields are invalid");
|
||
return error;
|
||
}
|
||
} else {
|
||
/* Selection_method "hash: w/o fields means default hash method. */
|
||
gp->fields.values_size = 0;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static enum ofperr
|
||
parse_ofp15_group_properties(struct ofpbuf *msg,
|
||
enum ofp11_group_type group_type,
|
||
enum ofp15_group_mod_command group_cmd,
|
||
struct ofputil_group_props *gp,
|
||
size_t properties_len)
|
||
{
|
||
struct ofpbuf properties = ofpbuf_const_initializer(
|
||
ofpbuf_pull(msg, properties_len), properties_len);
|
||
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 OFPPROP_EXP(NTR_VENDOR_ID, NTRT_SELECTION_METHOD):
|
||
case OFPPROP_EXP(NTR_COMPAT_VENDOR_ID, NTRT_SELECTION_METHOD):
|
||
error = parse_group_prop_ntr_selection_method(&payload, group_type,
|
||
group_cmd, gp);
|
||
break;
|
||
|
||
default:
|
||
error = OFPPROP_UNKNOWN(false, "group", type);
|
||
break;
|
||
}
|
||
|
||
if (error) {
|
||
return error;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
ofputil_decode_ofp11_group_desc_reply(struct ofputil_group_desc *gd,
|
||
struct ofpbuf *msg,
|
||
enum ofp_version version)
|
||
{
|
||
struct ofp11_group_desc_stats *ogds;
|
||
size_t length;
|
||
|
||
if (!msg->header) {
|
||
ofpraw_pull_assert(msg);
|
||
}
|
||
|
||
if (!msg->size) {
|
||
return EOF;
|
||
}
|
||
|
||
ogds = ofpbuf_try_pull(msg, sizeof *ogds);
|
||
if (!ogds) {
|
||
VLOG_WARN_RL(&rl, "OFPST11_GROUP_DESC reply has %"PRIu32" "
|
||
"leftover bytes at end", msg->size);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
gd->type = ogds->type;
|
||
gd->group_id = ntohl(ogds->group_id);
|
||
|
||
length = ntohs(ogds->length);
|
||
if (length < sizeof *ogds || length - sizeof *ogds > msg->size) {
|
||
VLOG_WARN_RL(&rl, "OFPST11_GROUP_DESC reply claims invalid "
|
||
"length %"PRIuSIZE, length);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
|
||
return ofputil_pull_ofp11_buckets(msg, length - sizeof *ogds, version,
|
||
&gd->buckets);
|
||
}
|
||
|
||
static int
|
||
ofputil_decode_ofp15_group_desc_reply(struct ofputil_group_desc *gd,
|
||
struct ofpbuf *msg,
|
||
enum ofp_version version)
|
||
{
|
||
struct ofp15_group_desc_stats *ogds;
|
||
uint16_t length, bucket_list_len;
|
||
int error;
|
||
|
||
if (!msg->header) {
|
||
ofpraw_pull_assert(msg);
|
||
}
|
||
|
||
if (!msg->size) {
|
||
return EOF;
|
||
}
|
||
|
||
ogds = ofpbuf_try_pull(msg, sizeof *ogds);
|
||
if (!ogds) {
|
||
VLOG_WARN_RL(&rl, "OFPST11_GROUP_DESC reply has %"PRIu32" "
|
||
"leftover bytes at end", msg->size);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
gd->type = ogds->type;
|
||
gd->group_id = ntohl(ogds->group_id);
|
||
|
||
length = ntohs(ogds->length);
|
||
if (length < sizeof *ogds || length - sizeof *ogds > msg->size) {
|
||
VLOG_WARN_RL(&rl, "OFPST11_GROUP_DESC reply claims invalid "
|
||
"length %u", length);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
|
||
bucket_list_len = ntohs(ogds->bucket_list_len);
|
||
if (length < bucket_list_len + sizeof *ogds) {
|
||
VLOG_WARN_RL(&rl, "OFPST11_GROUP_DESC reply claims invalid "
|
||
"bucket list length %u", bucket_list_len);
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
error = ofputil_pull_ofp15_buckets(msg, bucket_list_len, version, gd->type,
|
||
&gd->buckets);
|
||
if (error) {
|
||
return error;
|
||
}
|
||
|
||
/* By definition group desc messages don't have a group mod command.
|
||
* However, parse_group_prop_ntr_selection_method() checks to make sure
|
||
* that the command is OFPGC15_ADD or OFPGC15_DELETE to guard
|
||
* against group mod messages with other commands supplying
|
||
* a NTR selection method group experimenter property.
|
||
* Such properties are valid for group desc replies so
|
||
* claim that the group mod command is OFPGC15_ADD to
|
||
* satisfy the check in parse_group_prop_ntr_selection_method() */
|
||
error = parse_ofp15_group_properties(
|
||
msg, gd->type, OFPGC15_ADD, &gd->props,
|
||
length - sizeof *ogds - bucket_list_len);
|
||
if (error) {
|
||
ofputil_uninit_group_desc(gd);
|
||
}
|
||
return error;
|
||
}
|
||
|
||
/* Converts a group description reply in 'msg' into an abstract
|
||
* ofputil_group_desc in 'gd'.
|
||
*
|
||
* Multiple group description replies can be packed into a single OpenFlow
|
||
* message. Calling this function multiple times for a single 'msg' iterates
|
||
* through the replies. The caller must initially leave 'msg''s layer pointers
|
||
* null and not modify them between calls.
|
||
*
|
||
* Returns 0 if successful, EOF if no replies were left in this 'msg',
|
||
* otherwise a positive errno value. */
|
||
int
|
||
ofputil_decode_group_desc_reply(struct ofputil_group_desc *gd,
|
||
struct ofpbuf *msg, enum ofp_version version)
|
||
{
|
||
ofputil_init_group_properties(&gd->props);
|
||
|
||
switch (version)
|
||
{
|
||
case OFP11_VERSION:
|
||
case OFP12_VERSION:
|
||
case OFP13_VERSION:
|
||
case OFP14_VERSION:
|
||
return ofputil_decode_ofp11_group_desc_reply(gd, msg, version);
|
||
|
||
case OFP10_VERSION:
|
||
case OFP15_VERSION:
|
||
return ofputil_decode_ofp15_group_desc_reply(gd, msg, version);
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
static void
|
||
ofp_print_bucket_id(struct ds *s, const char *label, uint32_t bucket_id,
|
||
enum ofp_version ofp_version)
|
||
{
|
||
if (ofp_version > OFP10_VERSION && ofp_version < OFP15_VERSION) {
|
||
return;
|
||
}
|
||
|
||
ds_put_cstr(s, label);
|
||
|
||
switch (bucket_id) {
|
||
case OFPG15_BUCKET_FIRST:
|
||
ds_put_cstr(s, "first");
|
||
break;
|
||
case OFPG15_BUCKET_LAST:
|
||
ds_put_cstr(s, "last");
|
||
break;
|
||
case OFPG15_BUCKET_ALL:
|
||
ds_put_cstr(s, "all");
|
||
break;
|
||
default:
|
||
ds_put_format(s, "%"PRIu32, bucket_id);
|
||
break;
|
||
}
|
||
|
||
ds_put_char(s, ',');
|
||
}
|
||
|
||
static void
|
||
ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
|
||
const struct ovs_list *p_buckets,
|
||
const struct ofputil_group_props *props,
|
||
enum ofp_version ofp_version, bool suppress_type,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map)
|
||
{
|
||
struct ofputil_bucket *bucket;
|
||
|
||
ds_put_format(s, "group_id=%"PRIu32, group_id);
|
||
|
||
if (!suppress_type) {
|
||
static const char *type_str[] = { "all", "select", "indirect",
|
||
"ff", "unknown" };
|
||
ds_put_format(s, ",type=%s", type_str[type > 4 ? 4 : type]);
|
||
}
|
||
|
||
if (props->selection_method[0]) {
|
||
ds_put_format(s, ",selection_method=%s", props->selection_method);
|
||
if (props->selection_method_param) {
|
||
ds_put_format(s, ",selection_method_param=%"PRIu64,
|
||
props->selection_method_param);
|
||
}
|
||
|
||
size_t n = bitmap_count1(props->fields.used.bm, MFF_N_IDS);
|
||
if (n == 1) {
|
||
ds_put_cstr(s, ",fields=");
|
||
oxm_format_field_array(s, &props->fields);
|
||
} else if (n > 1) {
|
||
ds_put_cstr(s, ",fields(");
|
||
oxm_format_field_array(s, &props->fields);
|
||
ds_put_char(s, ')');
|
||
}
|
||
}
|
||
|
||
if (!p_buckets) {
|
||
return;
|
||
}
|
||
|
||
ds_put_char(s, ',');
|
||
|
||
LIST_FOR_EACH (bucket, list_node, p_buckets) {
|
||
ds_put_cstr(s, "bucket=");
|
||
|
||
ofp_print_bucket_id(s, "bucket_id:", bucket->bucket_id, ofp_version);
|
||
if (bucket->weight != (type == OFPGT11_SELECT ? 1 : 0)) {
|
||
ds_put_format(s, "weight:%"PRIu16",", bucket->weight);
|
||
}
|
||
if (bucket->watch_port != OFPP_NONE) {
|
||
ds_put_cstr(s, "watch_port:");
|
||
ofputil_format_port(bucket->watch_port, port_map, s);
|
||
ds_put_char(s, ',');
|
||
}
|
||
if (bucket->watch_group != OFPG_ANY) {
|
||
ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group);
|
||
}
|
||
|
||
ds_put_cstr(s, "actions=");
|
||
struct ofpact_format_params fp = {
|
||
.port_map = port_map,
|
||
.table_map = table_map,
|
||
.s = s,
|
||
};
|
||
ofpacts_format(bucket->ofpacts, bucket->ofpacts_len, &fp);
|
||
ds_put_char(s, ',');
|
||
}
|
||
|
||
ds_chomp(s, ',');
|
||
}
|
||
|
||
enum ofperr
|
||
ofputil_group_desc_format(struct ds *s, const struct ofp_header *oh,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map)
|
||
{
|
||
struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
|
||
for (;;) {
|
||
struct ofputil_group_desc gd;
|
||
int retval;
|
||
|
||
retval = ofputil_decode_group_desc_reply(&gd, &b, oh->version);
|
||
if (retval) {
|
||
return retval != EOF ? retval : 0;
|
||
}
|
||
|
||
ds_put_char(s, '\n');
|
||
ds_put_char(s, ' ');
|
||
ofp_print_group(s, gd.group_id, gd.type, &gd.buckets, &gd.props,
|
||
oh->version, false, port_map, table_map);
|
||
ofputil_uninit_group_desc(&gd);
|
||
}
|
||
}
|
||
|
||
void
|
||
ofputil_uninit_group_mod(struct ofputil_group_mod *gm)
|
||
{
|
||
ofputil_bucket_list_destroy(&gm->buckets);
|
||
ofputil_group_properties_destroy(&gm->props);
|
||
}
|
||
|
||
static void
|
||
bad_group_cmd(enum ofp15_group_mod_command cmd)
|
||
{
|
||
const char *opt_version;
|
||
const char *version;
|
||
const char *cmd_str;
|
||
|
||
switch (cmd) {
|
||
case OFPGC15_ADD:
|
||
case OFPGC15_MODIFY:
|
||
case OFPGC15_ADD_OR_MOD:
|
||
case OFPGC15_DELETE:
|
||
version = "1.1";
|
||
opt_version = "11";
|
||
break;
|
||
|
||
case OFPGC15_INSERT_BUCKET:
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
version = "1.5";
|
||
opt_version = "15";
|
||
break;
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
|
||
switch (cmd) {
|
||
case OFPGC15_ADD:
|
||
cmd_str = "add-group";
|
||
break;
|
||
|
||
case OFPGC15_MODIFY:
|
||
case OFPGC15_ADD_OR_MOD:
|
||
cmd_str = "mod-group";
|
||
break;
|
||
|
||
case OFPGC15_DELETE:
|
||
cmd_str = "del-group";
|
||
break;
|
||
|
||
case OFPGC15_INSERT_BUCKET:
|
||
cmd_str = "insert-bucket";
|
||
break;
|
||
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
cmd_str = "remove-bucket";
|
||
break;
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
|
||
ovs_fatal(0, "%s needs OpenFlow %s or later (\'-O OpenFlow%s\')",
|
||
cmd_str, version, opt_version);
|
||
|
||
}
|
||
|
||
static struct ofpbuf *
|
||
ofputil_encode_ofp11_group_mod(enum ofp_version ofp_version,
|
||
const struct ofputil_group_mod *gm,
|
||
const struct ovs_list *new_buckets,
|
||
int group_existed)
|
||
{
|
||
struct ofpbuf *b;
|
||
struct ofp11_group_mod *ogm;
|
||
size_t start_ogm;
|
||
struct ofputil_bucket *bucket;
|
||
|
||
b = ofpraw_alloc(OFPRAW_OFPT11_GROUP_MOD, ofp_version, 0);
|
||
start_ogm = b->size;
|
||
ofpbuf_put_zeros(b, sizeof *ogm);
|
||
|
||
uint16_t command = gm->command;
|
||
const struct ovs_list *buckets = &gm->buckets;
|
||
switch (gm->command) {
|
||
case OFPGC15_INSERT_BUCKET:
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
if (!new_buckets) {
|
||
bad_group_cmd(gm->command);
|
||
}
|
||
command = OFPGC11_MODIFY;
|
||
buckets = new_buckets;
|
||
break;
|
||
|
||
case OFPGC11_ADD_OR_MOD:
|
||
if (group_existed >= 0) {
|
||
command = group_existed ? OFPGC11_MODIFY : OFPGC11_ADD;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
LIST_FOR_EACH (bucket, list_node, buckets) {
|
||
ofputil_put_ofp11_bucket(bucket, b, ofp_version);
|
||
}
|
||
ogm = ofpbuf_at_assert(b, start_ogm, sizeof *ogm);
|
||
ogm->command = htons(command);
|
||
ogm->type = gm->type;
|
||
ogm->group_id = htonl(gm->group_id);
|
||
|
||
ofpmsg_update_length(b);
|
||
|
||
return b;
|
||
}
|
||
|
||
static struct ofpbuf *
|
||
ofputil_encode_ofp15_group_mod(enum ofp_version ofp_version,
|
||
const struct ofputil_group_mod *gm,
|
||
int group_existed)
|
||
{
|
||
struct ofpbuf *b;
|
||
struct ofp15_group_mod *ogm;
|
||
size_t start_ogm;
|
||
struct ofputil_bucket *bucket;
|
||
struct id_pool *bucket_ids = NULL;
|
||
|
||
b = ofpraw_alloc((ofp_version == OFP10_VERSION
|
||
? OFPRAW_NXT_GROUP_MOD
|
||
: OFPRAW_OFPT15_GROUP_MOD), ofp_version, 0);
|
||
start_ogm = b->size;
|
||
ofpbuf_put_zeros(b, sizeof *ogm);
|
||
|
||
LIST_FOR_EACH (bucket, list_node, &gm->buckets) {
|
||
uint32_t bucket_id;
|
||
|
||
/* Generate a bucket id if none was supplied */
|
||
if (bucket->bucket_id > OFPG15_BUCKET_MAX) {
|
||
if (!bucket_ids) {
|
||
const struct ofputil_bucket *bkt;
|
||
|
||
bucket_ids = id_pool_create(0, OFPG15_BUCKET_MAX + 1);
|
||
|
||
/* Mark all bucket_ids that are present in gm
|
||
* as used in the pool. */
|
||
LIST_FOR_EACH_REVERSE (bkt, list_node, &gm->buckets) {
|
||
if (bkt == bucket) {
|
||
break;
|
||
}
|
||
if (bkt->bucket_id <= OFPG15_BUCKET_MAX) {
|
||
id_pool_add(bucket_ids, bkt->bucket_id);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!id_pool_alloc_id(bucket_ids, &bucket_id)) {
|
||
OVS_NOT_REACHED();
|
||
}
|
||
} else {
|
||
bucket_id = bucket->bucket_id;
|
||
}
|
||
|
||
ofputil_put_ofp15_bucket(bucket, bucket_id, gm->type, b, ofp_version);
|
||
}
|
||
ogm = ofpbuf_at_assert(b, start_ogm, sizeof *ogm);
|
||
ogm->command = htons(gm->command != OFPGC11_ADD_OR_MOD || group_existed < 0
|
||
? gm->command
|
||
: group_existed ? OFPGC11_MODIFY : OFPGC11_ADD);
|
||
ogm->type = gm->type;
|
||
ogm->group_id = htonl(gm->group_id);
|
||
ogm->command_bucket_id = htonl(gm->command_bucket_id);
|
||
ogm->bucket_array_len = htons(b->size - start_ogm - sizeof *ogm);
|
||
|
||
/* Add group properties */
|
||
if (gm->props.selection_method[0]) {
|
||
ofputil_put_group_prop_ntr_selection_method(ofp_version, &gm->props, b);
|
||
}
|
||
|
||
id_pool_destroy(bucket_ids);
|
||
ofpmsg_update_length(b);
|
||
return b;
|
||
}
|
||
|
||
/* Converts abstract group mod 'gm' into a message for OpenFlow version
|
||
* 'ofp_version' and returns the message.
|
||
*
|
||
* If 'new_buckets' is nonnull, it should point to the full set of new buckets
|
||
* that resulted from a OFPGC15_INSERT_BUCKET or OFPGC15_REMOVE_BUCKET command.
|
||
* It is needed to translate such group_mods into OpenFlow 1.1-1.4
|
||
* OFPGC11_MODIFY. If it is null but needed for translation, then encoding the
|
||
* group_mod will print an error on stderr and exit().
|
||
*
|
||
* If 'group_existed' is nonnegative, it should specify whether the group
|
||
* existed before the command was executed. If it is nonnegative, then it is
|
||
* used to translate OVS's nonstandard OFPGC11_ADD_OR_MOD into a standard
|
||
* command. If it is negative, then OFPGC11_ADD_OR_MOD will be left as is. */
|
||
struct ofpbuf *
|
||
ofputil_encode_group_mod(enum ofp_version ofp_version,
|
||
const struct ofputil_group_mod *gm,
|
||
const struct ovs_list *new_buckets,
|
||
int group_existed)
|
||
{
|
||
|
||
switch (ofp_version) {
|
||
case OFP11_VERSION:
|
||
case OFP12_VERSION:
|
||
case OFP13_VERSION:
|
||
case OFP14_VERSION:
|
||
return ofputil_encode_ofp11_group_mod(ofp_version, gm,
|
||
new_buckets, group_existed);
|
||
|
||
case OFP10_VERSION:
|
||
case OFP15_VERSION:
|
||
return ofputil_encode_ofp15_group_mod(ofp_version, gm, group_existed);
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
static enum ofperr
|
||
ofputil_pull_ofp11_group_mod(struct ofpbuf *msg, enum ofp_version ofp_version,
|
||
struct ofputil_group_mod *gm)
|
||
{
|
||
const struct ofp11_group_mod *ogm;
|
||
enum ofperr error;
|
||
|
||
ogm = ofpbuf_pull(msg, sizeof *ogm);
|
||
gm->command = ntohs(ogm->command);
|
||
gm->type = ogm->type;
|
||
gm->group_id = ntohl(ogm->group_id);
|
||
gm->command_bucket_id = OFPG15_BUCKET_ALL;
|
||
|
||
error = ofputil_pull_ofp11_buckets(msg, msg->size, ofp_version,
|
||
&gm->buckets);
|
||
|
||
/* OF1.3.5+ prescribes an error when an OFPGC_DELETE includes buckets. */
|
||
if (!error
|
||
&& ofp_version >= OFP13_VERSION
|
||
&& gm->command == OFPGC11_DELETE
|
||
&& !ovs_list_is_empty(&gm->buckets)) {
|
||
error = OFPERR_OFPGMFC_INVALID_GROUP;
|
||
ofputil_bucket_list_destroy(&gm->buckets);
|
||
}
|
||
|
||
return error;
|
||
}
|
||
|
||
static enum ofperr
|
||
ofputil_pull_ofp15_group_mod(struct ofpbuf *msg, enum ofp_version ofp_version,
|
||
struct ofputil_group_mod *gm)
|
||
{
|
||
const struct ofp15_group_mod *ogm;
|
||
uint16_t bucket_list_len;
|
||
enum ofperr error = OFPERR_OFPGMFC_BAD_BUCKET;
|
||
|
||
ogm = ofpbuf_pull(msg, sizeof *ogm);
|
||
gm->command = ntohs(ogm->command);
|
||
gm->type = ogm->type;
|
||
gm->group_id = ntohl(ogm->group_id);
|
||
|
||
gm->command_bucket_id = ntohl(ogm->command_bucket_id);
|
||
switch (gm->command) {
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
if (gm->command_bucket_id == OFPG15_BUCKET_ALL) {
|
||
error = 0;
|
||
}
|
||
/* Fall through */
|
||
case OFPGC15_INSERT_BUCKET:
|
||
if (gm->command_bucket_id <= OFPG15_BUCKET_MAX ||
|
||
gm->command_bucket_id == OFPG15_BUCKET_FIRST
|
||
|| gm->command_bucket_id == OFPG15_BUCKET_LAST) {
|
||
error = 0;
|
||
}
|
||
break;
|
||
|
||
case OFPGC11_ADD:
|
||
case OFPGC11_MODIFY:
|
||
case OFPGC11_ADD_OR_MOD:
|
||
case OFPGC11_DELETE:
|
||
default:
|
||
if (gm->command_bucket_id == OFPG15_BUCKET_ALL) {
|
||
error = 0;
|
||
}
|
||
break;
|
||
}
|
||
if (error) {
|
||
VLOG_WARN_RL(&rl,
|
||
"group command bucket id (%u) is out of range",
|
||
gm->command_bucket_id);
|
||
return OFPERR_OFPGMFC_BAD_BUCKET;
|
||
}
|
||
|
||
bucket_list_len = ntohs(ogm->bucket_array_len);
|
||
if (bucket_list_len > msg->size) {
|
||
return OFPERR_OFPBRC_BAD_LEN;
|
||
}
|
||
error = ofputil_pull_ofp15_buckets(msg, bucket_list_len, ofp_version,
|
||
gm->type, &gm->buckets);
|
||
if (error) {
|
||
return error;
|
||
}
|
||
|
||
error = parse_ofp15_group_properties(msg, gm->type, gm->command,
|
||
&gm->props, msg->size);
|
||
if (error) {
|
||
ofputil_uninit_group_mod(gm);
|
||
}
|
||
return error;
|
||
}
|
||
|
||
static enum ofperr
|
||
ofputil_check_group_mod(const struct ofputil_group_mod *gm)
|
||
{
|
||
switch (gm->type) {
|
||
case OFPGT11_INDIRECT:
|
||
if (gm->command != OFPGC11_DELETE
|
||
&& !ovs_list_is_singleton(&gm->buckets) ) {
|
||
return OFPERR_OFPGMFC_INVALID_GROUP;
|
||
}
|
||
break;
|
||
case OFPGT11_ALL:
|
||
case OFPGT11_SELECT:
|
||
case OFPGT11_FF:
|
||
break;
|
||
default:
|
||
return OFPERR_OFPGMFC_BAD_TYPE;
|
||
}
|
||
|
||
switch (gm->command) {
|
||
case OFPGC11_ADD:
|
||
case OFPGC11_MODIFY:
|
||
case OFPGC11_ADD_OR_MOD:
|
||
case OFPGC11_DELETE:
|
||
case OFPGC15_INSERT_BUCKET:
|
||
break;
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
if (!ovs_list_is_empty(&gm->buckets)) {
|
||
return OFPERR_OFPGMFC_BAD_BUCKET;
|
||
}
|
||
break;
|
||
default:
|
||
return OFPERR_OFPGMFC_BAD_COMMAND;
|
||
}
|
||
|
||
struct ofputil_bucket *bucket;
|
||
LIST_FOR_EACH (bucket, list_node, &gm->buckets) {
|
||
if (bucket->weight && gm->type != OFPGT11_SELECT
|
||
&& gm->command != OFPGC15_INSERT_BUCKET) {
|
||
return OFPERR_OFPGMFC_INVALID_GROUP;
|
||
}
|
||
|
||
switch (gm->type) {
|
||
case OFPGT11_ALL:
|
||
case OFPGT11_INDIRECT:
|
||
if (ofputil_bucket_has_liveness(bucket)) {
|
||
return OFPERR_OFPGMFC_WATCH_UNSUPPORTED;
|
||
}
|
||
break;
|
||
case OFPGT11_SELECT:
|
||
break;
|
||
case OFPGT11_FF:
|
||
if (!ofputil_bucket_has_liveness(bucket)) {
|
||
return OFPERR_OFPGMFC_INVALID_GROUP;
|
||
}
|
||
break;
|
||
default:
|
||
/* Returning BAD TYPE to be consistent
|
||
* though gm->type has been checked already. */
|
||
return OFPERR_OFPGMFC_BAD_TYPE;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Converts OpenFlow group mod message 'oh' into an abstract group mod in
|
||
* 'gm'. Returns 0 if successful, otherwise an OpenFlow error code. */
|
||
enum ofperr
|
||
ofputil_decode_group_mod(const struct ofp_header *oh,
|
||
struct ofputil_group_mod *gm)
|
||
{
|
||
ofputil_init_group_properties(&gm->props);
|
||
|
||
enum ofp_version ofp_version = oh->version;
|
||
struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length));
|
||
ofpraw_pull_assert(&msg);
|
||
|
||
enum ofperr err;
|
||
switch (ofp_version) {
|
||
case OFP11_VERSION:
|
||
case OFP12_VERSION:
|
||
case OFP13_VERSION:
|
||
case OFP14_VERSION:
|
||
err = ofputil_pull_ofp11_group_mod(&msg, ofp_version, gm);
|
||
break;
|
||
|
||
case OFP10_VERSION:
|
||
case OFP15_VERSION:
|
||
err = ofputil_pull_ofp15_group_mod(&msg, ofp_version, gm);
|
||
break;
|
||
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
if (err) {
|
||
return err;
|
||
}
|
||
|
||
err = ofputil_check_group_mod(gm);
|
||
if (err) {
|
||
ofputil_uninit_group_mod(gm);
|
||
}
|
||
return err;
|
||
}
|
||
|
||
void
|
||
ofputil_group_mod_format__(struct ds *s, enum ofp_version ofp_version,
|
||
const struct ofputil_group_mod *gm,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map)
|
||
{
|
||
bool bucket_command = false;
|
||
|
||
ds_put_char(s, '\n');
|
||
|
||
ds_put_char(s, ' ');
|
||
switch (gm->command) {
|
||
case OFPGC11_ADD:
|
||
ds_put_cstr(s, "ADD");
|
||
break;
|
||
|
||
case OFPGC11_MODIFY:
|
||
ds_put_cstr(s, "MOD");
|
||
break;
|
||
|
||
case OFPGC11_ADD_OR_MOD:
|
||
ds_put_cstr(s, "ADD_OR_MOD");
|
||
break;
|
||
|
||
case OFPGC11_DELETE:
|
||
ds_put_cstr(s, "DEL");
|
||
break;
|
||
|
||
case OFPGC15_INSERT_BUCKET:
|
||
ds_put_cstr(s, "INSERT_BUCKET");
|
||
bucket_command = true;
|
||
break;
|
||
|
||
case OFPGC15_REMOVE_BUCKET:
|
||
ds_put_cstr(s, "REMOVE_BUCKET");
|
||
bucket_command = true;
|
||
break;
|
||
|
||
default:
|
||
ds_put_format(s, "cmd:%"PRIu16"", gm->command);
|
||
}
|
||
ds_put_char(s, ' ');
|
||
|
||
if (bucket_command) {
|
||
ofp_print_bucket_id(s, "command_bucket_id:",
|
||
gm->command_bucket_id, ofp_version);
|
||
}
|
||
|
||
ofp_print_group(s, gm->group_id, gm->type, &gm->buckets, &gm->props,
|
||
ofp_version, bucket_command, port_map, table_map);
|
||
}
|
||
|
||
enum ofperr
|
||
ofputil_group_mod_format(struct ds *s, const struct ofp_header *oh,
|
||
const struct ofputil_port_map *port_map,
|
||
const struct ofputil_table_map *table_map)
|
||
{
|
||
struct ofputil_group_mod gm;
|
||
int error;
|
||
|
||
error = ofputil_decode_group_mod(oh, &gm);
|
||
if (error) {
|
||
return error;
|
||
}
|
||
ofputil_group_mod_format__(s, oh->version, &gm, port_map, table_map);
|
||
ofputil_uninit_group_mod(&gm);
|
||
return 0;
|
||
}
|