[ovs-dev] [groups v3] Implement OpenFlow 1.1+ "groups" protocol.
Ben Pfaff
blp at nicira.com
Thu Aug 29 21:00:51 UTC 2013
From: Neil Zhu <zhuj at centecnetworks.com>
This doesn't include a dpif implementation of groups functionality. In its
current form, it is untested. Before this is committed, it needs some
more comments and an ovs-ofctl manpage update.
Signed-off-by: Neil Zhu <zhuj at centecnetworks.com>
Co-authored-by: Ben Pfaff <blp at nicira.com>
Signed-off-by: Ben Pfaff <blp at nicira.com>
---
v1->v2: Changes to please Ben.
v2->v3: Bug fixes from Jarno.
AUTHORS | 1 +
NEWS | 7 +
include/openflow/openflow-1.1.h | 8 +-
include/openflow/openflow-1.2.h | 2 +-
include/openflow/openflow-common.h | 2 +-
lib/learning-switch.c | 1 +
lib/ofp-actions.c | 43 +++
lib/ofp-actions.h | 11 +
lib/ofp-msgs.h | 10 +-
lib/ofp-parse.c | 285 +++++++++++++++++
lib/ofp-parse.h | 11 +
lib/ofp-print.c | 230 +++++++++++++-
lib/ofp-util.c | 589 +++++++++++++++++++++++++++++++++++-
lib/ofp-util.def | 1 +
lib/ofp-util.h | 95 ++++++
lib/rconn.c | 1 +
ofproto/ofproto-dpif-xlate.c | 4 +
ofproto/ofproto-dpif.c | 6 +
ofproto/ofproto-provider.h | 48 ++-
ofproto/ofproto.c | 372 ++++++++++++++++++++++-
tests/ofp-print.at | 137 ++++++++-
utilities/ovs-ofctl.c | 162 ++++++++++
22 files changed, 1984 insertions(+), 42 deletions(-)
diff --git a/AUTHORS b/AUTHORS
index af34bfe..a708fb2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -66,6 +66,7 @@ Mehak Mahajan mmahajan at nicira.com
Murphy McCauley murphy.mccauley at gmail.com
Natasha Gude natasha at nicira.com
Neil McKee neil.mckee at inmon.com
+Neil Zhu zhuj at centecnetworks.com
Paraneetharan Chandrasekaran paraneetharanc at gmail.com
Paul Fazzone pfazzone at nicira.com
Paul Ingram paul at nicira.com
diff --git a/NEWS b/NEWS
index 7c1b480..8bdc621 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,12 @@ v2.0.0 - xx xxx xxxx
- 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.
@@ -26,6 +32,7 @@ v2.0.0 - xx xxx xxxx
- 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.
diff --git a/include/openflow/openflow-1.1.h b/include/openflow/openflow-1.1.h
index f7c2e78..a2309b1 100644
--- a/include/openflow/openflow-1.1.h
+++ b/include/openflow/openflow-1.1.h
@@ -1,4 +1,4 @@
-/* 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
@@ -150,8 +150,8 @@ OFP_ASSERT(sizeof(struct ofp11_port_mod) == 32);
/* 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
@@ -725,7 +725,7 @@ OFP_ASSERT(sizeof(struct ofp11_bucket_counter) == 16);
/* 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]; */
diff --git a/include/openflow/openflow-1.2.h b/include/openflow/openflow-1.2.h
index 2977047..d1e42a4 100644
--- a/include/openflow/openflow-1.2.h
+++ b/include/openflow/openflow-1.2.h
@@ -301,7 +301,7 @@ OFP_ASSERT(sizeof(struct ofp12_table_stats) == 128);
/* 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. */
diff --git a/include/openflow/openflow-common.h b/include/openflow/openflow-common.h
index a9e5a76..5018f85 100644
--- a/include/openflow/openflow-common.h
+++ b/include/openflow/openflow-common.h
@@ -306,7 +306,7 @@ OFP_ASSERT(sizeof(struct ofp_action_vendor_header) == 8);
* 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
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index 336257c..c587930 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -346,6 +346,7 @@ lswitch_process_packet(struct lswitch *sw, const struct ofpbuf *msg)
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:
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 61e2854..77aa69c 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -860,6 +860,12 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
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);
@@ -945,6 +951,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
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:
@@ -1302,6 +1309,9 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
}
return 0;
+ case OFPACT_GROUP:
+ return 0;
+
default:
NOT_REACHED();
}
@@ -1598,6 +1608,7 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out)
ofpact_sample_to_nxast(ofpact_get_SAMPLE(a), out);
break;
+ case OFPACT_GROUP:
case OFPACT_OUTPUT:
case OFPACT_ENQUEUE:
case OFPACT_SET_VLAN_VID:
@@ -1710,6 +1721,9 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
/* XXX */
break;
+ case OFPACT_GROUP:
+ break;
+
case OFPACT_CONTROLLER:
case OFPACT_OUTPUT_REG:
case OFPACT_BUNDLE:
@@ -1882,6 +1896,11 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
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:
@@ -2051,6 +2070,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
case OFPACT_CLEAR_ACTIONS:
case OFPACT_GOTO_TABLE:
case OFPACT_METER:
+ case OFPACT_GROUP:
default:
return false;
}
@@ -2073,6 +2093,24 @@ ofpacts_output_to_port(const struct ofpact *ofpacts, size_t ofpacts_len,
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)
@@ -2383,6 +2421,11 @@ ofpact_format(const struct ofpact *a, struct ds *s)
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;
}
}
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 101c33d..a3fb60f 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -51,6 +51,7 @@
#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) \
@@ -490,6 +491,14 @@ struct ofpact_goto_table {
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,
@@ -517,6 +526,8 @@ void ofpacts_put_openflow11_instructions(const struct ofpact[],
/* 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);
diff --git a/lib/ofp-msgs.h b/lib/ofp-msgs.h
index 3ead49e..47256de 100644
--- a/lib/ofp-msgs.h
+++ b/lib/ofp-msgs.h
@@ -177,6 +177,9 @@ enum ofpraw {
/* 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. */
@@ -295,15 +298,15 @@ enum ofpraw {
/* 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. */
@@ -460,6 +463,7 @@ enum ofptype {
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. */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 176f61f..9098467 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -819,6 +819,10 @@ parse_named_action(enum ofputil_action_code code,
}
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;
@@ -1157,6 +1161,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
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) {
@@ -1794,6 +1799,7 @@ parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
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;
}
@@ -1878,3 +1884,282 @@ exit:
}
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;
+ }
+ }
+
+ 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;
+}
diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h
index cdb6831..1c3228a 100644
--- a/lib/ofp-parse.h
+++ b/lib/ofp-parse.h
@@ -29,6 +29,7 @@ struct ofpbuf;
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;
@@ -66,4 +67,14 @@ char *parse_flow_monitor_request(struct ofputil_flow_monitor_request *,
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 */
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 560762b..cc1bd73 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -2141,6 +2141,204 @@ ofp_print_not_implemented(struct ds *string)
}
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;
+ enum ofperr error;
+ struct ofpbuf b;
+
+ error = ofputil_decode_group_features_reply(oh, &features, &b);
+ if (error) {
+ ofp_print_error(string, error);
+ return;
+ }
+
+ 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)
{
@@ -2149,17 +2347,39 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
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);
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 61fb74f..492c494 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -1537,6 +1537,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
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)) {
@@ -1578,6 +1580,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
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. */
@@ -1608,6 +1611,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
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();
@@ -2058,7 +2062,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
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);
@@ -2124,6 +2128,7 @@ ofputil_decode_ofpst10_flow_request(struct ofputil_flow_stats_request *fsr,
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);
@@ -2144,9 +2149,7 @@ ofputil_decode_ofpst11_flow_request(struct ofputil_flow_stats_request *fsr,
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);
@@ -2176,6 +2179,7 @@ ofputil_decode_nxst_flow_request(struct ofputil_flow_stats_request *fsr,
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;
@@ -2242,7 +2246,7 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
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);
@@ -4576,6 +4580,65 @@ ofputil_port_to_string(ofp_port_t port,
}
}
+/* 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 group, 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
@@ -5193,6 +5256,522 @@ ofputil_decode_port_stats_request(const struct ofp_header *request,
}
}
+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);
+ }
+}
+
+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;
+}
+
+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);
+}
+
+/* Encode a group stat for 'ogs' and append it to 'replies'. */
+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();
+ }
+}
+
+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;
+}
+
+struct ofpbuf *
+ofputil_encode_group_features_reply(
+ const struct ofputil_group_features *features,
+ const struct ofp_header *request)
+{
+ struct ofpbuf *reply;
+ enum ofpraw raw;
+ struct ofp12_group_features_stats *ogf;
+
+ switch ((enum ofp_version) request->version) {
+ case OFP10_VERSION:
+ case OFP11_VERSION:
+ ovs_fatal(0, "Group features reply needs OpenFlow 1.2 or later "
+ "(\'-O OpenFlow12\')");
+ case OFP12_VERSION:
+ case OFP13_VERSION:
+ raw = OFPRAW_OFPST12_GROUP_FEATURES_REPLY;
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+
+ reply = ofpraw_alloc_xid(raw, (enum ofp_version) 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;
+}
+
+enum ofperr
+ofputil_decode_group_features_reply(const struct ofp_header *oh,
+ struct ofputil_group_features *features,
+ struct ofpbuf *b)
+{
+ const struct ofp12_group_features_stats *ogf;
+ enum ofpraw raw;
+
+ ofpbuf_use_const(b, oh, ntohs(oh->length));
+ raw = ofpraw_pull_assert(b);
+ ovs_assert(raw == OFPRAW_OFPST12_GROUP_FEATURES_REPLY);
+
+ ogf = ofpbuf_pull(b, sizeof *ogf);
+ 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]);
+
+ 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;
+}
+
+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;
+
+ if (!msg->size) {
+ return EOF;
+ }
+
+ error = (msg->l2
+ ? ofpraw_decode(&raw, msg->l2)
+ : ofpraw_pull(&raw, msg));
+ if (error) {
+ return error;
+ }
+
+ 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 an OFPST_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(). */
+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;
+}
+
+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);
+}
+
+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;
+}
+
+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
diff --git a/lib/ofp-util.def b/lib/ofp-util.def
index d88d420..f5eec73 100644
--- a/lib/ofp-util.def
+++ b/lib/ofp-util.def
@@ -40,6 +40,7 @@ OFPAT11_ACTION(OFPAT11_SET_QUEUE, ofp11_action_set_queue, 0, "set_queue")
//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)
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 7e50db2..ad62c8a 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -43,6 +43,14 @@ void ofputil_format_port(ofp_port_t port, struct ds *);
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);
@@ -268,6 +276,7 @@ struct ofputil_flow_mod {
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. */
@@ -287,6 +296,7 @@ struct ofputil_flow_stats_request {
ovs_be64 cookie;
ovs_be64 cookie_mask;
ofp_port_t out_port;
+ uint32_t out_group;
uint8_t table_id;
};
@@ -867,4 +877,89 @@ int ofputil_decode_queue_stats(struct ofputil_queue_stats *qs, struct ofpbuf *ms
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);
+enum ofperr ofputil_decode_group_features_reply(
+ const struct ofp_header *, struct ofputil_group_features *,
+ struct ofpbuf *);
+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 */
diff --git a/lib/rconn.c b/lib/rconn.c
index 39a12c9..17cbd82 100644
--- a/lib/rconn.c
+++ b/lib/rconn.c
@@ -1154,6 +1154,7 @@ is_admitted_msg(const struct ofpbuf *b)
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:
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index eeccbc5..2901fbe 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -2181,6 +2181,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
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,
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 820ec34..70a226c 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -6384,4 +6384,10 @@ const struct ofproto_class ofproto_dpif_class = {
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 */
};
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index d6a8a0b..d37014f 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -111,6 +111,11 @@ struct ofproto {
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 hmap groups; /* Contains "struct ofgroup"s. */
+ uint32_t n_groups[4]; /* Number of existing groups of each type. */
+ struct ofputil_group_features ogf;
};
void ofproto_init_tables(struct ofproto *, int n_tables);
@@ -289,16 +294,34 @@ bool ofproto_rule_has_out_port(const struct rule *, ofp_port_t out_port);
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 {
+ struct hmap_node hmap_node; /* In struct ofproto's "group" 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;
+};
+
/* 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.
@@ -307,6 +330,9 @@ bool ofproto_rule_is_hidden(const struct rule *);
*
* - "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,
@@ -328,9 +354,10 @@ bool ofproto_rule_is_hidden(const struct rule *);
* 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
@@ -1451,6 +1478,21 @@ struct ofproto_class {
/* 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;
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 1173936..586e565 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -213,6 +213,7 @@ static enum ofperr modify_flows__(struct ofproto *, struct ofconn *,
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 *,
@@ -456,6 +457,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
ofproto->vlan_bitmap = NULL;
ofproto->vlans_changed = false;
ofproto->min_mtu = INT_MAX;
+ hmap_init(&ofproto->groups);
error = ofproto->ofproto_class->construct(ofproto);
if (error) {
@@ -2249,6 +2251,14 @@ ofproto_rule_has_out_port(const struct rule *rule, ofp_port_t port)
|| 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
@@ -2890,7 +2900,8 @@ static enum ofperr
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;
@@ -2943,6 +2954,7 @@ collect_rules_loose(struct ofproto *ofproto, uint8_t table_id,
}
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);
}
@@ -2970,7 +2982,8 @@ static enum ofperr
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;
@@ -3023,6 +3036,7 @@ collect_rules_strict(struct ofproto *ofproto, uint8_t table_id,
}
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);
}
@@ -3062,7 +3076,7 @@ handle_flow_stats_request(struct ofconn *ofconn,
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;
}
@@ -3189,7 +3203,7 @@ handle_aggregate_stats_request(struct ofconn *ofconn,
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;
}
@@ -3652,7 +3666,7 @@ modify_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
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)) {
@@ -3677,8 +3691,7 @@ modify_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
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)) {
@@ -3737,7 +3750,7 @@ delete_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
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)
@@ -3755,7 +3768,7 @@ delete_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
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,
@@ -3818,7 +3831,8 @@ ofproto_rule_expire(struct rule *rule, uint8_t reason)
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);
@@ -4675,6 +4689,329 @@ handle_meter_request(struct ofconn *ofconn, const struct ofp_header *request,
return 0;
}
+static struct ofgroup *
+ofproto_group_lookup(const struct ofproto *ofproto,
+ uint32_t group_id)
+{
+ struct ofgroup *group;
+
+ HMAP_FOR_EACH_IN_BUCKET (group, hmap_node,
+ hash_int(group_id, 0), &ofproto->groups) {
+ if (group->group_id == group_id) {
+ return group;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+append_group_stats(struct ofgroup *group, struct list *replies)
+{
+ 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){
+ HMAP_FOR_EACH (group, hmap_node, &ofproto->groups) {
+ append_group_stats(group, &replies);
+ }
+ } else {
+ group = ofproto_group_lookup(ofproto, group_id);
+ if (group) {
+ append_group_stats(group, &replies);
+ }
+ }
+
+ 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);
+
+ 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);
+ }
+
+ 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;
+}
+
+/* Checks whether a new group 'group_id' may be created in 'ofproto'. Returns
+ * 0 if 'gm' is OK, otherwise an OpenFlow error code. */
+static enum ofperr
+ofproto_may_create_group(const struct ofproto *ofproto, uint32_t group_id)
+{
+ if (group_id > OFPG_MAX) {
+ return OFPERR_OFPGMFC_INVALID_GROUP;
+ } else if (ofproto_group_lookup(ofproto, group_id)) {
+ return OFPERR_OFPGMFC_GROUP_EXISTS;
+ } else {
+ 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;
+
+ error = ofproto_may_create_group(ofproto, gm->group_id);
+ if (error) {
+ return error;
+ }
+
+ if (gm->type > OFPGT11_FF) {
+ return OFPERR_OFPGMFC_BAD_TYPE;
+ }
+
+ if (ofproto->n_groups[gm->type] >= ofproto->ogf.max_groups[gm->type]) {
+ return OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ }
+
+ /* 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;
+ }
+
+ 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);
+
+ hmap_insert(&ofproto->groups, &ofgroup->hmap_node,
+ hash_int(ofgroup->group_id, 0));
+ ofproto->n_groups[ofgroup->type]++;
+
+ /* Insert new group. */
+ error = ofproto->ofproto_class->group_construct(ofgroup);
+ if (error) {
+ ofputil_bucket_list_destroy(&ofgroup->buckets);
+
+ hmap_remove(&ofproto->groups, &ofgroup->hmap_node);
+ ofproto->ofproto_class->group_dealloc(ofgroup);
+ ofproto->n_groups[gm->type]--;
+ }
+
+ 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;
+ }
+
+ ofgroup = ofproto_group_lookup(ofproto, gm->group_id);
+ if (!ofgroup) {
+ return OFPERR_OFPGMFC_UNKNOWN_GROUP;
+ }
+
+ if (ofgroup->type != gm->type
+ && ofproto->n_groups[gm->type] >= ofproto->ogf.max_groups[gm->type]) {
+ return OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ }
+
+ victim = ofproto->ofproto_class->group_alloc();
+ if (!victim) {
+ VLOG_WARN_RL(&rl, "%s: failed to create group", ofproto->name);
+ return OFPERR_OFPGMFC_OUT_OF_GROUPS;
+ }
+ *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);
+ }
+ ofproto->ofproto_class->group_dealloc(victim);
+
+ return error;
+}
+
+static void
+delete_group__(struct ofgroup *ofgroup)
+{
+ struct ofproto *ofproto = ofgroup->ofproto;
+
+ ofproto->ofproto_class->group_destruct(ofgroup);
+ ofputil_bucket_list_destroy(&ofgroup->buckets);
+ hmap_remove(&ofproto->groups, &ofgroup->hmap_node);
+ ofproto->n_groups[ofgroup->type]--;
+ ofproto->ofproto_class->group_dealloc(ofgroup);
+}
+
+/* Implements OFPGC_DELETE. */
+static void
+delete_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
+{
+ if (gm->group_id == OFPG_ALL) {
+ struct ofgroup *ofgroup, *next;
+
+ /* Remove indirect groups first, to allow other groups to be removed
+ * successfully. */
+ HMAP_FOR_EACH_SAFE (ofgroup, next, hmap_node, &ofproto->groups) {
+ if (ofgroup->type == OFPGT11_INDIRECT) {
+ delete_group__(ofgroup);
+ }
+ }
+ HMAP_FOR_EACH_SAFE (ofgroup, next, hmap_node, &ofproto->groups) {
+ delete_group__(ofgroup);
+ }
+ } else {
+ struct ofgroup *ofgroup = ofproto_group_lookup(ofproto, gm->group_id);
+ if (ofgroup) {
+ delete_group__(ofgroup);
+ }
+ }
+}
+
+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);
+ 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)
{
@@ -4710,6 +5047,9 @@ 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);
@@ -4778,12 +5118,18 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
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;
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index f554aba..a044b14 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -1585,13 +1585,6 @@ OFPST_QUEUE reply (xid=0x1): 6 queues
])
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 "\
@@ -1679,6 +1672,120 @@ OFPST_QUEUE reply (OF1.3) (xid=0x1): 6 queues
])
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 "\
@@ -2068,6 +2175,22 @@ NXT_FLOW_MOD (xid=0x2): ADD NXM_NX_TUN_ID(00000000000001c8), NXM_NX_REG0(0000007
])
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 "\
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 547d50c..9294752 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -301,6 +301,13 @@ usage(void)
" 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"
@@ -1843,6 +1850,153 @@ ofctl_benchmark(int argc OVS_UNUSED, char *argv[])
}
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)
{
usage();
@@ -3004,6 +3158,14 @@ static const struct command all_commands[] = {
{ "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. */
--
1.7.10.4
More information about the dev
mailing list