From: Neil Zhu Date: Mon, 2 Sep 2013 01:30:17 +0000 (-0700) Subject: Implement OpenFlow 1.1+ "groups" protocol. X-Git-Tag: sliver-openvswitch-2.0.90-1~16^2~40 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=7395c05254df87ce52b37b04478e802befd799d9;p=sliver-openvswitch.git 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 Co-authored-by: Ben Pfaff Signed-off-by: Ben Pfaff Co-authored-by: Simon Horman Signed-off-by: Simon Horman Co-authored-by: Jarno Rajahalme Signed-off-by: Jarno Rajahalme --- diff --git a/AUTHORS b/AUTHORS index af34bfedd..a708fb2a7 100644 --- a/AUTHORS +++ b/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 diff --git a/NEWS b/NEWS index 7c1b480ce..8bdc62114 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,12 @@ v2.0.0 - xx xxx xxxx - OpenFlow: * Experimental support for OpenFlow 1.1 (in addition to 1.2 and 1.3, which had experimental support in 1.10). + * Experimental protocol support for OpenFlow 1.1+ groups. This + does not yet include an implementation in the Open vSwitch + software switch. + * Experimental protocol support for OpenFlow 1.2+ meters. This + does not yet include an implementation in the Open vSwitch + software switch. * New support for matching outer source and destination IP address of tunneled packets, for tunnel ports configured with the newly added "remote_ip=flow" and "local_ip=flow" options. @@ -26,6 +32,7 @@ v2.0.0 - xx xxx xxxx - Support for Linux kernels up to 3.10 - ovs-ofctl: * New "ofp-parse" for printing OpenFlow messages read from a file. + * New commands for OpenFlow 1.1+ groups. - Added configurable flow caching support to IPFIX exporter. diff --git a/include/openflow/openflow-1.1.h b/include/openflow/openflow-1.1.h index f7c2e78bc..a2309b1f8 100644 --- a/include/openflow/openflow-1.1.h +++ b/include/openflow/openflow-1.1.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008, 2011, 2012 The Board of Trustees of The Leland Stanford +/* Copyright (c) 2008, 2011, 2012, 2013 The Board of Trustees of The Leland Stanford * Junior University * * We are making the OpenFlow specification and associated documentation @@ -150,8 +150,8 @@ OFP_ASSERT(sizeof(struct ofp11_port_mod) == 32); /* Group setup and teardown (controller -> datapath). */ struct ofp11_group_mod { - ovs_be16 command; /* One of OFPGC_*. */ - uint8_t type; /* One of OFPGT_*. */ + ovs_be16 command; /* One of OFPGC11_*. */ + uint8_t type; /* One of OFPGT11_*. */ uint8_t pad; /* Pad to 64 bits. */ ovs_be32 group_id; /* Group identifier. */ /* struct ofp11_bucket buckets[0]; The bucket length is inferred from the @@ -725,7 +725,7 @@ OFP_ASSERT(sizeof(struct ofp11_bucket_counter) == 16); /* Body of reply to OFPST11_GROUP_DESC request. */ struct ofp11_group_desc_stats { ovs_be16 length; /* Length of this entry. */ - uint8_t type; /* One of OFPGT_*. */ + uint8_t type; /* One of OFPGT11_*. */ uint8_t pad; /* Pad to 64 bits. */ ovs_be32 group_id; /* Group identifier. */ /* struct ofp11_bucket buckets[0]; */ diff --git a/include/openflow/openflow-1.2.h b/include/openflow/openflow-1.2.h index 29770472e..d1e42a4db 100644 --- a/include/openflow/openflow-1.2.h +++ b/include/openflow/openflow-1.2.h @@ -301,7 +301,7 @@ OFP_ASSERT(sizeof(struct ofp12_table_stats) == 128); /* Body of reply to OFPST12_GROUP_FEATURES request. Group features. */ struct ofp12_group_features_stats { - ovs_be32 types; /* Bitmap of OFPGT_* values supported. */ + ovs_be32 types; /* Bitmap of OFPGT11_* values supported. */ ovs_be32 capabilities; /* Bitmap of OFPGFC12_* capability supported. */ ovs_be32 max_groups[4]; /* Maximum number of groups for each type. */ ovs_be32 actions[4]; /* Bitmaps of OFPAT_* that are supported. */ diff --git a/include/openflow/openflow-common.h b/include/openflow/openflow-common.h index a9e5a7631..5018f8500 100644 --- a/include/openflow/openflow-common.h +++ b/include/openflow/openflow-common.h @@ -306,7 +306,7 @@ OFP_ASSERT(sizeof(struct ofp_action_vendor_header) == 8); * header and any padding used to make the action 64-bit aligned. * NB: The length of an action *must* always be a multiple of eight. */ struct ofp_action_header { - ovs_be16 type; /* One of OFPAT10_*. */ + ovs_be16 type; /* One of OFPAT*. */ ovs_be16 len; /* Length of action, including this header. This is the length of action, including any padding to make it diff --git a/lib/learning-switch.c b/lib/learning-switch.c index 336257c64..c587930ba 100644 --- a/lib/learning-switch.c +++ b/lib/learning-switch.c @@ -346,6 +346,7 @@ lswitch_process_packet(struct lswitch *sw, const struct ofpbuf *msg) case OFPTYPE_PORT_STATUS: case OFPTYPE_PACKET_OUT: case OFPTYPE_FLOW_MOD: + case OFPTYPE_GROUP_MOD: case OFPTYPE_PORT_MOD: case OFPTYPE_BARRIER_REQUEST: case OFPTYPE_BARRIER_REPLY: diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 61e285429..77aa69cec 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -860,6 +860,12 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out) break; } + case OFPUTIL_OFPAT11_GROUP: { + struct ofp11_action_group *oag = (struct ofp11_action_group *)a; + ofpact_put_GROUP(out)->group_id = ntohl(oag->group_id); + break; + } + #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM: #include "ofp-util.def" return ofpact_from_nxast(a, code, out); @@ -945,6 +951,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type) case OFPACT_GOTO_TABLE: return OVSINST_OFPIT11_GOTO_TABLE; case OFPACT_OUTPUT: + case OFPACT_GROUP: case OFPACT_CONTROLLER: case OFPACT_ENQUEUE: case OFPACT_OUTPUT_REG: @@ -1302,6 +1309,9 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports, } return 0; + case OFPACT_GROUP: + return 0; + default: NOT_REACHED(); } @@ -1598,6 +1608,7 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out) ofpact_sample_to_nxast(ofpact_get_SAMPLE(a), out); break; + case OFPACT_GROUP: case OFPACT_OUTPUT: case OFPACT_ENQUEUE: case OFPACT_SET_VLAN_VID: @@ -1710,6 +1721,9 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out) /* XXX */ break; + case OFPACT_GROUP: + break; + case OFPACT_CONTROLLER: case OFPACT_OUTPUT_REG: case OFPACT_BUNDLE: @@ -1882,6 +1896,11 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out) case OFPACT_METER: NOT_REACHED(); + case OFPACT_GROUP: + ofputil_put_OFPAT11_GROUP(out)->group_id = + htonl(ofpact_get_GROUP(a)->group_id); + break; + case OFPACT_CONTROLLER: case OFPACT_OUTPUT_REG: case OFPACT_BUNDLE: @@ -2051,6 +2070,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) case OFPACT_CLEAR_ACTIONS: case OFPACT_GOTO_TABLE: case OFPACT_METER: + case OFPACT_GROUP: default: return false; } @@ -2073,6 +2093,24 @@ ofpacts_output_to_port(const struct ofpact *ofpacts, size_t ofpacts_len, return false; } +/* Returns true if any action in the 'ofpacts_len' bytes of 'ofpacts' outputs + * to 'group', false otherwise. */ +bool +ofpacts_output_to_group(const struct ofpact *ofpacts, size_t ofpacts_len, + uint32_t group_id) +{ + const struct ofpact *a; + + OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { + if (a->type == OFPACT_GROUP + && ofpact_get_GROUP(a)->group_id == group_id) { + return true; + } + } + + return false; +} + bool ofpacts_equal(const struct ofpact *a, size_t a_len, const struct ofpact *b, size_t b_len) @@ -2383,6 +2421,11 @@ ofpact_format(const struct ofpact *a, struct ds *s) ovs_instruction_name_from_type(OVSINST_OFPIT13_METER), ofpact_get_METER(a)->meter_id); break; + + case OFPACT_GROUP: + ds_put_format(s, "group:%"PRIu32, + ofpact_get_GROUP(a)->group_id); + break; } } diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index 101c33dd1..a3fb60f27 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -51,6 +51,7 @@ #define OFPACTS \ /* Output. */ \ DEFINE_OFPACT(OUTPUT, ofpact_output, ofpact) \ + DEFINE_OFPACT(GROUP, ofpact_group, ofpact) \ DEFINE_OFPACT(CONTROLLER, ofpact_controller, ofpact) \ DEFINE_OFPACT(ENQUEUE, ofpact_enqueue, ofpact) \ DEFINE_OFPACT(OUTPUT_REG, ofpact_output_reg, ofpact) \ @@ -490,6 +491,14 @@ struct ofpact_goto_table { uint8_t table_id; }; +/* OFPACT_GROUP. + * + * Used for OFPAT11_GROUP. */ +struct ofpact_group { + struct ofpact ofpact; + uint32_t group_id; +}; + /* Converting OpenFlow to ofpacts. */ enum ofperr ofpacts_pull_openflow10(struct ofpbuf *openflow, unsigned int actions_len, @@ -517,6 +526,8 @@ void ofpacts_put_openflow11_instructions(const struct ofpact[], /* Working with ofpacts. */ bool ofpacts_output_to_port(const struct ofpact[], size_t ofpacts_len, ofp_port_t port); +bool ofpacts_output_to_group(const struct ofpact[], size_t ofpacts_len, + uint32_t group_id); bool ofpacts_equal(const struct ofpact a[], size_t a_len, const struct ofpact b[], size_t b_len); diff --git a/lib/ofp-msgs.h b/lib/ofp-msgs.h index 3ead49e29..47256de53 100644 --- a/lib/ofp-msgs.h +++ b/lib/ofp-msgs.h @@ -177,6 +177,9 @@ enum ofpraw { /* NXT 1.0+ (13): struct nx_flow_mod, uint8_t[8][]. */ OFPRAW_NXT_FLOW_MOD, + /* OFPT 1.1+ (15): struct ofp11_group_mod, uint8_t[8][]. */ + OFPRAW_OFPT11_GROUP_MOD, + /* OFPT 1.0 (15): struct ofp10_port_mod. */ OFPRAW_OFPT10_PORT_MOD, /* OFPT 1.1+ (16): struct ofp11_port_mod. */ @@ -295,15 +298,15 @@ enum ofpraw { /* OFPST 1.1+ (6): struct ofp11_group_stats_request. */ OFPRAW_OFPST11_GROUP_REQUEST, - /* OFPST 1.1-1.2 (6): struct ofp11_group_stats[]. */ + /* OFPST 1.1-1.2 (6): uint8_t[8][]. */ OFPRAW_OFPST11_GROUP_REPLY, - /* OFPST 1.3 (6): struct ofp13_group_stats[]. */ + /* OFPST 1.3 (6): uint8_t[8][]. */ OFPRAW_OFPST13_GROUP_REPLY, /* OFPST 1.1+ (7): void. */ OFPRAW_OFPST11_GROUP_DESC_REQUEST, - /* OFPST 1.1+ (7): struct ofp11_group_desc_stats[]. */ + /* OFPST 1.1+ (7): uint8_t[8][]. */ OFPRAW_OFPST11_GROUP_DESC_REPLY, /* OFPST 1.2+ (8): void. */ @@ -460,6 +463,7 @@ enum ofptype { OFPTYPE_FLOW_MOD, /* OFPRAW_OFPT10_FLOW_MOD. * OFPRAW_OFPT11_FLOW_MOD. * OFPRAW_NXT_FLOW_MOD. */ + OFPTYPE_GROUP_MOD, /* OFPRAW_OFPT11_GROUP_MOD. */ OFPTYPE_PORT_MOD, /* OFPRAW_OFPT10_PORT_MOD. * OFPRAW_OFPT11_PORT_MOD. */ diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index 176f61f2a..ad3981c57 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -819,6 +819,10 @@ parse_named_action(enum ofputil_action_code code, } break; + case OFPUTIL_OFPAT11_GROUP: + error = str_to_u32(arg, &ofpact_put_GROUP(ofpacts)->group_id); + break; + case OFPUTIL_NXAST_STACK_PUSH: error = nxm_parse_stack_action(ofpact_put_STACK_PUSH(ofpacts), arg); break; @@ -1157,6 +1161,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string, fm->buffer_id = UINT32_MAX; fm->out_port = OFPP_ANY; fm->flags = 0; + fm->out_group = OFPG11_ANY; if (fields & F_ACTIONS) { act_str = strstr(string, "action"); if (!act_str) { @@ -1794,6 +1799,7 @@ parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr, fsr->cookie_mask = fm.cookie_mask; fsr->match = fm.match; fsr->out_port = fm.out_port; + fsr->out_group = fm.out_group; fsr->table_id = fm.table_id; return NULL; } @@ -1878,3 +1884,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; +} diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h index cdb68311c..1c3228abd 100644 --- a/lib/ofp-parse.h +++ b/lib/ofp-parse.h @@ -29,6 +29,7 @@ struct ofpbuf; struct ofputil_flow_mod; struct ofputil_flow_monitor_request; struct ofputil_flow_stats_request; +struct ofputil_group_mod; struct ofputil_meter_mod; enum ofputil_protocol; @@ -66,4 +67,14 @@ char *parse_flow_monitor_request(struct ofputil_flow_monitor_request *, enum ofputil_protocol *usable_protocols) WARN_UNUSED_RESULT; +char *parse_ofp_group_mod_file(const char *file_name, uint16_t command, + struct ofputil_group_mod **gms, size_t *n_gms, + enum ofputil_protocol *usable_protocols) + WARN_UNUSED_RESULT; + +char *parse_ofp_group_mod_str(struct ofputil_group_mod *, uint16_t command, + const char *string, + enum ofputil_protocol *usable_protocols) + WARN_UNUSED_RESULT; + #endif /* ofp-parse.h */ diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 560762bfa..702d9c144 100644 --- a/lib/ofp-print.c +++ b/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_QUEUE_GET_CONFIG_REQUEST: - case OFPTYPE_QUEUE_GET_CONFIG_REPLY: - case OFPTYPE_GET_ASYNC_REQUEST: - case OFPTYPE_GET_ASYNC_REPLY: case OFPTYPE_GROUP_STATS_REQUEST: + ofp_print_stats_request(string, oh); + ofp_print_ofpst_group_request(string, oh); + break; + case OFPTYPE_GROUP_STATS_REPLY: + ofp_print_group_stats(string, oh); + break; + case OFPTYPE_GROUP_DESC_STATS_REQUEST: + ofp_print_stats_request(string, oh); + break; + case OFPTYPE_GROUP_DESC_STATS_REPLY: + ofp_print_group_desc(string, oh); + break; + case OFPTYPE_GROUP_FEATURES_STATS_REQUEST: + ofp_print_stats_request(string, oh); + break; + case OFPTYPE_GROUP_FEATURES_STATS_REPLY: + ofp_print_group_features(string, oh); + break; + + case OFPTYPE_GROUP_MOD: + ofp_print_group_mod(string, oh); + break; + + case OFPTYPE_QUEUE_GET_CONFIG_REQUEST: + case OFPTYPE_QUEUE_GET_CONFIG_REPLY: + case OFPTYPE_GET_ASYNC_REQUEST: + case OFPTYPE_GET_ASYNC_REPLY: case OFPTYPE_TABLE_FEATURES_STATS_REQUEST: case OFPTYPE_TABLE_FEATURES_STATS_REPLY: ofp_print_not_implemented(string); diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 61fb74feb..b181a0df3 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -1537,6 +1537,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm, if (error) { return error; } + fm->out_group = ntohl(ofm->out_group); + if ((ofm->command == OFPFC_DELETE || ofm->command == OFPFC_DELETE_STRICT) && ofm->out_group != htonl(OFPG_ANY)) { @@ -1578,6 +1580,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm, fm->hard_timeout = ntohs(ofm->hard_timeout); fm->buffer_id = ntohl(ofm->buffer_id); fm->out_port = u16_to_ofp(ntohs(ofm->out_port)); + fm->out_group = OFPG11_ANY; raw_flags = ofm->flags; } else if (raw == OFPRAW_NXT_FLOW_MOD) { /* Nicira extended flow_mod. */ @@ -1608,6 +1611,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm, fm->hard_timeout = ntohs(nfm->hard_timeout); fm->buffer_id = ntohl(nfm->buffer_id); fm->out_port = u16_to_ofp(ntohs(nfm->out_port)); + fm->out_group = OFPG11_ANY; raw_flags = nfm->flags; } else { NOT_REACHED(); @@ -2058,7 +2062,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm, ofm->priority = htons(fm->priority); ofm->buffer_id = htonl(fm->buffer_id); ofm->out_port = ofputil_port_to_ofp11(fm->out_port); - ofm->out_group = htonl(OFPG11_ANY); + ofm->out_group = htonl(fm->out_group); ofm->flags = raw_flags; ofputil_put_ofp11_match(msg, &fm->match, protocol); ofpacts_put_openflow11_instructions(fm->ofpacts, fm->ofpacts_len, msg); @@ -2124,6 +2128,7 @@ ofputil_decode_ofpst10_flow_request(struct ofputil_flow_stats_request *fsr, fsr->aggregate = aggregate; ofputil_match_from_ofp10_match(&ofsr->match, &fsr->match); fsr->out_port = u16_to_ofp(ntohs(ofsr->out_port)); + fsr->out_group = OFPG11_ANY; fsr->table_id = ofsr->table_id; fsr->cookie = fsr->cookie_mask = htonll(0); @@ -2144,9 +2149,7 @@ ofputil_decode_ofpst11_flow_request(struct ofputil_flow_stats_request *fsr, if (error) { return error; } - if (ofsr->out_group != htonl(OFPG11_ANY)) { - return OFPERR_OFPFMFC_UNKNOWN; - } + fsr->out_group = ntohl(ofsr->out_group); fsr->cookie = ofsr->cookie; fsr->cookie_mask = ofsr->cookie_mask; error = ofputil_pull_ofp11_match(b, &fsr->match, NULL); @@ -2176,6 +2179,7 @@ ofputil_decode_nxst_flow_request(struct ofputil_flow_stats_request *fsr, fsr->aggregate = aggregate; fsr->out_port = u16_to_ofp(ntohs(nfsr->out_port)); + fsr->out_group = OFPG11_ANY; fsr->table_id = nfsr->table_id; return 0; @@ -2242,7 +2246,7 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr, ofsr = ofpbuf_put_zeros(msg, sizeof *ofsr); ofsr->table_id = fsr->table_id; ofsr->out_port = ofputil_port_to_ofp11(fsr->out_port); - ofsr->out_group = htonl(OFPG11_ANY); + ofsr->out_group = htonl(fsr->out_group); ofsr->cookie = fsr->cookie; ofsr->cookie_mask = fsr->cookie_mask; ofputil_put_ofp11_match(msg, &fsr->match, protocol); @@ -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 diff --git a/lib/ofp-util.def b/lib/ofp-util.def index d88d4206c..f5eec73ed 100644 --- a/lib/ofp-util.def +++ b/lib/ofp-util.def @@ -40,6 +40,7 @@ OFPAT11_ACTION(OFPAT11_SET_QUEUE, ofp11_action_set_queue, 0, "set_queue") //OFPAT11_ACTION(OFPAT11_SET_NW_TTL, ofp11_action_nw_ttl, 0, "set_nw_ttl") OFPAT11_ACTION(OFPAT11_DEC_NW_TTL, ofp_action_header, 0, NULL) OFPAT11_ACTION(OFPAT12_SET_FIELD, ofp12_action_set_field, 1, "set_field") +OFPAT11_ACTION(OFPAT11_GROUP, ofp11_action_group, 0, "group") #ifndef NXAST_ACTION #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) diff --git a/lib/ofp-util.h b/lib/ofp-util.h index 7e50db2a0..6de2a05e1 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -43,6 +43,14 @@ void ofputil_format_port(ofp_port_t port, struct ds *); void ofputil_port_to_string(ofp_port_t, char namebuf[OFP_MAX_PORT_NAME_LEN], size_t bufsize); +/* Group numbers. */ +enum { MAX_GROUP_NAME_LEN = INT_STRLEN(uint32_t) }; +bool ofputil_group_from_string(const char *, uint32_t *group_id); +void ofputil_format_group(uint32_t group_id, struct ds *); +void ofputil_group_to_string(uint32_t group_id, + char namebuf[MAX_GROUP_NAME_LEN + 1], + size_t bufsize); + /* Converting OFPFW10_NW_SRC_MASK and OFPFW10_NW_DST_MASK wildcard bit counts * to and from IP bitmasks. */ ovs_be32 ofputil_wcbits_to_netmask(int wcbits); @@ -268,6 +276,7 @@ struct ofputil_flow_mod { uint16_t hard_timeout; uint32_t buffer_id; ofp_port_t out_port; + uint32_t out_group; enum ofputil_flow_mod_flags flags; struct ofpact *ofpacts; /* Series of "struct ofpact"s. */ size_t ofpacts_len; /* Length of ofpacts, in bytes. */ @@ -287,6 +296,7 @@ struct ofputil_flow_stats_request { ovs_be64 cookie; ovs_be64 cookie_mask; ofp_port_t out_port; + uint32_t out_group; uint8_t table_id; }; @@ -867,4 +877,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 */ diff --git a/lib/rconn.c b/lib/rconn.c index 39a12c906..17cbd82d2 100644 --- a/lib/rconn.c +++ b/lib/rconn.c @@ -1154,6 +1154,7 @@ is_admitted_msg(const struct ofpbuf *b) case OFPTYPE_PORT_STATUS: case OFPTYPE_PACKET_OUT: case OFPTYPE_FLOW_MOD: + case OFPTYPE_GROUP_MOD: case OFPTYPE_PORT_MOD: case OFPTYPE_METER_MOD: case OFPTYPE_BARRIER_REQUEST: diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index eeccbc573..2901fbeb4 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -2181,6 +2181,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, ofpact_get_OUTPUT(a)->max_len, true); break; + case OFPACT_GROUP: + /* XXX not yet implemented */ + break; + case OFPACT_CONTROLLER: controller = ofpact_get_CONTROLLER(a); execute_controller_action(ctx, controller->max_len, diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 820ec346b..70a226c43 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -6384,4 +6384,10 @@ const struct ofproto_class ofproto_dpif_class = { NULL, /* meter_set */ NULL, /* meter_get */ NULL, /* meter_del */ + NULL, /* group_alloc */ + NULL, /* group_construct */ + NULL, /* group_destruct */ + NULL, /* group_dealloc */ + NULL, /* group_modify */ + NULL, /* group_get_stats */ }; diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index d6a8a0bd9..ab640c222 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -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; diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index 0b054e74d..21155dcda 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -213,6 +213,7 @@ static enum ofperr modify_flows__(struct ofproto *, struct ofconn *, static void delete_flow__(struct rule *rule, struct ofopgroup *, enum ofp_flow_removed_reason) OVS_RELEASES(rule->evict); +static enum ofperr add_group(struct ofproto *, struct ofputil_group_mod *); static bool handle_openflow(struct ofconn *, const struct ofpbuf *); static enum ofperr handle_flow_mod__(struct ofproto *, struct ofconn *, struct ofputil_flow_mod *, @@ -456,6 +457,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); - /* FIXME: Change the following once they are implemented: */ - case OFPTYPE_QUEUE_GET_CONFIG_REQUEST: - case OFPTYPE_GET_ASYNC_REQUEST: case OFPTYPE_GROUP_STATS_REQUEST: + return handle_group_stats_request(ofconn, oh); + case OFPTYPE_GROUP_DESC_STATS_REQUEST: + return handle_group_desc_stats_request(ofconn, oh); + case OFPTYPE_GROUP_FEATURES_STATS_REQUEST: + return handle_group_features_stats_request(ofconn, oh); + + /* FIXME: Change the following once they are implemented: */ + case OFPTYPE_QUEUE_GET_CONFIG_REQUEST: + case OFPTYPE_GET_ASYNC_REQUEST: case OFPTYPE_TABLE_FEATURES_STATS_REQUEST: return OFPERR_OFPBRC_BAD_TYPE; diff --git a/tests/ofp-print.at b/tests/ofp-print.at index f554aba22..4a627936d 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -1585,13 +1585,6 @@ OFPST_QUEUE reply (xid=0x1): 6 queues ]) AT_CLEANUP -AT_SETUP([OFPST_PORT_DESC request - OF1.0]) -AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) -AT_CHECK([ovs-ofctl ofp-print "0110000c00000001000d0000"], [0], [dnl -OFPST_PORT_DESC request (xid=0x1): -]) -AT_CLEANUP - AT_SETUP([OFPST_QUEUE reply - OF1.1]) AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) AT_CHECK([ovs-ofctl ofp-print "\ @@ -1679,6 +1672,120 @@ OFPST_QUEUE reply (OF1.3) (xid=0x1): 6 queues ]) AT_CLEANUP +AT_SETUP([OFPST_GROUP request]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "\ +02 12 00 18 00 00 00 02 00 06 00 00 00 00 00 00 \ +ff ff ff ff 00 00 00 00 \ +"], [0], [OFPST_GROUP request (OF1.1) (xid=0x2): group_id=ANY +]) +AT_CLEANUP + +AT_SETUP([OFPST_GROUP reply - OF1.1]) +AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) +AT_CHECK([ovs-ofctl ofp-print "\ +02 13 00 a0 00 00 00 02 00 06 00 00 00 00 00 00 \ +00 50 00 00 87 65 43 21 00 00 00 04 00 00 00 00 \ +00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +00 00 00 00 00 00 66 66 00 00 00 00 00 33 33 33 \ +00 40 00 00 00 00 00 05 00 00 00 02 00 00 00 00 \ +00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +"], [0], [dnl +OFPST_GROUP reply (OF1.1) (xid=0x2): + group_id=2271560481,ref_count=4,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962,bucket2:packet_count=26214,byte_count=3355443 + group_id=5,ref_count=2,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962 +]) +AT_CLEANUP + +AT_SETUP([OFPST_GROUP reply - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) +AT_CHECK([ovs-ofctl ofp-print "\ +04 13 00 b0 00 00 00 02 00 06 00 00 00 00 00 00 \ +00 58 00 00 87 65 43 21 00 00 00 04 00 00 00 00 \ +00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \ +00 00 00 12 1d cd 65 00 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +00 00 00 00 00 00 66 66 00 00 00 00 00 33 33 33 \ +00 48 00 00 00 00 00 05 00 00 00 02 00 00 00 00 \ +00 00 00 00 00 00 88 88 00 00 00 00 00 77 77 77 \ +00 00 00 10 1d cd 65 00 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +00 00 00 00 00 00 11 11 00 00 00 00 00 22 22 22 \ +"], [0], [dnl +OFPST_GROUP reply (OF1.3) (xid=0x2): + group_id=2271560481,duration=18.5s,ref_count=4,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962,bucket2:packet_count=26214,byte_count=3355443 + group_id=5,duration=16.5s,ref_count=2,packet_count=34952,byte_count=7829367,bucket0:packet_count=4369,byte_count=2236962,bucket1:packet_count=4369,byte_count=2236962 +]) +AT_CLEANUP + +AT_SETUP([OFPST_GROUP_DESC request]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "\ +02 12 00 10 00 00 00 02 00 07 00 00 00 00 00 00 \ +"], [0], [OFPST_GROUP_DESC request (OF1.1) (xid=0x2): +]) +AT_CLEANUP + +AT_SETUP([OFPST_GROUP_DESC reply]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "\ +02 13 00 78 00 00 00 02 00 07 00 00 00 00 00 00 \ +00 68 01 00 00 00 20 00 \ +00 20 00 64 00 00 00 01 ff ff ff ff 00 00 00 00 \ +00 00 00 10 00 00 00 01 00 00 00 00 00 00 00 00 \ +00 20 00 c8 00 00 00 02 ff ff ff ff 00 00 00 00 \ +00 00 00 10 00 00 00 02 00 00 00 00 00 00 00 00 \ +00 20 00 c8 00 00 00 03 ff ff ff ff 00 00 00 00 \ +00 00 00 10 00 00 00 03 00 00 00 00 00 00 00 00 \ +"], [0], [dnl +OFPST_GROUP_DESC reply (OF1.1) (xid=0x2): + group_id=8192,type=select,bucket=weight:100,watch_port:1,actions=output:1,bucket=weight:200,watch_port:2,actions=output:2,bucket=weight:200,watch_port:3,actions=output:3 +]) +AT_CLEANUP + +AT_SETUP([OFPST_GROUP_FEATURES request]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "\ +03 12 00 10 00 00 00 02 00 08 00 00 00 00 00 00 \ +"], [0], [OFPST_GROUP_FEATURES request (OF1.2) (xid=0x2): +]) +AT_CLEANUP + +AT_SETUP([OFPST_GROUP_FEATURES reply]) +AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) +AT_CHECK([ovs-ofctl ofp-print "\ +03 13 00 38 00 00 00 02 00 08 00 00 00 00 00 00 \ +00 00 00 0f 00 00 00 0f \ +00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 \ +00 00 00 01 00 00 00 03 00 00 00 07 00 00 00 0f \ +"], [0], [dnl +OFPST_GROUP_FEATURES reply (OF1.2) (xid=0x2): + Group table: + Types: 0xf + Capabilities: 0xf + All group : + max_groups = 0x1 actions=0x00000001 + Select group : + max_groups = 0x2 actions=0x00000003 + Indirect group : + max_groups = 0x3 actions=0x00000007 + Fast Failover group : + max_groups = 0x4 actions=0x0000000f +]) +AT_CLEANUP + +AT_SETUP([OFPST_PORT_DESC request - OF1.0]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "0110000c00000001000d0000"], [0], [dnl +OFPST_PORT_DESC request (xid=0x1): +]) +AT_CLEANUP + AT_SETUP([OFPST_PORT_DESC reply - OF1.0]) AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) AT_CHECK([ovs-ofctl ofp-print "\ @@ -2068,6 +2175,22 @@ NXT_FLOW_MOD (xid=0x2): ADD NXM_NX_TUN_ID(00000000000001c8), NXM_NX_REG0(0000007 ]) AT_CLEANUP +AT_SETUP([OFPT_GROUP_MOD]) +AT_KEYWORDS([ofp-print]) +AT_CHECK([ovs-ofctl ofp-print "\ +02 0f 00 70 11 22 33 44 00 00 01 00 87 65 43 21 \ +00 20 00 64 00 00 00 01 ff ff ff ff 00 00 00 00 \ +00 00 00 10 00 00 00 01 00 00 00 00 00 00 00 00 \ +00 20 00 c8 00 00 00 02 ff ff ff ff 00 00 00 00 \ +00 00 00 10 00 00 00 02 00 00 00 00 00 00 00 00 \ +00 20 00 c8 00 00 00 03 ff ff ff ff 00 00 00 00 \ +00 00 00 10 00 00 00 03 00 00 00 00 00 00 00 00 \ +"], [0], [dnl +OFPT_GROUP_MOD (OF1.1) (xid=0x11223344): + ADD group_id=2271560481,type=select,bucket=weight:100,watch_port:1,actions=output:1,bucket=weight:200,watch_port:2,actions=output:2,bucket=weight:200,watch_port:3,actions=output:3 +]) +AT_CLEANUP + AT_SETUP([NXT_FLOW_REMOVED]) AT_KEYWORDS([ofp-print]) AT_CHECK([ovs-ofctl ofp-print "\ diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 85b2e44ca..18a177243 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -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 diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index 547d50c5b..929475232 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -301,6 +301,13 @@ usage(void) " monitor SWITCH [MISSLEN] [invalid_ttl] [watch:[...]]\n" " print packets received from SWITCH\n" " snoop SWITCH snoop on SWITCH and its controller\n" + " add-group SWITCH GROUP add group described by GROUP\n" + " add-group SWITCH FILE add group from FILE\n" + " mod-group SWITCH GROUP modify specific group\n" + " del-groups SWITCH [GROUP] delete matching GROUPs\n" + " dump-group-features SWITCH print group features\n" + " dump-groups SWITCH print group description\n" + " dump-group-stats SWITCH [GROUP] print group statistics\n" "\nFor OpenFlow switches and controllers:\n" " probe TARGET probe whether TARGET is up\n" " ping TARGET [N] latency of N-byte echos\n" @@ -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. */