This is in preparation for supporting group mod and desc reply messages with an NMX selection method group experimenter property.
NMX selection method Signed-off-by: Simon Horman <simon.hor...@netronome.com> --- v2 Use list of struct field_array of TLVs rather than OF1.1 match for fields field of NMX selection method property --- lib/meta-flow.c | 26 +++++ lib/meta-flow.h | 13 +++ lib/nx-match.c | 51 ++++++++++ lib/nx-match.h | 3 + lib/ofp-parse.c | 15 ++- lib/ofp-print.c | 4 +- lib/ofp-util.c | 242 +++++++++++++++++++++++++++++++++++++++++++-- lib/ofp-util.h | 15 +++ ofproto/ofproto-provider.h | 5 + ofproto/ofproto.c | 9 ++ utilities/ovs-ofctl.c | 4 +- 11 files changed, 372 insertions(+), 15 deletions(-) diff --git a/lib/meta-flow.c b/lib/meta-flow.c index 9ce4cfe..bd60cb6 100644 --- a/lib/meta-flow.c +++ b/lib/meta-flow.c @@ -2242,3 +2242,29 @@ mf_format_subvalue(const union mf_subvalue *subvalue, struct ds *s) } ds_put_char(s, '0'); } + +void +field_array_push_back(enum mf_field_id id, const union mf_value *value, + struct ovs_list *field_array) +{ + struct field_array *fa; + + fa = xmalloc(sizeof *fa); + + fa->id = id; + fa->value = *value; + + list_init(&fa->list_node); + list_push_back(field_array, &fa->list_node); +} + +void +field_array_delete(struct ovs_list *field_array) +{ + struct field_array *fa, *next; + + LIST_FOR_EACH_SAFE (fa, next, list_node, field_array) { + list_remove(&fa->list_node); + free(fa); + } +} diff --git a/lib/meta-flow.h b/lib/meta-flow.h index 4a6c443..a5716da 100644 --- a/lib/meta-flow.h +++ b/lib/meta-flow.h @@ -22,6 +22,7 @@ #include <netinet/ip6.h> #include "bitmap.h" #include "flow.h" +#include "list.h" #include "ofp-errors.h" #include "packets.h" #include "util.h" @@ -1546,6 +1547,13 @@ union mf_subvalue { }; BUILD_ASSERT_DECL(sizeof(union mf_value) == sizeof (union mf_subvalue)); +/* An array of fields with values */ +struct field_array { + struct ovs_list list_node; /* List of other elements in the array */ + enum mf_field_id id; /* MFF_*. */ + union mf_value value; +}; + /* Finding mf_fields. */ const struct mf_field *mf_from_name(const char *name); @@ -1624,4 +1632,9 @@ void mf_format(const struct mf_field *, struct ds *); void mf_format_subvalue(const union mf_subvalue *subvalue, struct ds *s); +/* Field Arrays. */ +void field_array_push_back(enum mf_field_id id, const union mf_value *, + struct ovs_list *); +void field_array_delete(struct ovs_list *); + #endif /* meta-flow.h */ diff --git a/lib/nx-match.c b/lib/nx-match.c index 114c35b..4f84619 100644 --- a/lib/nx-match.c +++ b/lib/nx-match.c @@ -589,6 +589,57 @@ oxm_pull_match_loose(struct ofpbuf *b, struct match *match) { return oxm_pull_match__(b, false, match); } + +/* Verify an array of OXM TLVs treating value of each TLV as a mask, + * disallowing masks in each TLV and ignoring pre-requisites. */ +enum ofperr +oxm_pull_field_array(const void *fields_data, size_t fields_len, + struct ovs_list *field_array) +{ + struct ofpbuf b; + struct mf_bitmap used = MF_BITMAP_INITIALIZER; + + ofpbuf_use_const(&b, fields_data, fields_len); + while (ofpbuf_size(&b)) { + const uint8_t *pos = ofpbuf_data(&b); + const struct mf_field *field; + union mf_value value, mask; + enum ofperr error; + + error = nx_pull_match_entry(&b, false, &field, &value, &mask); + if (error) { + VLOG_DBG_RL(&rl, "error pulling field array field"); + return error; + } else if (!field) { + VLOG_DBG_RL(&rl, "unknown field array field"); + error = OFPERR_OFPBMC_BAD_FIELD; + } else if (bitmap_is_set(used.bm, field->id)) { + VLOG_DBG_RL(&rl, "duplicate field array field '%s'", field->name); + error = OFPERR_OFPBMC_DUP_FIELD; + } else if (!mf_is_mask_valid(field, &value)) { + VLOG_DBG_RL(&rl, "bad mask in field array field '%s'", field->name); + return OFPERR_OFPBMC_BAD_MASK; + } else if (!is_all_ones(&mask, field->n_bytes)) { + VLOG_DBG_RL(&rl, "mask has a mask in field array field '%s'", + field->name); + return OFPERR_OFPBMC_BAD_VALUE; + } else { + bitmap_set1(used.bm, field->id); + field_array_push_back(field->id, &value, field_array); + } + + if (error) { + const uint8_t *start = fields_data; + + VLOG_DBG_RL(&rl, "error parsing OXM at offset %"PRIdPTR" " + "within field array (%s)", pos - start, + ofperr_to_string(error)); + return error; + } + } + + return 0; +} /* nx_put_match() and helpers. * diff --git a/lib/nx-match.h b/lib/nx-match.h index 9cb6461..1c9821c 100644 --- a/lib/nx-match.h +++ b/lib/nx-match.h @@ -32,6 +32,7 @@ struct ofpact_reg_move; struct ofpact_reg_load; struct ofpact_stack; struct ofpbuf; +struct ovs_list; struct nx_action_reg_load; struct nx_action_reg_move; @@ -55,6 +56,8 @@ enum ofperr nx_pull_match_loose(struct ofpbuf *, unsigned int match_len, ovs_be64 *cookie_mask); enum ofperr oxm_pull_match(struct ofpbuf *, struct match *); enum ofperr oxm_pull_match_loose(struct ofpbuf *, struct match *); +enum ofperr oxm_pull_field_array(const void *, size_t fields_len, + struct ovs_list *); int nx_put_match(struct ofpbuf *, const struct match *, ovs_be64 cookie, ovs_be64 cookie_mask); int oxm_put_match(struct ofpbuf *, const struct match *, enum ofp_version); diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index 9a5df3b..d033382 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -1209,6 +1209,7 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command, gm->group_id = OFPG_ANY; gm->command_bucket_id = OFPG15_BUCKET_ALL; list_init(&gm->buckets); + list_init(&gm->props.fields); if (command == OFPGC11_DELETE && string[0] == '\0') { gm->group_id = OFPG_ALL; return NULL; @@ -1365,7 +1366,7 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command, return NULL; out: - ofputil_bucket_list_destroy(&gm->buckets); + ofputil_uninit_group_mod(gm); return error; } @@ -1380,7 +1381,7 @@ parse_ofp_group_mod_str(struct ofputil_group_mod *gm, uint16_t command, free(string); if (error) { - ofputil_bucket_list_destroy(&gm->buckets); + ofputil_uninit_group_mod(gm); } return error; } @@ -1419,6 +1420,9 @@ parse_ofp_group_mod_file(const char *file_name, uint16_t command, if (list_is_empty(&(*gms)[i].buckets)) { (*gms)[i].buckets.next = NULL; } + if (list_is_empty(&(*gms)[i].props.fields)) { + (*gms)[i].props.fields.next = NULL; + } } *gms = x2nrealloc(*gms, &allocated_gms, sizeof **gms); for (i = 0; i < *n_gms; i++) { @@ -1427,6 +1431,11 @@ parse_ofp_group_mod_file(const char *file_name, uint16_t command, } else { list_init(&(*gms)[i].buckets); } + if ((*gms)[i].props.fields.next) { + list_moved(&(*gms)[i].props.fields); + } else { + list_init(&(*gms)[i].props.fields); + } } } error = parse_ofp_group_mod_str(&(*gms)[*n_gms], command, ds_cstr(&s), @@ -1435,7 +1444,7 @@ parse_ofp_group_mod_file(const char *file_name, uint16_t command, size_t i; for (i = 0; i < *n_gms; i++) { - ofputil_bucket_list_destroy(&(*gms)[i].buckets); + ofputil_uninit_group_mod(*gms + i); } free(*gms); *gms = NULL; diff --git a/lib/ofp-print.c b/lib/ofp-print.c index f4c5bc6..a596979 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -2219,7 +2219,7 @@ ofp_print_group_desc(struct ds *s, const struct ofp_header *oh) ds_put_char(s, ' '); ofp_print_group(s, gd.group_id, gd.type, &gd.buckets, oh->version, false); - ofputil_bucket_list_destroy(&gd.buckets); + ofputil_uninit_group_desc(&gd); } } @@ -2373,7 +2373,7 @@ ofp_print_group_mod(struct ds *s, const struct ofp_header *oh) ofp_print_group(s, gm.group_id, gm.type, &gm.buckets, oh->version, bucket_command); - ofputil_bucket_list_destroy(&gm.buckets); + ofputil_uninit_group_mod(&gm); } static void diff --git a/lib/ofp-util.c b/lib/ofp-util.c index bf55fb2..9019a258 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -38,6 +38,7 @@ #include "ofp-msgs.h" #include "ofp-util.h" #include "ofpbuf.h" +#include "openflow/netronome-ext.h" #include "packets.h" #include "random.h" #include "unaligned.h" @@ -59,6 +60,14 @@ struct ofp_prop_header { ovs_be16 len; }; +struct ofp_prop_experimenter { + ovs_be16 type; /* OFP*_EXPERIMENTER. */ + ovs_be16 length; /* Length in bytes of this property. */ + ovs_be32 experimenter; /* Experimenter ID which takes the same form as + * in struct ofp_experimenter_header. */ + ovs_be32 exp_type; /* Experimenter defined. */ +}; + /* Pulls a property, beginning with struct ofp_prop_header, from the beginning * of 'msg'. Stores the type of the property in '*typep' and, if 'property' is * nonnull, the entire property, including the header, in '*property'. Returns @@ -7014,6 +7023,13 @@ ofputil_encode_group_stats_request(enum ofp_version ofp_version, return request; } +void +ofputil_uninit_group_desc(struct ofputil_group_desc *gd) +{ + ofputil_bucket_list_destroy(&gd->buckets); + field_array_delete(&gd->props.fields); +} + /* Decodes the OpenFlow group description request in 'oh', returning the group * whose description is requested, or OFPG_ALL if stats for all groups was * requested. */ @@ -7716,6 +7732,191 @@ ofputil_pull_ofp15_buckets(struct ofpbuf *msg, size_t buckets_length, return 0; } +static void +ofputil_init_group_properties(struct ofputil_group_props *gp) +{ + memset(gp, 0, sizeof *gp); + list_init(&gp->fields); +} + +static enum ofperr +parse_group_prop_nmx_selection_method(struct ofpbuf *payload, + enum ofp11_group_type group_type, + enum ofp15_group_mod_command group_cmd, + struct ofputil_group_props *gp) +{ + struct nmx_group_prop_selection_method *prop = ofpbuf_data(payload); + size_t fields_len, method_len; + enum ofperr error; + + switch (group_type) { + case OFPGT11_SELECT: + break; + case OFPGT11_ALL: + case OFPGT11_INDIRECT: + case OFPGT11_FF: + log_property(false, "nmx selection method property is only allowed " + "for select groups"); + return OFPERR_OFPBPC_BAD_VALUE; + default: + OVS_NOT_REACHED(); + } + + switch (group_cmd) { + case OFPGC15_ADD: + case OFPGC15_MODIFY: + break; + case OFPGC15_DELETE: + case OFPGC15_INSERT_BUCKET: + case OFPGC15_REMOVE_BUCKET: + log_property(false, "nmx selection method property is only allowed " + "for add and delete group modifications"); + return OFPERR_OFPBPC_BAD_VALUE; + default: + OVS_NOT_REACHED(); + } + + if (ofpbuf_size(payload) < sizeof *prop) { + log_property(false, "nmx selection method property length " + "%u is not valid", ofpbuf_size(payload)); + return OFPERR_OFPBPC_BAD_LEN; + } + + method_len = strnlen(prop->selection_method, NMX_MAX_SELECTION_METHOD_LEN); + + if (method_len == NMX_MAX_SELECTION_METHOD_LEN) { + log_property(false, "nmx selection method is not null terminated"); + return OFPERR_OFPBPC_BAD_VALUE; + } + + strcpy(gp->selection_method, prop->selection_method); + gp->selection_method_param = ntohll(prop->selection_method_param); + + if (!method_len && gp->selection_method_param) { + log_property(false, "nmx selection method parameter is non-zero but " + "selection method is empty"); + return OFPERR_OFPBPC_BAD_VALUE; + } + + ofpbuf_pull(payload, sizeof *prop); + + fields_len = ntohs(prop->length) - sizeof *prop; + if (!method_len && fields_len) { + log_property(false, "nmx selection method parameter is zero " + "but fields are provided"); + return OFPERR_OFPBPC_BAD_VALUE; + } + + error = oxm_pull_field_array(ofpbuf_data(payload), fields_len, + &gp->fields); + if (error) { + log_property(false, "nmx selection method fields are invalid"); + return error; + } + + return 0; +} + +static enum ofperr +parse_group_prop_nmx(struct ofpbuf *payload, uint32_t exp_type, + enum ofp11_group_type group_type, + enum ofp15_group_mod_command group_cmd, + struct ofputil_group_props *gp) +{ + enum ofperr error; + + switch (exp_type) { + case NMXT_SELECTION_METHOD: + error = parse_group_prop_nmx_selection_method(payload, group_type, + group_cmd, gp); + break; + + default: + log_property(false, "unknown group property nmx experimenter type " + "%"PRIu32, exp_type); + error = OFPERR_OFPBPC_BAD_TYPE; + break; + } + + return error; +} + +static enum ofperr +parse_ofp15_group_prop_exp(struct ofpbuf *payload, + enum ofp11_group_type group_type, + enum ofp15_group_mod_command group_cmd, + struct ofputil_group_props *gp) +{ + struct ofp_prop_experimenter *prop = ofpbuf_data(payload); + uint16_t experimenter; + uint32_t exp_type; + enum ofperr error; + + if (ofpbuf_size(payload) < sizeof *prop) { + return OFPERR_OFPBPC_BAD_LEN; + } + + experimenter = ntohl(prop->experimenter); + exp_type = ntohl(prop->exp_type); + + switch (experimenter) { + case NMX_VENDOR_ID: + error = parse_group_prop_nmx(payload, exp_type, group_type, + group_cmd, gp); + break; + + default: + log_property(false, "unknown group property experimenter %"PRIu16, + experimenter); + error = OFPERR_OFPBPC_BAD_EXPERIMENTER; + break; + } + + return error; +} + +static enum ofperr +parse_ofp15_group_properties(struct ofpbuf *msg, + enum ofp11_group_type group_type, + enum ofp15_group_mod_command group_cmd, + struct ofputil_group_props *gp, + size_t properties_len) +{ + struct ofpbuf properties; + + ofpbuf_use_const(&properties, ofpbuf_pull(msg, properties_len), + properties_len); + + while (ofpbuf_size(&properties) > 0) { + struct ofpbuf payload; + enum ofperr error; + uint16_t type; + + error = ofputil_pull_property(&properties, &payload, &type); + if (error) { + return error; + } + + switch (type) { + case OFPGPT15_EXPERIMENTER: + error = parse_ofp15_group_prop_exp(&payload, group_type, + group_cmd, gp); + break; + + default: + log_property(false, "unknown group property %"PRIu16, type); + error = OFPERR_OFPBPC_BAD_TYPE; + break; + } + + if (error) { + return error; + } + } + + return 0; +} + static int ofputil_decode_ofp11_group_desc_reply(struct ofputil_group_desc *gd, struct ofpbuf *msg, @@ -7759,6 +7960,7 @@ ofputil_decode_ofp15_group_desc_reply(struct ofputil_group_desc *gd, { struct ofp15_group_desc_stats *ogds; uint16_t length, bucket_list_len; + int error; if (!msg->frame) { ofpraw_pull_assert(msg); @@ -7790,9 +7992,22 @@ ofputil_decode_ofp15_group_desc_reply(struct ofputil_group_desc *gd, "bucket list length %u", bucket_list_len); return OFPERR_OFPBRC_BAD_LEN; } + error = ofputil_pull_ofp15_buckets(msg, bucket_list_len, version, + &gd->buckets); + if (error) { + return error; + } - return ofputil_pull_ofp15_buckets(msg, bucket_list_len, version, - &gd->buckets); + /* By definition group desc messages don't have a group mod command. + * However, parse_group_prop_nmx_selection_method() checks to make sure + * that the command is OFPGC15_ADD or OFPGC15_DELETE to guard + * against group mod messages with other commands supplying + * a NMX selection method group experimenter property. + * Such properties are valid for group desc replies so + * claim that the group mod command is OFPGC15_ADD to + * satisfy the check in parse_group_prop_nmx_selection_method() */ + return parse_ofp15_group_properties(msg, gd->type, OFPGC15_ADD, &gd->props, + ofpbuf_size(msg)); } /* Converts a group description reply in 'msg' into an abstract @@ -7809,6 +8024,8 @@ int ofputil_decode_group_desc_reply(struct ofputil_group_desc *gd, struct ofpbuf *msg, enum ofp_version version) { + ofputil_init_group_properties(&gd->props); + switch (version) { case OFP11_VERSION: @@ -7826,6 +8043,13 @@ ofputil_decode_group_desc_reply(struct ofputil_group_desc *gd, } } +void +ofputil_uninit_group_mod(struct ofputil_group_mod *gm) +{ + ofputil_bucket_list_destroy(&gm->buckets); + field_array_delete(&gm->props.fields); +} + static struct ofpbuf * ofputil_encode_ofp11_group_mod(enum ofp_version ofp_version, const struct ofputil_group_mod *gm) @@ -8048,14 +8272,14 @@ ofputil_pull_ofp15_group_mod(struct ofpbuf *msg, enum ofp_version ofp_version, } bucket_list_len = ntohs(ogm->bucket_array_len); - if (bucket_list_len < ofpbuf_size(msg)) { - VLOG_WARN_RL(&bad_ofmsg_rl, "group has %u trailing bytes", - ofpbuf_size(msg) - bucket_list_len); - return OFPERR_OFPGMFC_BAD_BUCKET; + error = ofputil_pull_ofp15_buckets(msg, bucket_list_len, ofp_version, + &gm->buckets); + if (error) { + return error; } - return ofputil_pull_ofp15_buckets(msg, bucket_list_len, ofp_version, - &gm->buckets); + return parse_ofp15_group_properties(msg, gm->type, gm->command, &gm->props, + ofpbuf_size(msg)); } /* Converts OpenFlow group mod message 'oh' into an abstract group mod in @@ -8072,6 +8296,8 @@ ofputil_decode_group_mod(const struct ofp_header *oh, ofpbuf_use_const(&msg, oh, ntohs(oh->length)); ofpraw_pull_assert(&msg); + ofputil_init_group_properties(&gm->props); + switch (ofp_version) { case OFP11_VERSION: diff --git a/lib/ofp-util.h b/lib/ofp-util.h index df4d044..31b250b 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -27,6 +27,7 @@ #include "match.h" #include "meta-flow.h" #include "netdev.h" +#include "openflow/netronome-ext.h" #include "openflow/nicira-ext.h" #include "openvswitch/types.h" #include "type-props.h" @@ -217,6 +218,8 @@ void ofputil_match_to_ofp10_match(const struct match *, struct ofp10_match *); /* Work with ofp11_match. */ enum ofperr ofputil_pull_ofp11_match(struct ofpbuf *, struct match *, uint16_t *padded_match_len); +enum ofperr ofputil_pull_ofp11_mask(struct ofpbuf *, struct match *, + struct mf_bitmap *bm); enum ofperr ofputil_match_from_ofp11_match(const struct ofp11_match *, struct match *); int ofputil_put_ofp11_match(struct ofpbuf *, const struct match *, @@ -994,6 +997,14 @@ struct ofputil_bucket { }; /* Protocol-independent group_mod. */ +struct ofputil_group_props { + /* NMX selection method */ + char selection_method[NMX_MAX_SELECTION_METHOD_LEN]; + uint64_t selection_method_param; + struct ovs_list fields; /* A list of struct field_array */ +}; + +/* Protocol-independent group_mod. */ struct ofputil_group_mod { uint16_t command; /* One of OFPGC15_*. */ uint8_t type; /* One of OFPGT11_*. */ @@ -1003,6 +1014,7 @@ struct ofputil_group_mod { * OFPGC15_REMOVE_BUCKET commands * execution.*/ struct ovs_list buckets; /* Contains "struct ofputil_bucket"s. */ + struct ofputil_group_props props; /* Group properties. */ }; /* Group stats reply, independent of protocol. */ @@ -1032,6 +1044,7 @@ struct ofputil_group_desc { uint8_t type; /* One of OFPGT_*. */ uint32_t group_id; /* Group identifier. */ struct ovs_list buckets; /* Contains "struct ofputil_bucket"s. */ + struct ofputil_group_props props; /* Group properties. */ }; void ofputil_bucket_list_destroy(struct ovs_list *buckets); @@ -1062,6 +1075,7 @@ 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 *); +void ofputil_uninit_group_mod(struct ofputil_group_mod *gm); struct ofpbuf *ofputil_encode_group_mod(enum ofp_version ofp_version, const struct ofputil_group_mod *gm); @@ -1071,6 +1085,7 @@ enum ofperr ofputil_decode_group_mod(const struct ofp_header *, int ofputil_decode_group_stats_reply(struct ofpbuf *, struct ofputil_group_stats *); +void ofputil_uninit_group_desc(struct ofputil_group_desc *gd); uint32_t ofputil_decode_group_desc_request(const struct ofp_header *); struct ofpbuf *ofputil_encode_group_desc_request(enum ofp_version, uint32_t group_id); diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index d114390..edf448a 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -499,6 +499,11 @@ struct ofgroup { struct ovs_list buckets; /* Contains "struct ofputil_bucket"s. */ const uint32_t n_buckets; + + /* NMX selection method */ + const char selection_method[NMX_MAX_SELECTION_METHOD_LEN]; + const uint64_t selection_method_param; + const struct ovs_list fields; /* List of other elements in the array */ }; bool ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id, diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index b3909ad..2f4d62f 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -2673,6 +2673,7 @@ ofproto_group_unref(struct ofgroup *group) if (group && ovs_refcount_unref(&group->ref_count) == 1) { group->ofproto->ofproto_class->group_destruct(group); ofputil_bucket_list_destroy(&group->buckets); + field_array_delete(CONST_CAST(struct ovs_list *, &group->fields)); group->ofproto->ofproto_class->group_dealloc(group); } } @@ -5850,6 +5851,14 @@ init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm, *CONST_CAST(uint32_t *, &(*ofgroup)->n_buckets) = list_size(&(*ofgroup)->buckets); + memcpy(CONST_CAST(char *, (*ofgroup)->selection_method), + gm->props.selection_method, NMX_MAX_SELECTION_METHOD_LEN); + *CONST_CAST(uint64_t *, &(*ofgroup)->selection_method_param) = + gm->props.selection_method_param; + list_move(CONST_CAST(struct ovs_list *, &(*ofgroup)->fields), + &gm->props.fields); + list_init(&gm->props.fields); + /* Construct called BEFORE any locks are held. */ error = ofproto->ofproto_class->group_construct(*ofgroup); if (error) { diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index b5ace5a..4c553b2 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -2127,7 +2127,7 @@ ofctl_group_mod_file(int argc OVS_UNUSED, char *argv[], uint16_t command) } ofctl_group_mod__(argv[1], gms, n_gms, usable_protocols); for (i = 0; i < n_gms; i++) { - ofputil_bucket_list_destroy(&gms[i].buckets); + ofputil_uninit_group_mod(gms + i); } free(gms); } @@ -2148,7 +2148,7 @@ ofctl_group_mod(int argc, char *argv[], uint16_t command) ovs_fatal(0, "%s", error); } ofctl_group_mod__(argv[1], &gm, 1, usable_protocols); - ofputil_bucket_list_destroy(&gm.buckets); + ofputil_uninit_group_mod(&gm); } } -- 2.1.4 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev