mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 01:51:26 +00:00
Implement OpenFlow 1.1+ "groups" protocol.
This doesn't include a dpif implementation of groups functionality. In its current form, it is untested. Signed-off-by: Neil Zhu <zhuj@centecnetworks.com> Co-authored-by: Ben Pfaff <blp@nicira.com> Signed-off-by: Ben Pfaff <blp@nicira.com> Co-authored-by: Simon Horman <horms@verge.net.au> Signed-off-by: Simon Horman <horms@verge.net.au> Co-authored-by: Jarno Rajahalme <jrajahalme@nicira.com> Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
This commit is contained in:
parent
6f8dbd272a
commit
7395c05254
1
AUTHORS
1
AUTHORS
@ -66,6 +66,7 @@ Mehak Mahajan mmahajan@nicira.com
|
||||
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
|
||||
|
7
NEWS
7
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.
|
||||
|
||||
|
||||
|
@ -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]; */
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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. */
|
||||
|
||||
|
290
lib/ofp-parse.c
290
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,287 @@ 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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
228
lib/ofp-print.c
228
lib/ofp-print.c
@ -2140,6 +2140,198 @@ ofp_print_not_implemented(struct ds *string)
|
||||
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)
|
||||
@ -2149,17 +2341,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_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_GROUP_STATS_REQUEST:
|
||||
case OFPTYPE_GROUP_STATS_REPLY:
|
||||
case OFPTYPE_GROUP_DESC_STATS_REQUEST:
|
||||
case OFPTYPE_GROUP_DESC_STATS_REPLY:
|
||||
case OFPTYPE_GROUP_FEATURES_STATS_REQUEST:
|
||||
case OFPTYPE_GROUP_FEATURES_STATS_REPLY:
|
||||
case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
|
||||
case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
|
||||
ofp_print_not_implemented(string);
|
||||
|
609
lib/ofp-util.c
609
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);
|
||||
@ -2486,7 +2490,7 @@ unknown_to_zero(uint64_t count)
|
||||
|
||||
/* 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)
|
||||
@ -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 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
|
||||
@ -5193,6 +5256,540 @@ ofputil_decode_port_stats_request(const struct ofp_header *request,
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
@ -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)
|
||||
|
@ -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,88 @@ 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);
|
||||
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 */
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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 */
|
||||
};
|
||||
|
@ -111,6 +111,12 @@ 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 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);
|
||||
@ -289,16 +295,50 @@ 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 {
|
||||
/* 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.
|
||||
@ -307,6 +347,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 +371,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 +1495,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;
|
||||
|
@ -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,8 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
|
||||
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) {
|
||||
@ -1124,6 +1127,8 @@ ofproto_flush__(struct ofproto *ofproto)
|
||||
}
|
||||
}
|
||||
|
||||
static void delete_group(struct ofproto *ofproto, uint32_t group_id);
|
||||
|
||||
static void
|
||||
ofproto_destroy__(struct ofproto *ofproto)
|
||||
{
|
||||
@ -1137,6 +1142,10 @@ 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);
|
||||
@ -2249,6 +2258,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 +2907,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 +2961,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 +2989,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 +3043,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 +3083,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 +3210,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 +3673,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 +3698,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 +3757,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 +3775,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 +3838,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 +4696,402 @@ handle_meter_request(struct ofconn *ofconn, const struct ofp_header *request,
|
||||
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)
|
||||
{
|
||||
@ -4710,6 +5127,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 +5198,18 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
|
||||
case OFPTYPE_METER_FEATURES_STATS_REQUEST:
|
||||
return handle_meter_features_request(ofconn, oh);
|
||||
|
||||
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_GROUP_STATS_REQUEST:
|
||||
case OFPTYPE_GROUP_DESC_STATS_REQUEST:
|
||||
case OFPTYPE_GROUP_FEATURES_STATS_REQUEST:
|
||||
case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
|
||||
return OFPERR_OFPBRC_BAD_TYPE;
|
||||
|
||||
|
@ -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 "\
|
||||
|
@ -190,6 +190,29 @@ statistics are printed for all queues on \fIport\fR; if only
|
||||
\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
|
||||
@ -258,6 +281,30 @@ keyword \fBLOCAL\fR (the preferred way to refer to the OpenFlow
|
||||
``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"
|
||||
@ -1439,6 +1486,75 @@ of \fBduration\fR. (This is separate from \fBduration\fR because
|
||||
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
|
||||
|
@ -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"
|
||||
@ -1842,6 +1849,153 @@ ofctl_benchmark(int argc OVS_UNUSED, char *argv[])
|
||||
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)
|
||||
{
|
||||
@ -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. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user