Murphy McCauley murphy.mccauley@gmail.com
Natasha Gude natasha@nicira.com
Neil McKee neil.mckee@inmon.com
+Neil Zhu zhuj@centecnetworks.com
Paraneetharan Chandrasekaran paraneetharanc@gmail.com
Paul Fazzone pfazzone@nicira.com
Paul Ingram paul@nicira.com
- OpenFlow:
* Experimental support for OpenFlow 1.1 (in addition to 1.2 and
1.3, which had experimental support in 1.10).
+ * Experimental protocol support for OpenFlow 1.1+ groups. This
+ does not yet include an implementation in the Open vSwitch
+ software switch.
+ * Experimental protocol support for OpenFlow 1.2+ meters. This
+ does not yet include an implementation in the Open vSwitch
+ software switch.
* New support for matching outer source and destination IP address
of tunneled packets, for tunnel ports configured with the newly
added "remote_ip=flow" and "local_ip=flow" options.
- Support for Linux kernels up to 3.10
- ovs-ofctl:
* New "ofp-parse" for printing OpenFlow messages read from a file.
+ * New commands for OpenFlow 1.1+ groups.
- Added configurable flow caching support to IPFIX exporter.
-/* Copyright (c) 2008, 2011, 2012 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2011, 2012, 2013 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
/* Group setup and teardown (controller -> datapath). */
struct ofp11_group_mod {
- ovs_be16 command; /* One of OFPGC_*. */
- uint8_t type; /* One of OFPGT_*. */
+ ovs_be16 command; /* One of OFPGC11_*. */
+ uint8_t type; /* One of OFPGT11_*. */
uint8_t pad; /* Pad to 64 bits. */
ovs_be32 group_id; /* Group identifier. */
/* struct ofp11_bucket buckets[0]; The bucket length is inferred from the
/* Body of reply to OFPST11_GROUP_DESC request. */
struct ofp11_group_desc_stats {
ovs_be16 length; /* Length of this entry. */
- uint8_t type; /* One of OFPGT_*. */
+ uint8_t type; /* One of OFPGT11_*. */
uint8_t pad; /* Pad to 64 bits. */
ovs_be32 group_id; /* Group identifier. */
/* struct ofp11_bucket buckets[0]; */
/* Body of reply to OFPST12_GROUP_FEATURES request. Group features. */
struct ofp12_group_features_stats {
- ovs_be32 types; /* Bitmap of OFPGT_* values supported. */
+ ovs_be32 types; /* Bitmap of OFPGT11_* values supported. */
ovs_be32 capabilities; /* Bitmap of OFPGFC12_* capability supported. */
ovs_be32 max_groups[4]; /* Maximum number of groups for each type. */
ovs_be32 actions[4]; /* Bitmaps of OFPAT_* that are supported. */
* header and any padding used to make the action 64-bit aligned.
* NB: The length of an action *must* always be a multiple of eight. */
struct ofp_action_header {
- ovs_be16 type; /* One of OFPAT10_*. */
+ ovs_be16 type; /* One of OFPAT*. */
ovs_be16 len; /* Length of action, including this
header. This is the length of action,
including any padding to make it
case OFPTYPE_PORT_STATUS:
case OFPTYPE_PACKET_OUT:
case OFPTYPE_FLOW_MOD:
+ case OFPTYPE_GROUP_MOD:
case OFPTYPE_PORT_MOD:
case OFPTYPE_BARRIER_REQUEST:
case OFPTYPE_BARRIER_REPLY:
break;
}
+ case OFPUTIL_OFPAT11_GROUP: {
+ struct ofp11_action_group *oag = (struct ofp11_action_group *)a;
+ ofpact_put_GROUP(out)->group_id = ntohl(oag->group_id);
+ break;
+ }
+
#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
#include "ofp-util.def"
return ofpact_from_nxast(a, code, out);
case OFPACT_GOTO_TABLE:
return OVSINST_OFPIT11_GOTO_TABLE;
case OFPACT_OUTPUT:
+ case OFPACT_GROUP:
case OFPACT_CONTROLLER:
case OFPACT_ENQUEUE:
case OFPACT_OUTPUT_REG:
}
return 0;
+ case OFPACT_GROUP:
+ return 0;
+
default:
NOT_REACHED();
}
ofpact_sample_to_nxast(ofpact_get_SAMPLE(a), out);
break;
+ case OFPACT_GROUP:
case OFPACT_OUTPUT:
case OFPACT_ENQUEUE:
case OFPACT_SET_VLAN_VID:
/* XXX */
break;
+ case OFPACT_GROUP:
+ break;
+
case OFPACT_CONTROLLER:
case OFPACT_OUTPUT_REG:
case OFPACT_BUNDLE:
case OFPACT_METER:
NOT_REACHED();
+ case OFPACT_GROUP:
+ ofputil_put_OFPAT11_GROUP(out)->group_id =
+ htonl(ofpact_get_GROUP(a)->group_id);
+ break;
+
case OFPACT_CONTROLLER:
case OFPACT_OUTPUT_REG:
case OFPACT_BUNDLE:
case OFPACT_CLEAR_ACTIONS:
case OFPACT_GOTO_TABLE:
case OFPACT_METER:
+ case OFPACT_GROUP:
default:
return false;
}
return false;
}
+/* Returns true if any action in the 'ofpacts_len' bytes of 'ofpacts' outputs
+ * to 'group', false otherwise. */
+bool
+ofpacts_output_to_group(const struct ofpact *ofpacts, size_t ofpacts_len,
+ uint32_t group_id)
+{
+ const struct ofpact *a;
+
+ OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
+ if (a->type == OFPACT_GROUP
+ && ofpact_get_GROUP(a)->group_id == group_id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
bool
ofpacts_equal(const struct ofpact *a, size_t a_len,
const struct ofpact *b, size_t b_len)
ovs_instruction_name_from_type(OVSINST_OFPIT13_METER),
ofpact_get_METER(a)->meter_id);
break;
+
+ case OFPACT_GROUP:
+ ds_put_format(s, "group:%"PRIu32,
+ ofpact_get_GROUP(a)->group_id);
+ break;
}
}
#define OFPACTS \
/* Output. */ \
DEFINE_OFPACT(OUTPUT, ofpact_output, ofpact) \
+ DEFINE_OFPACT(GROUP, ofpact_group, ofpact) \
DEFINE_OFPACT(CONTROLLER, ofpact_controller, ofpact) \
DEFINE_OFPACT(ENQUEUE, ofpact_enqueue, ofpact) \
DEFINE_OFPACT(OUTPUT_REG, ofpact_output_reg, ofpact) \
uint8_t table_id;
};
+/* OFPACT_GROUP.
+ *
+ * Used for OFPAT11_GROUP. */
+struct ofpact_group {
+ struct ofpact ofpact;
+ uint32_t group_id;
+};
+
/* Converting OpenFlow to ofpacts. */
enum ofperr ofpacts_pull_openflow10(struct ofpbuf *openflow,
unsigned int actions_len,
/* Working with ofpacts. */
bool ofpacts_output_to_port(const struct ofpact[], size_t ofpacts_len,
ofp_port_t port);
+bool ofpacts_output_to_group(const struct ofpact[], size_t ofpacts_len,
+ uint32_t group_id);
bool ofpacts_equal(const struct ofpact a[], size_t a_len,
const struct ofpact b[], size_t b_len);
/* NXT 1.0+ (13): struct nx_flow_mod, uint8_t[8][]. */
OFPRAW_NXT_FLOW_MOD,
+ /* OFPT 1.1+ (15): struct ofp11_group_mod, uint8_t[8][]. */
+ OFPRAW_OFPT11_GROUP_MOD,
+
/* OFPT 1.0 (15): struct ofp10_port_mod. */
OFPRAW_OFPT10_PORT_MOD,
/* OFPT 1.1+ (16): struct ofp11_port_mod. */
/* OFPST 1.1+ (6): struct ofp11_group_stats_request. */
OFPRAW_OFPST11_GROUP_REQUEST,
- /* OFPST 1.1-1.2 (6): struct ofp11_group_stats[]. */
+ /* OFPST 1.1-1.2 (6): uint8_t[8][]. */
OFPRAW_OFPST11_GROUP_REPLY,
- /* OFPST 1.3 (6): struct ofp13_group_stats[]. */
+ /* OFPST 1.3 (6): uint8_t[8][]. */
OFPRAW_OFPST13_GROUP_REPLY,
/* OFPST 1.1+ (7): void. */
OFPRAW_OFPST11_GROUP_DESC_REQUEST,
- /* OFPST 1.1+ (7): struct ofp11_group_desc_stats[]. */
+ /* OFPST 1.1+ (7): uint8_t[8][]. */
OFPRAW_OFPST11_GROUP_DESC_REPLY,
/* OFPST 1.2+ (8): void. */
OFPTYPE_FLOW_MOD, /* OFPRAW_OFPT10_FLOW_MOD.
* OFPRAW_OFPT11_FLOW_MOD.
* OFPRAW_NXT_FLOW_MOD. */
+ OFPTYPE_GROUP_MOD, /* OFPRAW_OFPT11_GROUP_MOD. */
OFPTYPE_PORT_MOD, /* OFPRAW_OFPT10_PORT_MOD.
* OFPRAW_OFPT11_PORT_MOD. */
}
break;
+ case OFPUTIL_OFPAT11_GROUP:
+ error = str_to_u32(arg, &ofpact_put_GROUP(ofpacts)->group_id);
+ break;
+
case OFPUTIL_NXAST_STACK_PUSH:
error = nxm_parse_stack_action(ofpact_put_STACK_PUSH(ofpacts), arg);
break;
fm->buffer_id = UINT32_MAX;
fm->out_port = OFPP_ANY;
fm->flags = 0;
+ fm->out_group = OFPG11_ANY;
if (fields & F_ACTIONS) {
act_str = strstr(string, "action");
if (!act_str) {
fsr->cookie_mask = fm.cookie_mask;
fsr->match = fm.match;
fsr->out_port = fm.out_port;
+ fsr->out_group = fm.out_group;
fsr->table_id = fm.table_id;
return NULL;
}
}
return error;
}
+
+static char * WARN_UNUSED_RESULT
+parse_bucket_str(struct ofputil_bucket *bucket, char *str_,
+ enum ofputil_protocol *usable_protocols)
+{
+ struct ofpbuf ofpacts;
+ char *pos, *act, *arg;
+ int n_actions;
+
+ bucket->weight = 1;
+ bucket->watch_port = OFPP_ANY;
+ bucket->watch_group = OFPG11_ANY;
+
+ pos = str_;
+ n_actions = 0;
+ ofpbuf_init(&ofpacts, 64);
+ while (ofputil_parse_key_value(&pos, &act, &arg)) {
+ char *error = NULL;
+
+ if (!strcasecmp(act, "weight")) {
+ error = str_to_u16(arg, "weight", &bucket->weight);
+ } else if (!strcasecmp(act, "watch_port")) {
+ if (!ofputil_port_from_string(arg, &bucket->watch_port)
+ || (ofp_to_u16(bucket->watch_port) >= ofp_to_u16(OFPP_MAX)
+ && bucket->watch_port != OFPP_ANY)) {
+ error = xasprintf("%s: invalid watch_port", arg);
+ }
+ } else if (!strcasecmp(act, "watch_group")) {
+ error = str_to_u32(arg, &bucket->watch_group);
+ if (!error && bucket->watch_group > OFPG_MAX) {
+ error = xasprintf("invalid watch_group id %"PRIu32,
+ bucket->watch_group);
+ }
+ } else {
+ error = str_to_ofpact__(pos, act, arg, &ofpacts, n_actions,
+ usable_protocols);
+ n_actions++;
+ }
+
+ if (error) {
+ ofpbuf_uninit(&ofpacts);
+ return error;
+ }
+ }
+
+ ofpact_pad(&ofpacts);
+ bucket->ofpacts = ofpacts.data;
+ bucket->ofpacts_len = ofpacts.size;
+
+ return NULL;
+}
+
+static char * WARN_UNUSED_RESULT
+parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
+ char *string,
+ enum ofputil_protocol *usable_protocols)
+{
+ enum {
+ F_GROUP_TYPE = 1 << 0,
+ F_BUCKETS = 1 << 1,
+ } fields;
+ char *save_ptr = NULL;
+ bool had_type = false;
+ char *name;
+ struct ofputil_bucket *bucket;
+ char *error = NULL;
+
+ *usable_protocols = OFPUTIL_P_OF11_UP;
+
+ 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;
+
+ default:
+ NOT_REACHED();
+ }
+
+ memset(gm, 0, sizeof *gm);
+ gm->command = command;
+ gm->group_id = OFPG_ANY;
+ list_init(&gm->buckets);
+ if (command == OFPGC11_DELETE && string[0] == '\0') {
+ gm->group_id = OFPG_ALL;
+ return NULL;
+ }
+
+ *usable_protocols = OFPUTIL_P_OF11_UP;
+
+ if (fields & F_BUCKETS) {
+ char *bkt_str = strstr(string, "bucket");
+
+ if (bkt_str) {
+ *bkt_str = '\0';
+ }
+
+ 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, usable_protocols);
+ if (error) {
+ free(bucket);
+ goto out;
+ }
+ list_push_back(&gm->buckets, &bucket->list_node);
+
+ bkt_str = next_bkt_str;
+ }
+ }
+
+ for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name;
+ name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) {
+ char *value;
+
+ value = strtok_r(NULL, ", \t\r\n", &save_ptr);
+ if (!value) {
+ error = xasprintf("field %s missing value", name);
+ goto out;
+ }
+
+ if (!strcmp(name, "group_id")) {
+ if(!strcmp(value, "all")) {
+ gm->group_id = OFPG_ALL;
+ } else {
+ char *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, "bucket")) {
+ error = xstrdup("bucket is not needed");
+ goto out;
+ } 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;
+ }
+
+ /* Validate buckets. */
+ LIST_FOR_EACH (bucket, list_node, &gm->buckets) {
+ if (bucket->weight != 1 && gm->type != OFPGT11_SELECT) {
+ error = xstrdup("Only select groups can have bucket weights.");
+ goto out;
+ }
+ }
+ if (gm->type == OFPGT11_INDIRECT && !list_is_short(&gm->buckets)) {
+ error = xstrdup("Indirect groups can have at most one bucket.");
+ goto out;
+ }
+
+ return NULL;
+ out:
+ ofputil_bucket_list_destroy(&gm->buckets);
+ return error;
+}
+
+char * WARN_UNUSED_RESULT
+parse_ofp_group_mod_str(struct ofputil_group_mod *gm, uint16_t command,
+ const char *str_,
+ enum ofputil_protocol *usable_protocols)
+{
+ char *string = xstrdup(str_);
+ char *error = parse_ofp_group_mod_str__(gm, command, string,
+ usable_protocols);
+ free(string);
+
+ if (error) {
+ ofputil_bucket_list_destroy(&gm->buckets);
+ }
+ return error;
+}
+
+char * WARN_UNUSED_RESULT
+parse_ofp_group_mod_file(const char *file_name, uint16_t 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_OF11_UP;
+ while (!ds_get_preprocessed_line(&s, stream, &line_number)) {
+ enum ofputil_protocol usable;
+ char *error;
+
+ if (*n_gms >= allocated_gms) {
+ *gms = x2nrealloc(*gms, &allocated_gms, sizeof **gms);
+ }
+ error = parse_ofp_group_mod_str(&(*gms)[*n_gms], command, ds_cstr(&s),
+ &usable);
+ if (error) {
+ size_t i;
+
+ for (i = 0; i < *n_gms; i++) {
+ ofputil_bucket_list_destroy(&(*gms)[i].buckets);
+ }
+ free(*gms);
+ *gms = NULL;
+ *n_gms = 0;
+
+ ds_destroy(&s);
+ if (stream != stdin) {
+ fclose(stream);
+ }
+
+ return xasprintf("%s:%d: %s", file_name, line_number, error);
+ }
+ *usable_protocols &= usable;
+ *n_gms += 1;
+ }
+
+ ds_destroy(&s);
+ if (stream != stdin) {
+ fclose(stream);
+ }
+ return NULL;
+}
struct ofputil_flow_mod;
struct ofputil_flow_monitor_request;
struct ofputil_flow_stats_request;
+struct ofputil_group_mod;
struct ofputil_meter_mod;
enum ofputil_protocol;
enum ofputil_protocol *usable_protocols)
WARN_UNUSED_RESULT;
+char *parse_ofp_group_mod_file(const char *file_name, uint16_t command,
+ struct ofputil_group_mod **gms, size_t *n_gms,
+ enum ofputil_protocol *usable_protocols)
+ WARN_UNUSED_RESULT;
+
+char *parse_ofp_group_mod_str(struct ofputil_group_mod *, uint16_t command,
+ const char *string,
+ enum ofputil_protocol *usable_protocols)
+ WARN_UNUSED_RESULT;
+
#endif /* ofp-parse.h */
ds_put_cstr(string, "NOT IMPLEMENTED YET!\n");
}
+static void
+ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
+ struct list *p_buckets)
+{
+ static const char *type_str[] = { "all", "select", "indirect",
+ "ff", "unknown" };
+ struct ofputil_bucket *bucket;
+
+ ds_put_format(s, "group_id=%"PRIu32",type=%s",
+ group_id, type_str[type > 4 ? 4 : type]);
+ if (!p_buckets) {
+ return;
+ }
+
+ LIST_FOR_EACH (bucket, list_node, p_buckets) {
+ ds_put_cstr(s, ",bucket=");
+
+ if (bucket->weight != 1) {
+ ds_put_format(s, "weight:%"PRIu16",", bucket->weight);
+ }
+ if (bucket->watch_port != OFPP_NONE) {
+ ds_put_format(s, "watch_port:%"PRIu32",", bucket->watch_port);
+ }
+ if (bucket->watch_group != OFPG11_ANY) {
+ ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group);
+ }
+
+ ofpacts_format(bucket->ofpacts, bucket->ofpacts_len, s);
+ }
+}
+
+static void
+ofp_print_group_desc(struct ds *s, const struct ofp_header *oh)
+{
+ struct ofpbuf b;
+
+ ofpbuf_use_const(&b, oh, ntohs(oh->length));
+ for (;;) {
+ struct ofputil_group_desc gd;
+ int retval;
+
+ retval = ofputil_decode_group_desc_reply(&gd, &b);
+ if (retval) {
+ if (retval != EOF) {
+ ds_put_cstr(s, " ***parse error***");
+ }
+ break;
+ }
+
+ ds_put_char(s, '\n');
+ ds_put_char(s, ' ');
+ ofp_print_group(s, gd.group_id, gd.type, &gd.buckets);
+ }
+}
+
+static void
+ofp_print_ofpst_group_request(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) {
+ ofp_print_error(string, error);
+ return;
+ }
+
+ ds_put_cstr(string, " group_id=");
+ ofputil_format_group(group_id, string);
+}
+
+static void
+ofp_print_group_stats(struct ds *s, const struct ofp_header *oh)
+{
+ struct ofpbuf b;
+ uint32_t bucket_i;
+
+ ofpbuf_use_const(&b, 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***");
+ }
+ 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 (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);
+ }
+ }
+ }
+}
+
+static void
+ofp_print_group_features(struct ds *string, const struct ofp_header *oh)
+{
+ struct ofputil_group_features features;
+
+ 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);
+
+ if (features.types & (1u << OFPGT11_ALL)) {
+ ds_put_format(string, " All group :\n");
+ ds_put_format(string,
+ " max_groups = %#"PRIx32" actions=0x%08"PRIx32"\n",
+ features.max_groups[0], features.actions[0]);
+ }
+
+ if (features.types & (1u << OFPGT11_SELECT)) {
+ ds_put_format(string, " Select group :\n");
+ ds_put_format(string, " max_groups = %#"PRIx32" "
+ "actions=0x%08"PRIx32"\n",
+ features.max_groups[1], features.actions[1]);
+ }
+
+ if (features.types & (1u << OFPGT11_INDIRECT)) {
+ ds_put_format(string, " Indirect group :\n");
+ ds_put_format(string, " max_groups = %#"PRIx32" "
+ "actions=0x%08"PRIx32"\n",
+ features.max_groups[2], features.actions[2]);
+ }
+
+ if (features.types & (1u << OFPGT11_FF)) {
+ ds_put_format(string, " Fast Failover group :\n");
+ ds_put_format(string, " max_groups = %#"PRIx32" "
+ "actions=0x%08"PRIx32"\n",
+ features.max_groups[3], features.actions[3]);
+ }
+}
+
+static void
+ofp_print_group_mod(struct ds *s, const struct ofp_header *oh)
+{
+ struct ofputil_group_mod gm;
+ int error;
+
+ error = ofputil_decode_group_mod(oh, &gm);
+ if (error) {
+ ofp_print_error(s, error);
+ return;
+ }
+
+ 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_DELETE:
+ ds_put_cstr(s, "DEL");
+ break;
+
+ default:
+ ds_put_format(s, "cmd:%"PRIu16"", gm.command);
+ }
+ ds_put_char(s, ' ');
+
+ ofp_print_group(s, gm.group_id, gm.type, &gm.buckets);
+}
+
static void
ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
struct ds *string, int verbosity)
ofp_header_to_string__(oh, raw, string);
switch (ofptype_from_ofpraw(raw)) {
- /* FIXME: Change the following once they are implemented: */
- case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
- case OFPTYPE_QUEUE_GET_CONFIG_REPLY:
- case OFPTYPE_GET_ASYNC_REQUEST:
- case OFPTYPE_GET_ASYNC_REPLY:
case OFPTYPE_GROUP_STATS_REQUEST:
+ ofp_print_stats_request(string, oh);
+ ofp_print_ofpst_group_request(string, oh);
+ break;
+
case OFPTYPE_GROUP_STATS_REPLY:
+ ofp_print_group_stats(string, oh);
+ break;
+
case OFPTYPE_GROUP_DESC_STATS_REQUEST:
+ ofp_print_stats_request(string, oh);
+ break;
+
case OFPTYPE_GROUP_DESC_STATS_REPLY:
+ ofp_print_group_desc(string, oh);
+ break;
+
case OFPTYPE_GROUP_FEATURES_STATS_REQUEST:
+ ofp_print_stats_request(string, oh);
+ break;
+
case OFPTYPE_GROUP_FEATURES_STATS_REPLY:
+ ofp_print_group_features(string, oh);
+ break;
+
+ case OFPTYPE_GROUP_MOD:
+ ofp_print_group_mod(string, oh);
+ break;
+
+ case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
+ case OFPTYPE_QUEUE_GET_CONFIG_REPLY:
+ case OFPTYPE_GET_ASYNC_REQUEST:
+ case OFPTYPE_GET_ASYNC_REPLY:
case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
ofp_print_not_implemented(string);
if (error) {
return error;
}
+ fm->out_group = ntohl(ofm->out_group);
+
if ((ofm->command == OFPFC_DELETE
|| ofm->command == OFPFC_DELETE_STRICT)
&& ofm->out_group != htonl(OFPG_ANY)) {
fm->hard_timeout = ntohs(ofm->hard_timeout);
fm->buffer_id = ntohl(ofm->buffer_id);
fm->out_port = u16_to_ofp(ntohs(ofm->out_port));
+ fm->out_group = OFPG11_ANY;
raw_flags = ofm->flags;
} else if (raw == OFPRAW_NXT_FLOW_MOD) {
/* Nicira extended flow_mod. */
fm->hard_timeout = ntohs(nfm->hard_timeout);
fm->buffer_id = ntohl(nfm->buffer_id);
fm->out_port = u16_to_ofp(ntohs(nfm->out_port));
+ fm->out_group = OFPG11_ANY;
raw_flags = nfm->flags;
} else {
NOT_REACHED();
ofm->priority = htons(fm->priority);
ofm->buffer_id = htonl(fm->buffer_id);
ofm->out_port = ofputil_port_to_ofp11(fm->out_port);
- ofm->out_group = htonl(OFPG11_ANY);
+ ofm->out_group = htonl(fm->out_group);
ofm->flags = raw_flags;
ofputil_put_ofp11_match(msg, &fm->match, protocol);
ofpacts_put_openflow11_instructions(fm->ofpacts, fm->ofpacts_len, msg);
fsr->aggregate = aggregate;
ofputil_match_from_ofp10_match(&ofsr->match, &fsr->match);
fsr->out_port = u16_to_ofp(ntohs(ofsr->out_port));
+ fsr->out_group = OFPG11_ANY;
fsr->table_id = ofsr->table_id;
fsr->cookie = fsr->cookie_mask = htonll(0);
if (error) {
return error;
}
- if (ofsr->out_group != htonl(OFPG11_ANY)) {
- return OFPERR_OFPFMFC_UNKNOWN;
- }
+ fsr->out_group = ntohl(ofsr->out_group);
fsr->cookie = ofsr->cookie;
fsr->cookie_mask = ofsr->cookie_mask;
error = ofputil_pull_ofp11_match(b, &fsr->match, NULL);
fsr->aggregate = aggregate;
fsr->out_port = u16_to_ofp(ntohs(nfsr->out_port));
+ fsr->out_group = OFPG11_ANY;
fsr->table_id = nfsr->table_id;
return 0;
ofsr = ofpbuf_put_zeros(msg, sizeof *ofsr);
ofsr->table_id = fsr->table_id;
ofsr->out_port = ofputil_port_to_ofp11(fsr->out_port);
- ofsr->out_group = htonl(OFPG11_ANY);
+ ofsr->out_group = htonl(fsr->out_group);
ofsr->cookie = fsr->cookie;
ofsr->cookie_mask = fsr->cookie_mask;
ofputil_put_ofp11_match(msg, &fsr->match, protocol);
/* Appends an OFPST_FLOW or NXST_FLOW reply that contains the data in 'fs' to
* those already present in the list of ofpbufs in 'replies'. 'replies' should
- * have been initialized with ofputil_start_stats_reply(). */
+ * have been initialized with ofpmp_init(). */
void
ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
struct list *replies)
}
}
+/* 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 = OFPG11_ANY;
+ } else if (!strcasecmp(s, "all")) {
+ *group_idp = OFPG11_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. OFPG11_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. OFPG11_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 OFPG11_ALL:
+ ovs_strlcpy(namebuf, "ALL", bufsize);
+ break;
+
+ case OFPG11_ANY:
+ ovs_strlcpy(namebuf, "ANY", bufsize);
+ break;
+
+ default:
+ snprintf(namebuf, bufsize, "%"PRIu32, group_id);
+ break;
+ }
+}
+
/* Given a buffer 'b' that contains an array of OpenFlow ports of type
* 'ofp_version', tries to pull the first element from the array. If
* successful, initializes '*pp' with an abstract representation of the
}
}
+/* Frees all of the "struct ofputil_bucket"s in the 'buckets' list. */
+void
+ofputil_bucket_list_destroy(struct list *buckets)
+{
+ struct ofputil_bucket *bucket, *next_bucket;
+
+ LIST_FOR_EACH_SAFE (bucket, next_bucket, list_node, buckets) {
+ list_remove(&bucket->list_node);
+ free(bucket->ofpacts);
+ free(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 *request;
+
+ switch (ofp_version) {
+ case OFP10_VERSION:
+ ovs_fatal(0, "dump-group-stats needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION: {
+ struct ofp11_group_stats_request *req;
+ request = ofpraw_alloc(OFPRAW_OFPST11_GROUP_REQUEST, ofp_version, 0);
+ req = ofpbuf_put_zeros(request, sizeof *req);
+ req->group_id = htonl(group_id);
+ break;
+ }
+ default:
+ NOT_REACHED();
+ }
+
+ return request;
+}
+
+/* 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.)
+ *
+ * Group descriptions include the bucket and action configuration for each
+ * group. */
+struct ofpbuf *
+ofputil_encode_group_desc_request(enum ofp_version ofp_version)
+{
+ struct ofpbuf *request;
+
+ switch (ofp_version) {
+ case OFP10_VERSION:
+ ovs_fatal(0, "dump-groups needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION: {
+ request = ofpraw_alloc(OFPRAW_OFPST11_GROUP_DESC_REQUEST, ofp_version, 0);
+ break;
+ }
+ default:
+ NOT_REACHED();
+ }
+
+ return request;
+}
+
+static void *
+ofputil_group_stats_to_ofp11(const struct ofputil_group_stats *ogs,
+ size_t base_len, struct list *replies)
+{
+ struct ofp11_bucket_counter *bc11;
+ struct ofp11_group_stats *gs11;
+ size_t length;
+ int i;
+
+ length = base_len + sizeof(struct ofp11_bucket_counter) * ogs->n_buckets;
+
+ gs11 = ofpmp_append(replies, length);
+ memset(gs11, 0, base_len);
+ gs11->length = htons(length);
+ gs11->group_id = htonl(ogs->group_id);
+ gs11->ref_count = htonl(ogs->ref_count);
+ gs11->packet_count = htonll(ogs->packet_count);
+ gs11->byte_count = htonll(ogs->byte_count);
+
+ bc11 = (void *) (((uint8_t *) gs11) + base_len);
+ for (i = 0; i < ogs->n_buckets; i++) {
+ const struct bucket_counter *obc = &ogs->bucket_stats[i];
+
+ bc11[i].packet_count = htonll(obc->packet_count);
+ bc11[i].byte_count = htonll(obc->byte_count);
+ }
+
+ return gs11;
+}
+
+static void
+ofputil_append_of13_group_stats(const struct ofputil_group_stats *ogs,
+ struct list *replies)
+{
+ struct ofp13_group_stats *gs13;
+
+ gs13 = ofputil_group_stats_to_ofp11(ogs, sizeof *gs13, replies);
+ gs13->duration_sec = htonl(ogs->duration_sec);
+ gs13->duration_nsec = htonl(ogs->duration_nsec);
+}
+
+/* Encodes 'ogs' 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 list *replies,
+ const struct ofputil_group_stats *ogs)
+{
+ struct ofpbuf *msg = ofpbuf_from_list(list_back(replies));
+ struct ofp_header *oh = msg->data;
+
+ switch ((enum ofp_version)oh->version) {
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ ofputil_group_stats_to_ofp11(ogs, sizeof(struct ofp11_group_stats),
+ replies);
+ break;
+
+ case OFP13_VERSION:
+ ofputil_append_of13_group_stats(ogs, replies);
+ break;
+
+ case OFP10_VERSION:
+ default:
+ 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)
+{
+ struct ofpbuf *request = NULL;
+
+ switch (ofp_version) {
+ case OFP10_VERSION:
+ case OFP11_VERSION:
+ ovs_fatal(0, "dump-group-features needs OpenFlow 1.2 or later "
+ "(\'-O OpenFlow12\')");
+ case OFP12_VERSION:
+ case OFP13_VERSION: {
+ request = ofpraw_alloc(OFPRAW_OFPST12_GROUP_FEATURES_REQUEST,
+ ofp_version, 0);
+ break;
+ }
+ default:
+ NOT_REACHED();
+ }
+
+ return request;
+}
+
+/* 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 ofp12_group_features_stats *ogf;
+ struct ofpbuf *reply;
+
+ reply = ofpraw_alloc_xid(OFPRAW_OFPST12_GROUP_FEATURES_REPLY,
+ request->version, request->xid, 0);
+ ogf = ofpbuf_put_zeros(reply, sizeof *ogf);
+ ogf->types = htonl(features->types);
+ ogf->capabilities = htonl(features->capabilities);
+ ogf->max_groups[0] = htonl(features->max_groups[0]);
+ ogf->max_groups[1] = htonl(features->max_groups[1]);
+ ogf->max_groups[2] = htonl(features->max_groups[2]);
+ ogf->max_groups[3] = htonl(features->max_groups[3]);
+ ogf->actions[0] = htonl(features->actions[0]);
+ ogf->actions[1] = htonl(features->actions[1]);
+ ogf->actions[2] = htonl(features->actions[2]);
+ ogf->actions[3] = htonl(features->actions[3]);
+
+ 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);
+
+ features->types = ntohl(ogf->types);
+ features->capabilities = ntohl(ogf->capabilities);
+ features->max_groups[0] = ntohl(ogf->max_groups[0]);
+ features->max_groups[1] = ntohl(ogf->max_groups[1]);
+ features->max_groups[2] = ntohl(ogf->max_groups[2]);
+ features->max_groups[3] = ntohl(ogf->max_groups[3]);
+ features->actions[0] = ntohl(ogf->actions[0]);
+ features->actions[1] = ntohl(ogf->actions[1]);
+ features->actions[2] = ntohl(ogf->actions[2]);
+ features->actions[3] = ntohl(ogf->actions[3]);
+}
+
+/* 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'.
+ *
+ * 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;
+
+ error = (msg->l2
+ ? ofpraw_decode(&raw, msg->l2)
+ : 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_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 {
+ NOT_REACHED();
+ }
+
+ if (!ogs11) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "%s reply has %zu 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(&bad_ofmsg_rl, "%s reply claims invalid length %zu",
+ 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(&bad_ofmsg_rl, "%s reply has %zu leftover bytes at end",
+ ofpraw_get_name(raw), msg->size);
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ 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;
+}
+
+/* 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,
+ struct list *buckets,
+ struct list *replies)
+{
+ struct ofpbuf *reply = ofpbuf_from_list(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) {
+ struct ofp11_bucket *ob;
+ size_t start_ob;
+
+ start_ob = reply->size;
+ ofpbuf_put_zeros(reply, sizeof *ob);
+ ofpacts_put_openflow11_actions(bucket->ofpacts,
+ bucket->ofpacts_len, reply);
+
+ ob = ofpbuf_at_assert(reply, start_ob, sizeof *ob);
+ ob->len = htons(reply->size - start_ob);
+ ob->weight = htons(bucket->weight);
+ ob->watch_port = ofputil_port_to_ofp11(bucket->watch_port);
+ ob->watch_group = htonl(bucket->watch_group);
+ }
+ 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 enum ofperr
+ofputil_pull_buckets(struct ofpbuf *msg, size_t buckets_length,
+ struct list *buckets)
+{
+ struct ofp11_bucket *ob;
+
+ 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(&bad_ofmsg_rl, "buckets end with %zu leftover bytes",
+ buckets_length);
+ }
+
+ ob_len = ntohs(ob->len);
+ if (ob_len < sizeof *ob) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OpenFlow message bucket length "
+ "%zu is not valid", ob_len);
+ return OFPERR_OFPGMFC_BAD_BUCKET;
+ } else if (ob_len > buckets_length) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OpenFlow message bucket length "
+ "%zu exceeds remaining buckets data size %zu",
+ ob_len, buckets_length);
+ return OFPERR_OFPGMFC_BAD_BUCKET;
+ }
+ buckets_length -= ob_len;
+
+ ofpbuf_init(&ofpacts, 0);
+ error = ofpacts_pull_openflow11_actions(msg, ob_len - sizeof *ob,
+ &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);
+ return OFPERR_OFPGMFC_BAD_WATCH;
+ }
+ bucket->watch_group = ntohl(ob->watch_group);
+ bucket->ofpacts = ofpbuf_steal_data(&ofpacts);
+ bucket->ofpacts_len = ofpacts.size;
+ list_push_back(buckets, &bucket->list_node);
+ }
+
+ return 0;
+}
+
+/* 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)
+{
+ struct ofp11_group_desc_stats *ogds;
+ size_t length;
+
+ if (!msg->l2) {
+ ofpraw_pull_assert(msg);
+ }
+
+ if (!msg->size) {
+ return EOF;
+ }
+
+ ogds = ofpbuf_try_pull(msg, sizeof *ogds);
+ if (!ogds) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST11_GROUP_DESC reply has %zu "
+ "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(&bad_ofmsg_rl, "OFPST11_GROUP_DESC reply claims invalid "
+ "length %zu", length);
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ return ofputil_pull_buckets(msg, length - sizeof *ogds, &gd->buckets);
+}
+
+/* Converts abstract group mod 'gm' into a message for OpenFlow version
+ * 'ofp_version' and returns the message. */
+struct ofpbuf *
+ofputil_encode_group_mod(enum ofp_version ofp_version,
+ const struct ofputil_group_mod *gm)
+{
+ struct ofpbuf *b;
+ struct ofp11_group_mod *ogm;
+ size_t start_ogm;
+ size_t start_bucket;
+ struct ofputil_bucket *bucket;
+ struct ofp11_bucket *ob;
+
+ switch (ofp_version) {
+ case OFP10_VERSION: {
+ if (gm->command == OFPGC11_ADD) {
+ ovs_fatal(0, "add-group needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ } else if (gm->command == OFPGC11_MODIFY) {
+ ovs_fatal(0, "mod-group needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ } else {
+ ovs_fatal(0, "del-groups needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ }
+ }
+
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION: {
+ b = ofpraw_alloc(OFPRAW_OFPT11_GROUP_MOD, ofp_version, 0);
+ start_ogm = b->size;
+ ofpbuf_put_uninit(b, sizeof *ogm);
+
+ LIST_FOR_EACH (bucket, list_node, &gm->buckets) {
+ start_bucket = b->size;
+ ofpbuf_put_uninit(b, sizeof *ob);
+ if (bucket->ofpacts && bucket->ofpacts_len) {
+ ofpacts_put_openflow11_actions(bucket->ofpacts,
+ bucket->ofpacts_len, b);
+ }
+ ob = ofpbuf_at_assert(b, start_bucket, sizeof *ob);
+ ob->len = htons(b->size - start_bucket);;
+ ob->weight = htons(bucket->weight);
+ ob->watch_port = ofputil_port_to_ofp11(bucket->watch_port);
+ ob->watch_group = htonl(bucket->watch_group);
+ }
+ ogm = ofpbuf_at_assert(b, start_ogm, sizeof *ogm);
+ ogm->command = htons(gm->command);
+ ogm->type = gm->type;
+ ogm->pad = 0;
+ ogm->group_id = htonl(gm->group_id);
+
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ }
+
+ return b;
+}
+
+/* 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)
+{
+ const struct ofp11_group_mod *ogm;
+ struct ofpbuf msg;
+
+ ofpbuf_use_const(&msg, oh, ntohs(oh->length));
+ ofpraw_pull_assert(&msg);
+
+ ogm = ofpbuf_pull(&msg, sizeof *ogm);
+ gm->command = ntohs(ogm->command);
+ gm->type = ogm->type;
+ gm->group_id = ntohl(ogm->group_id);
+
+ return ofputil_pull_buckets(&msg, msg.size, &gm->buckets);
+}
+
/* Parse a queue status request message into 'oqsr'.
* Returns 0 if successful, otherwise an OFPERR_* number. */
enum ofperr
//OFPAT11_ACTION(OFPAT11_SET_NW_TTL, ofp11_action_nw_ttl, 0, "set_nw_ttl")
OFPAT11_ACTION(OFPAT11_DEC_NW_TTL, ofp_action_header, 0, NULL)
OFPAT11_ACTION(OFPAT12_SET_FIELD, ofp12_action_set_field, 1, "set_field")
+OFPAT11_ACTION(OFPAT11_GROUP, ofp11_action_group, 0, "group")
#ifndef NXAST_ACTION
#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME)
void ofputil_port_to_string(ofp_port_t, char namebuf[OFP_MAX_PORT_NAME_LEN],
size_t bufsize);
+/* Group numbers. */
+enum { MAX_GROUP_NAME_LEN = INT_STRLEN(uint32_t) };
+bool ofputil_group_from_string(const char *, uint32_t *group_id);
+void ofputil_format_group(uint32_t group_id, struct ds *);
+void ofputil_group_to_string(uint32_t group_id,
+ char namebuf[MAX_GROUP_NAME_LEN + 1],
+ size_t bufsize);
+
/* Converting OFPFW10_NW_SRC_MASK and OFPFW10_NW_DST_MASK wildcard bit counts
* to and from IP bitmasks. */
ovs_be32 ofputil_wcbits_to_netmask(int wcbits);
uint16_t hard_timeout;
uint32_t buffer_id;
ofp_port_t out_port;
+ uint32_t out_group;
enum ofputil_flow_mod_flags flags;
struct ofpact *ofpacts; /* Series of "struct ofpact"s. */
size_t ofpacts_len; /* Length of ofpacts, in bytes. */
ovs_be64 cookie;
ovs_be64 cookie_mask;
ofp_port_t out_port;
+ uint32_t out_group;
uint8_t table_id;
};
void ofputil_append_queue_stat(struct list *replies,
const struct ofputil_queue_stats *oqs);
+/* Bucket for use in groups. */
+struct ofputil_bucket {
+ struct list list_node;
+ uint16_t weight; /* Relative weight, for "select" groups. */
+ ofp_port_t watch_port; /* Port whose state affects whether this bucket
+ * is live. Only required for fast failover
+ * groups. */
+ uint32_t watch_group; /* Group whose state affects whether this
+ * bucket is live. Only required for fast
+ * failover groups. */
+ struct ofpact *ofpacts; /* Series of "struct ofpact"s. */
+ size_t ofpacts_len; /* Length of ofpacts, in bytes. */
+};
+
+/* Protocol-independent group_mod. */
+struct ofputil_group_mod {
+ uint16_t command; /* One of OFPGC11_*. */
+ uint8_t type; /* One of OFPGT11_*. */
+ uint32_t group_id; /* Group identifier. */
+ struct list buckets; /* Contains "struct ofputil_bucket"s. */
+};
+
+struct bucket_counter {
+ uint64_t packet_count; /* Number of packets processed by bucket. */
+ uint64_t byte_count; /* Number of bytes processed by bucket. */
+};
+
+/* Group stats reply, independent of protocol. */
+struct ofputil_group_stats {
+ uint32_t group_id; /* Group identifier. */
+ uint32_t ref_count;
+ uint64_t packet_count; /* Packet count, UINT64_MAX if unknown. */
+ uint64_t byte_count; /* Byte count, UINT64_MAX if unknown. */
+ uint32_t duration_sec; /* UINT32_MAX if unknown. */
+ uint32_t duration_nsec;
+ uint32_t n_buckets;
+ struct bucket_counter bucket_stats[16];
+};
+
+/* Group features reply, independent of protocol. */
+struct ofputil_group_features {
+ uint32_t types; /* Bitmap of OFPGT_* values supported. */
+ uint32_t capabilities; /* Bitmap of OFPGFC12_* capability supported. */
+ uint32_t max_groups[4]; /* Maximum number of groups for each type. */
+ uint32_t actions[4]; /* Bitmaps of OFPAT_* that are supported. */
+};
+
+/* Group desc reply, independent of protocol. */
+struct ofputil_group_desc {
+ uint8_t type; /* One of OFPGT_*. */
+ uint32_t group_id; /* Group identifier. */
+ struct list buckets; /* Contains "struct ofputil_bucket"s. */
+};
+
+void ofputil_bucket_list_destroy(struct list *buckets);
+
+struct ofpbuf *ofputil_encode_group_stats_request(enum ofp_version,
+ uint32_t group_id);
+enum ofperr ofputil_decode_group_stats_request(
+ const struct ofp_header *request, uint32_t *group_id);
+void ofputil_append_group_stats(struct list *replies,
+ const struct ofputil_group_stats *);
+struct ofpbuf *ofputil_encode_group_features_request(enum ofp_version);
+struct ofpbuf *ofputil_encode_group_features_reply(
+ const struct ofputil_group_features *, const struct ofp_header *request);
+void ofputil_decode_group_features_reply(const struct ofp_header *,
+ struct ofputil_group_features *);
+struct ofpbuf *ofputil_encode_group_mod(enum ofp_version ofp_version,
+ const struct ofputil_group_mod *gm);
+
+enum ofperr ofputil_decode_group_mod(const struct ofp_header *,
+ struct ofputil_group_mod *);
+
+int ofputil_decode_group_stats_reply(struct ofpbuf *,
+ struct ofputil_group_stats *);
+
+int ofputil_decode_group_desc_reply(struct ofputil_group_desc *,
+ struct ofpbuf *);
+
+void ofputil_append_group_desc_reply(const struct ofputil_group_desc *,
+ struct list *buckets,
+ struct list *replies);
+struct ofpbuf *ofputil_encode_group_desc_request(enum ofp_version);
+
#endif /* ofp-util.h */
case OFPTYPE_PORT_STATUS:
case OFPTYPE_PACKET_OUT:
case OFPTYPE_FLOW_MOD:
+ case OFPTYPE_GROUP_MOD:
case OFPTYPE_PORT_MOD:
case OFPTYPE_METER_MOD:
case OFPTYPE_BARRIER_REQUEST:
ofpact_get_OUTPUT(a)->max_len, true);
break;
+ case OFPACT_GROUP:
+ /* XXX not yet implemented */
+ break;
+
case OFPACT_CONTROLLER:
controller = ofpact_get_CONTROLLER(a);
execute_controller_action(ctx, controller->max_len,
NULL, /* meter_set */
NULL, /* meter_get */
NULL, /* meter_del */
+ NULL, /* group_alloc */
+ NULL, /* group_construct */
+ NULL, /* group_destruct */
+ NULL, /* group_dealloc */
+ NULL, /* group_modify */
+ NULL, /* group_get_stats */
};
unsigned long int *vlan_bitmap; /* 4096-bit bitmap of in-use VLANs. */
bool vlans_changed; /* True if new VLANs are in use. */
int min_mtu; /* Current MTU of non-internal ports. */
+
+ /* Groups. */
+ struct ovs_rwlock groups_rwlock;
+ struct hmap groups OVS_GUARDED; /* Contains "struct ofgroup"s. */
+ uint32_t n_groups[4] OVS_GUARDED; /* # of existing groups of each type. */
+ struct ofputil_group_features ogf;
};
void ofproto_init_tables(struct ofproto *, int n_tables);
void ofoperation_complete(struct ofoperation *, enum ofperr);
bool ofoperation_has_out_port(const struct ofoperation *, ofp_port_t out_port);
+bool ofproto_rule_has_out_group(const struct rule *, uint32_t group_id);
bool ofproto_rule_is_hidden(const struct rule *);
+/* A group within a "struct ofproto".
+ *
+ * With few exceptions, ofproto implementations may look at these fields but
+ * should not modify them. */
+struct ofgroup {
+ /* The rwlock is used to prevent groups from being deleted while child
+ * threads are using them to xlate flows. A read lock means the
+ * group is currently being used. A write lock means the group is
+ * in the process of being deleted or updated. Note that since
+ * a read lock on the groups container is held while searching, and
+ * a group is ever write locked only while holding a write lock
+ * on the container, the user's of groups will never face a group
+ * in the write locked state. */
+ struct ovs_rwlock rwlock;
+ struct hmap_node hmap_node; /* In struct ofproto's "groups" hmap. */
+ struct ofproto *ofproto; /* The ofproto that contains this group. */
+ uint32_t group_id;
+ uint8_t type; /* One of OFPGT_*. */
+
+ long long int created; /* Creation time. */
+ long long int modified; /* Time of last modification. */
+
+ struct list buckets; /* Contains "struct ofputil_bucket"s. */
+ uint32_t n_buckets;
+};
+
+bool ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id,
+ struct ofgroup **group)
+ OVS_TRY_RDLOCK(true, (*group)->rwlock);
+
+void ofproto_group_release(struct ofgroup *group)
+ OVS_RELEASES(group->rwlock);
+
/* ofproto class structure, to be defined by each ofproto implementation.
*
*
* Data Structures
* ===============
*
- * These functions work primarily with three different kinds of data
+ * These functions work primarily with four different kinds of data
* structures:
*
* - "struct ofproto", which represents an OpenFlow switch.
*
* - "struct rule", which represents an OpenFlow flow within an ofproto.
*
+ * - "struct ofgroup", which represents an OpenFlow 1.1+ group within an
+ * ofproto.
+ *
* Each of these data structures contains all of the implementation-independent
* generic state for the respective concept, called the "base" state. None of
* them contains any extra space for ofproto implementations to use. Instead,
* ofproto ->alloc ->construct ->destruct ->dealloc
* ofport ->port_alloc ->port_construct ->port_destruct ->port_dealloc
* rule ->rule_alloc ->rule_construct ->rule_destruct ->rule_dealloc
+ * group ->group_alloc ->group_construct ->group_destruct ->group_dealloc
*
- * "ofproto" and "ofport" have this exact life cycle. The "rule" data
- * structure also follow this life cycle with some additional elaborations
+ * "ofproto", "ofport", and "group" have this exact life cycle. The "rule"
+ * data structure also follow this life cycle with some additional elaborations
* described under "Rule Life Cycle" below.
*
* Any instance of a given data structure goes through the following life
/* Deletes a meter, making the 'ofproto_meter_id' invalid for any
* further calls. */
void (*meter_del)(struct ofproto *, ofproto_meter_id);
+
+
+/* ## -------------------- ## */
+/* ## OpenFlow 1.1+ groups ## */
+/* ## -------------------- ## */
+
+ struct ofgroup *(*group_alloc)(void);
+ enum ofperr (*group_construct)(struct ofgroup *);
+ void (*group_destruct)(struct ofgroup *);
+ void (*group_dealloc)(struct ofgroup *);
+
+ enum ofperr (*group_modify)(struct ofgroup *, struct ofgroup *victim);
+
+ enum ofperr (*group_get_stats)(const struct ofgroup *,
+ struct ofputil_group_stats *);
};
extern const struct ofproto_class ofproto_dpif_class;
static void delete_flow__(struct rule *rule, struct ofopgroup *,
enum ofp_flow_removed_reason)
OVS_RELEASES(rule->evict);
+static enum ofperr add_group(struct ofproto *, struct ofputil_group_mod *);
static bool handle_openflow(struct ofconn *, const struct ofpbuf *);
static enum ofperr handle_flow_mod__(struct ofproto *, struct ofconn *,
struct ofputil_flow_mod *,
ofproto->vlan_bitmap = NULL;
ofproto->vlans_changed = false;
ofproto->min_mtu = INT_MAX;
+ ovs_rwlock_init(&ofproto->groups_rwlock);
+ hmap_init(&ofproto->groups);
error = ofproto->ofproto_class->construct(ofproto);
if (error) {
}
}
+static void delete_group(struct ofproto *ofproto, uint32_t group_id);
+
static void
ofproto_destroy__(struct ofproto *ofproto)
{
free(ofproto->meters);
}
+ delete_group(ofproto, OFPG_ALL);
+ ovs_rwlock_destroy(&ofproto->groups_rwlock);
+ hmap_destroy(&ofproto->groups);
+
connmgr_destroy(ofproto->connmgr);
hmap_remove(&all_ofprotos, &ofproto->hmap_node);
|| ofpacts_output_to_port(rule->ofpacts, rule->ofpacts_len, port));
}
+/* Returns true if 'rule' has group and equals group_id. */
+bool
+ofproto_rule_has_out_group(const struct rule *rule, uint32_t group_id)
+{
+ return (group_id == OFPG11_ANY
+ || ofpacts_output_to_group(rule->ofpacts, rule->ofpacts_len, group_id));
+}
+
/* Returns true if a rule related to 'op' has an OpenFlow OFPAT_OUTPUT or
* OFPAT_ENQUEUE action that outputs to 'out_port'. */
bool
collect_rules_loose(struct ofproto *ofproto, uint8_t table_id,
const struct match *match,
ovs_be64 cookie, ovs_be64 cookie_mask,
- ofp_port_t out_port, struct list *rules)
+ ofp_port_t out_port, uint32_t out_group,
+ struct list *rules)
{
struct oftable *table;
struct cls_rule cr;
}
if (!ofproto_rule_is_hidden(rule)
&& ofproto_rule_has_out_port(rule, out_port)
+ && ofproto_rule_has_out_group(rule, out_group)
&& !((rule->flow_cookie ^ cookie) & cookie_mask)) {
list_push_back(rules, &rule->ofproto_node);
}
collect_rules_strict(struct ofproto *ofproto, uint8_t table_id,
const struct match *match, unsigned int priority,
ovs_be64 cookie, ovs_be64 cookie_mask,
- ofp_port_t out_port, struct list *rules)
+ ofp_port_t out_port, uint32_t out_group,
+ struct list *rules)
{
struct oftable *table;
struct cls_rule cr;
}
if (!ofproto_rule_is_hidden(rule)
&& ofproto_rule_has_out_port(rule, out_port)
+ && ofproto_rule_has_out_group(rule, out_group)
&& !((rule->flow_cookie ^ cookie) & cookie_mask)) {
list_push_back(rules, &rule->ofproto_node);
}
error = collect_rules_loose(ofproto, fsr.table_id, &fsr.match,
fsr.cookie, fsr.cookie_mask,
- fsr.out_port, &rules);
+ fsr.out_port, fsr.out_group, &rules);
if (error) {
return error;
}
error = collect_rules_loose(ofproto, request.table_id, &request.match,
request.cookie, request.cookie_mask,
- request.out_port, &rules);
+ request.out_port, request.out_group, &rules);
if (error) {
return error;
}
error = collect_rules_loose(ofproto, fm->table_id, &fm->match,
fm->cookie, fm->cookie_mask,
- OFPP_ANY, &rules);
+ OFPP_ANY, OFPG11_ANY, &rules);
if (error) {
return error;
} else if (list_is_empty(&rules)) {
error = collect_rules_strict(ofproto, fm->table_id, &fm->match,
fm->priority, fm->cookie, fm->cookie_mask,
- OFPP_ANY, &rules);
-
+ OFPP_ANY, OFPG11_ANY, &rules);
if (error) {
return error;
} else if (list_is_empty(&rules)) {
error = collect_rules_loose(ofproto, fm->table_id, &fm->match,
fm->cookie, fm->cookie_mask,
- fm->out_port, &rules);
+ fm->out_port, fm->out_group, &rules);
return (error ? error
: !list_is_empty(&rules) ? delete_flows__(ofproto, ofconn, request,
&rules, OFPRR_DELETE)
error = collect_rules_strict(ofproto, fm->table_id, &fm->match,
fm->priority, fm->cookie, fm->cookie_mask,
- fm->out_port, &rules);
+ fm->out_port, fm->out_group, &rules);
return (error ? error
: list_is_singleton(&rules) ? delete_flows__(ofproto, ofconn,
request, &rules,
struct ofproto *ofproto = rule->ofproto;
struct classifier *cls = &ofproto->tables[rule->table_id].cls;
- ovs_assert(reason == OFPRR_HARD_TIMEOUT || reason == OFPRR_IDLE_TIMEOUT);
+ ovs_assert(reason == OFPRR_HARD_TIMEOUT || reason == OFPRR_IDLE_TIMEOUT
+ || reason == OFPRR_DELETE || reason == OFPRR_GROUP_DELETE);
ofproto_rule_send_removed(rule, reason);
ovs_rwlock_wrlock(&cls->rwlock);
return 0;
}
+bool
+ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id,
+ struct ofgroup **group)
+ OVS_TRY_RDLOCK(true, (*group)->rwlock)
+{
+ ovs_rwlock_rdlock(&ofproto->groups_rwlock);
+ HMAP_FOR_EACH_IN_BUCKET (*group, hmap_node,
+ hash_int(group_id, 0), &ofproto->groups) {
+ if ((*group)->group_id == group_id) {
+ ovs_rwlock_rdlock(&(*group)->rwlock);
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ return true;
+ }
+ }
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ return false;
+}
+
+void
+ofproto_group_release(struct ofgroup *group)
+ OVS_RELEASES(group->rwlock)
+{
+ ovs_rwlock_unlock(&group->rwlock);
+}
+
+static bool
+ofproto_group_write_lookup(const struct ofproto *ofproto, uint32_t group_id,
+ struct ofgroup **group)
+ OVS_TRY_WRLOCK(true, ofproto->groups_rwlock)
+ OVS_TRY_WRLOCK(true, (*group)->rwlock)
+{
+ ovs_rwlock_wrlock(&ofproto->groups_rwlock);
+ HMAP_FOR_EACH_IN_BUCKET (*group, hmap_node,
+ hash_int(group_id, 0), &ofproto->groups) {
+ if ((*group)->group_id == group_id) {
+ ovs_rwlock_wrlock(&(*group)->rwlock);
+ return true;
+ }
+ }
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ return false;
+}
+
+static bool
+ofproto_group_exists(const struct ofproto *ofproto, uint32_t group_id)
+ OVS_REQ_RDLOCK(ofproto->groups_rwlock)
+{
+ struct ofgroup *grp;
+
+ HMAP_FOR_EACH_IN_BUCKET (grp, hmap_node,
+ hash_int(group_id, 0), &ofproto->groups) {
+ if (grp->group_id == group_id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+append_group_stats(struct ofgroup *group, struct list *replies)
+ OVS_REQ_RDLOCK(group->rwlock)
+{
+ struct ofputil_group_stats ogs;
+ struct ofproto *ofproto = group->ofproto;
+ long long int now = time_msec();
+ int error;
+
+ error = (ofproto->ofproto_class->group_get_stats
+ ? ofproto->ofproto_class->group_get_stats(group, &ogs)
+ : EOPNOTSUPP);
+ if (error) {
+ ogs.ref_count = UINT32_MAX;
+ ogs.packet_count = UINT64_MAX;
+ ogs.byte_count = UINT64_MAX;
+ ogs.n_buckets = group->n_buckets;
+ memset(ogs.bucket_stats, 0xff,
+ ogs.n_buckets * sizeof *ogs.bucket_stats);
+ }
+
+ ogs.group_id = group->group_id;
+ calc_duration(group->created, now, &ogs.duration_sec, &ogs.duration_nsec);
+
+ ofputil_append_group_stats(replies, &ogs);
+}
+
+static enum ofperr
+handle_group_stats_request(struct ofconn *ofconn,
+ const struct ofp_header *request)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct list replies;
+ enum ofperr error;
+ struct ofgroup *group;
+ uint32_t group_id;
+
+ error = ofputil_decode_group_stats_request(request, &group_id);
+ if (error) {
+ return error;
+ }
+
+ ofpmp_init(&replies, request);
+
+ if (group_id == OFPG_ALL) {
+ ovs_rwlock_rdlock(&ofproto->groups_rwlock);
+ HMAP_FOR_EACH (group, hmap_node, &ofproto->groups) {
+ ovs_rwlock_rdlock(&group->rwlock);
+ append_group_stats(group, &replies);
+ ovs_rwlock_unlock(&group->rwlock);
+ }
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ } else {
+ if (ofproto_group_lookup(ofproto, group_id, &group)) {
+ append_group_stats(group, &replies);
+ ofproto_group_release(group);
+ }
+ }
+
+ ofconn_send_replies(ofconn, &replies);
+
+ return 0;
+}
+
+static enum ofperr
+handle_group_desc_stats_request(struct ofconn *ofconn,
+ const struct ofp_header *request)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct list replies;
+ struct ofputil_group_desc gds;
+ struct ofgroup *group;
+
+ ofpmp_init(&replies, request);
+
+ ovs_rwlock_rdlock(&ofproto->groups_rwlock);
+ HMAP_FOR_EACH (group, hmap_node, &ofproto->groups) {
+ gds.group_id = group->group_id;
+ gds.type = group->type;
+ ofputil_append_group_desc_reply(&gds, &group->buckets, &replies);
+ }
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+
+ ofconn_send_replies(ofconn, &replies);
+
+ return 0;
+}
+
+static enum ofperr
+handle_group_features_stats_request(struct ofconn *ofconn,
+ const struct ofp_header *request)
+{
+ struct ofproto *p = ofconn_get_ofproto(ofconn);
+ struct ofpbuf *msg;
+
+ msg = ofputil_encode_group_features_reply(&p->ogf, request);
+ if (msg) {
+ ofconn_send_reply(ofconn, msg);
+ }
+
+ return 0;
+}
+
+/* Implements OFPGC11_ADD
+ * in which no matching flow already exists in the flow table.
+ *
+ * Adds the flow specified by 'ofm', which is followed by 'n_actions'
+ * ofp_actions, to the ofproto's flow table. Returns 0 on success, an OpenFlow
+ * error code on failure, or OFPROTO_POSTPONE if the operation cannot be
+ * initiated now but may be retried later.
+ *
+ * Upon successful return, takes ownership of 'fm->ofpacts'. On failure,
+ * ownership remains with the caller.
+ *
+ * 'ofconn' is used to retrieve the packet buffer specified in ofm->buffer_id,
+ * if any. */
+static enum ofperr
+add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
+{
+ struct ofgroup *ofgroup;
+ enum ofperr error;
+
+ if (gm->group_id > OFPG_MAX) {
+ return OFPERR_OFPGMFC_INVALID_GROUP;
+ }
+ if (gm->type > OFPGT11_FF) {
+ return OFPERR_OFPGMFC_BAD_TYPE;
+ }
+
+ /* Allocate new group and initialize it. */
+ ofgroup = ofproto->ofproto_class->group_alloc();
+ if (!ofgroup) {
+ VLOG_WARN_RL(&rl, "%s: failed to create group", ofproto->name);
+ return OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ }
+
+ ovs_rwlock_init(&ofgroup->rwlock);
+ ofgroup->ofproto = ofproto;
+ ofgroup->group_id = gm->group_id;
+ ofgroup->type = gm->type;
+ ofgroup->created = ofgroup->modified = time_msec();
+
+ list_move(&ofgroup->buckets, &gm->buckets);
+ ofgroup->n_buckets = list_size(&ofgroup->buckets);
+
+ /* Construct called BEFORE any locks are held. */
+ error = ofproto->ofproto_class->group_construct(ofgroup);
+ if (error) {
+ goto free_out;
+ }
+
+ /* We wrlock as late as possible to minimize the time we jam any other
+ * threads: No visible state changes before acquiring the lock. */
+ ovs_rwlock_wrlock(&ofproto->groups_rwlock);
+
+ if (ofproto->n_groups[gm->type] >= ofproto->ogf.max_groups[gm->type]) {
+ error = OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ goto unlock_out;
+ }
+
+ if (ofproto_group_exists(ofproto, gm->group_id)) {
+ error = OFPERR_OFPGMFC_GROUP_EXISTS;
+ goto unlock_out;
+ }
+
+ if (!error) {
+ /* Insert new group. */
+ hmap_insert(&ofproto->groups, &ofgroup->hmap_node,
+ hash_int(ofgroup->group_id, 0));
+ ofproto->n_groups[ofgroup->type]++;
+
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ return error;
+ }
+
+ unlock_out:
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ ofproto->ofproto_class->group_destruct(ofgroup);
+ free_out:
+ ofputil_bucket_list_destroy(&ofgroup->buckets);
+ ofproto->ofproto_class->group_dealloc(ofgroup);
+
+ return error;
+}
+
+/* Implements OFPFC_MODIFY. Returns 0 on success or an OpenFlow error code on
+ * failure.
+ *
+ * 'ofconn' is used to retrieve the packet buffer specified in fm->buffer_id,
+ * if any. */
+static enum ofperr
+modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
+{
+ struct ofgroup *ofgroup;
+ struct ofgroup *victim;
+ enum ofperr error;
+
+ if (gm->group_id > OFPG_MAX) {
+ return OFPERR_OFPGMFC_INVALID_GROUP;
+ }
+
+ if (gm->type > OFPGT11_FF) {
+ return OFPERR_OFPGMFC_BAD_TYPE;
+ }
+
+ victim = ofproto->ofproto_class->group_alloc();
+ if (!victim) {
+ VLOG_WARN_RL(&rl, "%s: failed to allocate group", ofproto->name);
+ return OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ }
+
+ if (!ofproto_group_write_lookup(ofproto, gm->group_id, &ofgroup)) {
+ error = OFPERR_OFPGMFC_UNKNOWN_GROUP;
+ goto free_out;
+ }
+ /* Both group's and its container's write locks held now.
+ * Also, n_groups[] is protected by ofproto->groups_rwlock. */
+ if (ofgroup->type != gm->type
+ && ofproto->n_groups[gm->type] >= ofproto->ogf.max_groups[gm->type]) {
+ error = OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ goto unlock_out;
+ }
+
+ *victim = *ofgroup;
+ list_move(&victim->buckets, &ofgroup->buckets);
+
+ ofgroup->type = gm->type;
+ list_move(&ofgroup->buckets, &gm->buckets);
+ ofgroup->n_buckets = list_size(&ofgroup->buckets);
+
+ error = ofproto->ofproto_class->group_modify(ofgroup, victim);
+ if (!error) {
+ ofputil_bucket_list_destroy(&victim->buckets);
+ ofproto->n_groups[victim->type]--;
+ ofproto->n_groups[ofgroup->type]++;
+ ofgroup->modified = time_msec();
+ } else {
+ ofputil_bucket_list_destroy(&ofgroup->buckets);
+
+ *ofgroup = *victim;
+ list_move(&ofgroup->buckets, &victim->buckets);
+ }
+
+ unlock_out:
+ ovs_rwlock_unlock(&ofgroup->rwlock);
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+ free_out:
+ ofproto->ofproto_class->group_dealloc(victim);
+ return error;
+}
+
+static void
+delete_group__(struct ofproto *ofproto, struct ofgroup *ofgroup)
+ OVS_RELEASES(ofproto->groups_rwlock)
+{
+ /* Must wait until existing readers are done,
+ * while holding the container's write lock at the same time. */
+ ovs_rwlock_wrlock(&ofgroup->rwlock);
+ hmap_remove(&ofproto->groups, &ofgroup->hmap_node);
+ /* No-one can find this group any more. */
+ ofproto->n_groups[ofgroup->type]--;
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+
+ ofproto->ofproto_class->group_destruct(ofgroup);
+ ofputil_bucket_list_destroy(&ofgroup->buckets);
+ ovs_rwlock_unlock(&ofgroup->rwlock);
+ ovs_rwlock_destroy(&ofgroup->rwlock);
+ ofproto->ofproto_class->group_dealloc(ofgroup);
+}
+
+/* Implements OFPGC_DELETE. */
+static void
+delete_group(struct ofproto *ofproto, uint32_t group_id)
+{
+ struct ofgroup *ofgroup;
+
+ ovs_rwlock_wrlock(&ofproto->groups_rwlock);
+ if (group_id == OFPG_ALL) {
+ for (;;) {
+ struct hmap_node *node = hmap_first(&ofproto->groups);
+ if (!node) {
+ break;
+ }
+ ofgroup = CONTAINER_OF(node, struct ofgroup, hmap_node);
+ delete_group__(ofproto, ofgroup);
+ /* Lock for each node separately, so that we will not jam the
+ * other threads for too long time. */
+ ovs_rwlock_wrlock(&ofproto->groups_rwlock);
+ }
+ } else {
+ HMAP_FOR_EACH_IN_BUCKET (ofgroup, hmap_node,
+ hash_int(group_id, 0), &ofproto->groups) {
+ if (ofgroup->group_id == group_id) {
+ delete_group__(ofproto, ofgroup);
+ return;
+ }
+ }
+ }
+ ovs_rwlock_unlock(&ofproto->groups_rwlock);
+}
+
+static enum ofperr
+handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ofputil_group_mod gm;
+ enum ofperr error;
+
+ error = reject_slave_controller(ofconn);
+ if (error) {
+ return error;
+ }
+
+ error = ofputil_decode_group_mod(oh, &gm);
+ if (error) {
+ return error;
+ }
+
+ switch (gm.command) {
+ case OFPGC11_ADD:
+ return add_group(ofproto, &gm);
+
+ case OFPGC11_MODIFY:
+ return modify_group(ofproto, &gm);
+
+ case OFPGC11_DELETE:
+ delete_group(ofproto, gm.group_id);
+ return 0;
+
+ default:
+ if (gm.command > OFPGC11_DELETE) {
+ VLOG_WARN_RL(&rl, "%s: Invalid group_mod command type %d",
+ ofproto->name, gm.command);
+ }
+ return OFPERR_OFPGMFC_BAD_COMMAND;
+ }
+}
+
static enum ofperr
handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
{
case OFPTYPE_FLOW_MOD:
return handle_flow_mod(ofconn, oh);
+ case OFPTYPE_GROUP_MOD:
+ return handle_group_mod(ofconn, oh);
+
case OFPTYPE_METER_MOD:
return handle_meter_mod(ofconn, oh);
case OFPTYPE_METER_FEATURES_STATS_REQUEST:
return handle_meter_features_request(ofconn, oh);
- /* FIXME: Change the following once they are implemented: */
- case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
- case OFPTYPE_GET_ASYNC_REQUEST:
case OFPTYPE_GROUP_STATS_REQUEST:
+ return handle_group_stats_request(ofconn, oh);
+
case OFPTYPE_GROUP_DESC_STATS_REQUEST:
+ return handle_group_desc_stats_request(ofconn, oh);
+
case OFPTYPE_GROUP_FEATURES_STATS_REQUEST:
+ return handle_group_features_stats_request(ofconn, oh);
+
+ /* FIXME: Change the following once they are implemented: */
+ case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
+ case OFPTYPE_GET_ASYNC_REQUEST:
case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
return OFPERR_OFPBRC_BAD_TYPE;
])
AT_CLEANUP
-AT_SETUP([OFPST_PORT_DESC request - OF1.0])
-AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
-AT_CHECK([ovs-ofctl ofp-print "0110000c00000001000d0000"], [0], [dnl
-OFPST_PORT_DESC request (xid=0x1):
-])
-AT_CLEANUP
-
AT_SETUP([OFPST_QUEUE reply - OF1.1])
AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
AT_CHECK([ovs-ofctl ofp-print "\
])
AT_CLEANUP
+AT_SETUP([OFPST_GROUP request])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+02 12 00 18 00 00 00 02 00 06 00 00 00 00 00 00 \
+ff ff ff ff 00 00 00 00 \
+"], [0], [OFPST_GROUP request (OF1.1) (xid=0x2): group_id=ANY
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_GROUP reply - OF1.1])
+AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
+AT_CHECK([ovs-ofctl ofp-print "\
+02 13 00 a0 00 00 00 02 00 06 00 00 00 00 00 00 \
+00 50 00 00 87 65 43 21 00 00 00 04 00 00 00 00 \
+00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+00 00 00 00 00 00 66 66 00 00 00 00 00 33 33 33 \
+00 40 00 00 00 00 00 05 00 00 00 02 00 00 00 00 \
+00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+"], [0], [dnl
+OFPST_GROUP reply (OF1.1) (xid=0x2):
+ group_id=2271560481,ref_count=4,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962,bucket2:packet_count=26214,byte_count=3355443
+ group_id=5,ref_count=2,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_GROUP reply - OF1.3])
+AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
+AT_CHECK([ovs-ofctl ofp-print "\
+04 13 00 b0 00 00 00 02 00 06 00 00 00 00 00 00 \
+00 58 00 00 87 65 43 21 00 00 00 04 00 00 00 00 \
+00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \
+00 00 00 12 1d cd 65 00 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+00 00 00 00 00 00 66 66 00 00 00 00 00 33 33 33 \
+00 48 00 00 00 00 00 05 00 00 00 02 00 00 00 00 \
+00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \
+00 00 00 10 1d cd 65 00 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \
+"], [0], [dnl
+OFPST_GROUP reply (OF1.3) (xid=0x2):
+ group_id=2271560481,duration=18.5s,ref_count=4,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962,bucket2:packet_count=26214,byte_count=3355443
+ group_id=5,duration=16.5s,ref_count=2,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_GROUP_DESC request])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+02 12 00 10 00 00 00 02 00 07 00 00 00 00 00 00 \
+"], [0], [OFPST_GROUP_DESC request (OF1.1) (xid=0x2):
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_GROUP_DESC reply])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+02 13 00 78 00 00 00 02 00 07 00 00 00 00 00 00 \
+00 68 01 00 00 00 20 00 \
+00 20 00 64 00 00 00 01 ff ff ff ff 00 00 00 00 \
+00 00 00 10 00 00 00 01 00 00 00 00 00 00 00 00 \
+00 20 00 c8 00 00 00 02 ff ff ff ff 00 00 00 00 \
+00 00 00 10 00 00 00 02 00 00 00 00 00 00 00 00 \
+00 20 00 c8 00 00 00 03 ff ff ff ff 00 00 00 00 \
+00 00 00 10 00 00 00 03 00 00 00 00 00 00 00 00 \
+"], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.1) (xid=0x2):
+ group_id=8192,type=select,bucket=weight:100,watch_port:1,actions=output:1,bucket=weight:200,watch_port:2,actions=output:2,bucket=weight:200,watch_port:3,actions=output:3
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_GROUP_FEATURES request])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+03 12 00 10 00 00 00 02 00 08 00 00 00 00 00 00 \
+"], [0], [OFPST_GROUP_FEATURES request (OF1.2) (xid=0x2):
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_GROUP_FEATURES reply])
+AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
+AT_CHECK([ovs-ofctl ofp-print "\
+03 13 00 38 00 00 00 02 00 08 00 00 00 00 00 00 \
+00 00 00 0f 00 00 00 0f \
+00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 \
+00 00 00 01 00 00 00 03 00 00 00 07 00 00 00 0f \
+"], [0], [dnl
+OFPST_GROUP_FEATURES reply (OF1.2) (xid=0x2):
+ Group table:
+ Types: 0xf
+ Capabilities: 0xf
+ All group :
+ max_groups = 0x1 actions=0x00000001
+ Select group :
+ max_groups = 0x2 actions=0x00000003
+ Indirect group :
+ max_groups = 0x3 actions=0x00000007
+ Fast Failover group :
+ max_groups = 0x4 actions=0x0000000f
+])
+AT_CLEANUP
+
+AT_SETUP([OFPST_PORT_DESC request - OF1.0])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "0110000c00000001000d0000"], [0], [dnl
+OFPST_PORT_DESC request (xid=0x1):
+])
+AT_CLEANUP
+
AT_SETUP([OFPST_PORT_DESC reply - OF1.0])
AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
AT_CHECK([ovs-ofctl ofp-print "\
])
AT_CLEANUP
+AT_SETUP([OFPT_GROUP_MOD])
+AT_KEYWORDS([ofp-print])
+AT_CHECK([ovs-ofctl ofp-print "\
+02 0f 00 70 11 22 33 44 00 00 01 00 87 65 43 21 \
+00 20 00 64 00 00 00 01 ff ff ff ff 00 00 00 00 \
+00 00 00 10 00 00 00 01 00 00 00 00 00 00 00 00 \
+00 20 00 c8 00 00 00 02 ff ff ff ff 00 00 00 00 \
+00 00 00 10 00 00 00 02 00 00 00 00 00 00 00 00 \
+00 20 00 c8 00 00 00 03 ff ff ff ff 00 00 00 00 \
+00 00 00 10 00 00 00 03 00 00 00 00 00 00 00 00 \
+"], [0], [dnl
+OFPT_GROUP_MOD (OF1.1) (xid=0x11223344):
+ ADD group_id=2271560481,type=select,bucket=weight:100,watch_port:1,actions=output:1,bucket=weight:200,watch_port:2,actions=output:2,bucket=weight:200,watch_port:3,actions=output:3
+])
+AT_CLEANUP
+
AT_SETUP([NXT_FLOW_REMOVED])
AT_KEYWORDS([ofp-print])
AT_CHECK([ovs-ofctl ofp-print "\
\fIport\fR is omitted, then statistics are printed for \fIqueue\fR on
every port where it exists.
.
+.SS "OpenFlow 1.1+ Switch Management Commands"
+.
+The following commands work only with switches that support OpenFlow
+1.1 or later. Because support for OpenFlow 1.1 and later is still
+experimental in Open vSwitch, it is necessary to explicitly enable
+these protocol versions in \fBovs\-ofctl\fR (using \fB\-O\fR) and in
+the switch itself (with the \fBprotocols\fR column in the \fBBridge\fR
+table). For more information, see ``Q: What versions of OpenFlow does
+Open vSwitch support?'' in the Open vSwitch FAQ.
+.
+.IP "\fBdump\-groups \fIswitch"
+Prints to the console all group entries in \fIswitch\fR's tables. Each line
+of output is a group entry as described in \fBGroup Syntax\fR below.
+.
+.IP "\fBdump\-group\-features \fIswitch"
+Prints to the console the group features of the \fIswitch\fR.
+.
+.IP "\fBdump\-group-stats \fIswitch \fR[\fIgroups\fR]"
+Prints to the console statistics for the specified \fIgroups in the
+\fIswitch\fR's tables. If \fIgroups\fR is omitted then statistics for all
+groups are printed. See \fBGroup Syntax\fR, below, for the syntax of
+\fIgroups\fR.
+.
.SS "OpenFlow Switch Flow Table Commands"
.
These commands manage the flow table in an OpenFlow switch. In each
``local'' port), or the keyword \fBNONE\fR to indicate that the packet
was generated by the switch itself.
.
+.SS "OpenFlow Switch Group Table Commands"
+.
+These commands manage the group table in an OpenFlow switch. In each
+case, \fIgroup\fR specifies a group entry in the format described in
+\fBGroup Syntax\fR, below, and \fIfile\fR is a text file that contains
+zero or more groups in the same syntax, one per line.
+
+.IP "\fBadd\-group \fIswitch group\fR"
+.IQ "\fBadd\-group \fIswitch \fB\- < \fIfile\fR"
+.IQ "\fBadd\-groups \fIswitch file\fR"
+Add each group entry to \fIswitch\fR's tables.
+.
+.IP "\fBmod\-group \fIswitch group\fR"
+.IQ "\fBmod\-group \fIswitch \fB\- < \fIfile\fR"
+Modify the action buckets in entries from \fIswitch\fR's tables for
+each group entry.
+.
+.IP "\fBdel\-groups \fIswitch\fR"
+.IQ "\fBdel\-groups \fIswitch \fR[\fIgroup\fR]"
+.IQ "\fBdel\-groups \fIswitch \fB\- < \fIfile\fR"
+Deletes entries from \fIswitch\fR's group table. With only a
+\fIswitch\fR argument, deletes all groups. Otherwise, deletes the group
+for each group entry.
+.
.SS "OpenFlow Switch Monitoring Commands"
.
.IP "\fBsnoop \fIswitch\fR"
The integer number of seconds that have passed without any packets
passing through the flow.
.
+.SS "Group Syntax"
+.PP
+Some \fBovs\-ofctl\fR commands accept an argument that describes a group or
+groups. Such flow descriptions comprise a series
+\fIfield\fB=\fIvalue\fR assignments, separated by commas or white
+space. (Embedding spaces into a group description normally requires
+quoting to prevent the shell from breaking the description into
+multiple arguments.). Unless noted otherwise only the last instance
+of each field is honoured.
+.PP
+.IP \fBgroup_id=\fIid\fR
+The integer group id of group.
+When this field is specified in \fBdel-groups\fR or \fBdump-groups\fR,
+the keyword "all" may be used to designate all groups.
+.
+This field is required.
+
+
+.IP \fBtype=\fItype\fR
+The type of the group. This \fBadd-group\fR, \fBadd-groups\fR and
+\fBdel-groups\fR command require this field. The following keywords
+designated the allowed types:
+.RS
+.IP \fBall\fR
+Execute all buckets in the group.
+.IP \fBselect\fR
+Execute one bucket in the group.
+The switch should select the bucket in such a way that should implement
+equal load sharing is achieved. The switch may optionally select the
+bucket based on bucket weights.
+.IP \fBindirect\fR
+Executes the one bucket in the group.
+.IP \fBff\fR
+.IQ \fBfast_failover\fR
+Executes the first live bucket in the group which is associated with
+a live port or group.
+.RE
+
+.IP \fBbucket\fR=\fIbucket_parameters\fR
+The \fBadd-group\fR, \fBadd-groups\fR and \fBmod-group\fR commands
+require at least one bucket field. Bucket fields must appear after
+all other fields.
+.
+Multiple bucket fields to specify multiple buckets.
+The order in which buckets are specified corresponds to their order in
+the group. If the type of the group is "indirect" then only one group may
+be specified.
+.
+\fIbucket_parameters\fR consists of a list of \fIfield\fB=\fIvalue\fR
+assignments, separated by commas or white space followed by a
+comma-separated list of actions.
+The syntax of actions are same
+to \fBactions=\fR field described in \fBFlow Syntax\fR above.
+The fields for \fIbucket_parameters\fR are:
+.
+.RS
+.IP \fBweight=\fIvalue\fR
+The relative weight of the bucket as an integer. This may be used by the switch
+during bucket select for groups whose \fBtype\fR is \fBselect\fR.
+.IP \fBwatch_port=\fIport\fR
+Port used to determine liveness of group.
+This or the \fBwatch_group\fR field is required
+for groups whose \fBtype\fR is \fBff\fR or \fBfast_failover\fR.
+.IP \fBwatch_group=\fIgroup_id\fR
+Group identifier of group used to determine liveness of group.
+This or the \fBwatch_port\fR field is required
+for groups whose \fBtype\fR is \fBff\fR or \fBfast_failover\fR.
+.RE
+.
.SH OPTIONS
.TP
\fB\-\-strict\fR
" monitor SWITCH [MISSLEN] [invalid_ttl] [watch:[...]]\n"
" print packets received from SWITCH\n"
" snoop SWITCH snoop on SWITCH and its controller\n"
+ " add-group SWITCH GROUP add group described by GROUP\n"
+ " add-group SWITCH FILE add group from FILE\n"
+ " mod-group SWITCH GROUP modify specific group\n"
+ " del-groups SWITCH [GROUP] delete matching GROUPs\n"
+ " dump-group-features SWITCH print group features\n"
+ " dump-groups SWITCH print group description\n"
+ " dump-group-stats SWITCH [GROUP] print group statistics\n"
"\nFor OpenFlow switches and controllers:\n"
" probe TARGET probe whether TARGET is up\n"
" ping TARGET [N] latency of N-byte echos\n"
count * message_size / (duration / 1000.0));
}
+static void
+ofctl_group_mod__(const char *remote, struct ofputil_group_mod *gms,
+ size_t n_gms)
+{
+ struct ofputil_group_mod *gm;
+ struct ofpbuf *request;
+
+ struct vconn *vconn;
+ size_t i;
+
+ open_vconn(remote, &vconn);
+
+ for (i = 0; i < n_gms; i++) {
+ gm = &gms[i];
+ request = ofputil_encode_group_mod(vconn_get_version(vconn), gm);
+ if (request) {
+ transact_noreply(vconn, request);
+ }
+ }
+
+ vconn_close(vconn);
+
+}
+
+
+static void
+ofctl_group_mod_file(int argc OVS_UNUSED, char *argv[], uint16_t command)
+{
+ struct ofputil_group_mod *gms = NULL;
+ enum ofputil_protocol usable_protocols;
+ size_t n_gms = 0;
+ char *error;
+
+ error = parse_ofp_group_mod_file(argv[2], command, &gms, &n_gms,
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+ ofctl_group_mod__(argv[1], gms, n_gms);
+ free(gms);
+}
+
+static void
+ofctl_group_mod(int argc, char *argv[], uint16_t command)
+{
+ if (argc > 2 && !strcmp(argv[2], "-")) {
+ ofctl_group_mod_file(argc, argv, command);
+ } else {
+ enum ofputil_protocol usable_protocols;
+ struct ofputil_group_mod gm;
+ char *error;
+
+ error = parse_ofp_group_mod_str(&gm, command, argc > 2 ? argv[2] : "",
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+ ofctl_group_mod__(argv[1], &gm, 1);
+ }
+}
+
+static void
+ofctl_add_group(int argc, char *argv[])
+{
+ ofctl_group_mod(argc, argv, OFPGC11_ADD);
+}
+
+static void
+ofctl_add_groups(int argc, char *argv[])
+{
+ ofctl_group_mod_file(argc, argv, OFPGC11_ADD);
+}
+
+static void
+ofctl_mod_group(int argc, char *argv[])
+{
+ ofctl_group_mod(argc, argv, OFPGC11_MODIFY);
+}
+
+static void
+ofctl_del_groups(int argc, char *argv[])
+{
+ ofctl_group_mod(argc, argv, OFPGC11_DELETE);
+}
+
+static void
+ofctl_dump_group_stats(int argc, char *argv[])
+{
+ enum ofputil_protocol usable_protocols;
+ struct ofputil_group_mod gm;
+ struct ofpbuf *request;
+ struct vconn *vconn;
+ uint32_t group_id;
+ char *error;
+
+ memset(&gm, 0, sizeof gm);
+
+ error = parse_ofp_group_mod_str(&gm, OFPGC11_DELETE,
+ argc > 2 ? argv[2] : "",
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+
+ group_id = gm.group_id;
+
+ open_vconn(argv[1], &vconn);
+ request = ofputil_encode_group_stats_request(vconn_get_version(vconn),
+ group_id);
+ if (request) {
+ dump_stats_transaction(vconn, request);
+ }
+
+ vconn_close(vconn);
+}
+
+static void
+ofctl_dump_group_desc(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+
+ open_vconn(argv[1], &vconn);
+
+ request = ofputil_encode_group_desc_request(vconn_get_version(vconn));
+ if (request) {
+ dump_stats_transaction(vconn, request);
+ }
+
+ vconn_close(vconn);
+}
+
+static void
+ofctl_dump_group_features(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+
+ open_vconn(argv[1], &vconn);
+ request = ofputil_encode_group_features_request(vconn_get_version(vconn));
+ if (request) {
+ dump_stats_transaction(vconn, request);
+ }
+
+ vconn_close(vconn);
+}
+
static void
ofctl_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
{
{ "probe", 1, 1, ofctl_probe },
{ "ping", 1, 2, ofctl_ping },
{ "benchmark", 3, 3, ofctl_benchmark },
+
+ { "add-group", 1, 2, ofctl_add_group },
+ { "add-groups", 1, 2, ofctl_add_groups },
+ { "mod-group", 1, 2, ofctl_mod_group },
+ { "del-groups", 1, 2, ofctl_del_groups },
+ { "dump-groups", 1, 1, ofctl_dump_group_desc },
+ { "dump-group-stats", 1, 2, ofctl_dump_group_stats },
+ { "dump-group-features", 1, 1, ofctl_dump_group_features },
{ "help", 0, INT_MAX, ofctl_help },
/* Undocumented commands for testing. */