2
0
mirror of https://github.com/openvswitch/ovs synced 2025-10-19 14:37:21 +00:00
Files
openvswitch/lib/ofp-group.c
Vishal Deep Ajmera 44810e6d41 ofproto: Add support to watch controller port liveness in fast-failover group
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>
2020-03-06 13:25:23 -08:00

2391 lines
76 KiB
C
Raw Blame History

This file contains invisible Unicode characters

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

/*
* Copyright (c) 2008-2017, 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;
}