+/* Meters implementation.
+ *
+ * Meter table entry, indexed by the OpenFlow meter_id.
+ * These are always dynamically allocated to allocate enough space for
+ * the bands.
+ * 'created' is used to compute the duration for meter stats.
+ * 'list rules' is needed so that we can delete the dependent rules when the
+ * meter table entry is deleted.
+ * 'provider_meter_id' is for the provider's private use.
+ */
+struct meter {
+ long long int created; /* Time created. */
+ struct list rules; /* List of "struct rule_dpif"s. */
+ ofproto_meter_id provider_meter_id;
+ uint16_t flags; /* Meter flags. */
+ uint16_t n_bands; /* Number of meter bands. */
+ struct ofputil_meter_band *bands;
+};
+
+/*
+ * This is used in instruction validation at flow set-up time,
+ * as flows may not use non-existing meters.
+ * This is also used by ofproto-providers to translate OpenFlow meter_ids
+ * in METER instructions to the corresponding provider meter IDs.
+ * Return value of UINT32_MAX signifies an invalid meter.
+ */
+uint32_t
+ofproto_get_provider_meter_id(const struct ofproto * ofproto,
+ uint32_t of_meter_id)
+{
+ if (of_meter_id && of_meter_id <= ofproto->meter_features.max_meters) {
+ const struct meter *meter = ofproto->meters[of_meter_id];
+ if (meter) {
+ return meter->provider_meter_id.uint32;
+ }
+ }
+ return UINT32_MAX;
+}
+
+static void
+meter_update(struct meter *meter, const struct ofputil_meter_config *config)
+{
+ free(meter->bands);
+
+ meter->flags = config->flags;
+ meter->n_bands = config->n_bands;
+ meter->bands = xmemdup(config->bands,
+ config->n_bands * sizeof *meter->bands);
+}
+
+static struct meter *
+meter_create(const struct ofputil_meter_config *config,
+ ofproto_meter_id provider_meter_id)
+{
+ struct meter *meter;
+
+ meter = xzalloc(sizeof *meter);
+ meter->provider_meter_id = provider_meter_id;
+ meter->created = time_msec();
+ list_init(&meter->rules);
+
+ meter_update(meter, config);
+
+ return meter;
+}
+
+static void
+meter_delete(struct ofproto *ofproto, uint32_t first, uint32_t last)
+{
+ uint32_t mid;
+ for (mid = first; mid <= last; ++mid) {
+ struct meter *meter = ofproto->meters[mid];
+ if (meter) {
+ ofproto->meters[mid] = NULL;
+ ofproto->ofproto_class->meter_del(ofproto,
+ meter->provider_meter_id);
+ free(meter->bands);
+ free(meter);
+ }
+ }
+}
+
+static enum ofperr
+handle_add_meter(struct ofproto *ofproto, struct ofputil_meter_mod *mm)
+{
+ ofproto_meter_id provider_meter_id = { UINT32_MAX };
+ struct meter **meterp = &ofproto->meters[mm->meter.meter_id];
+ enum ofperr error;
+
+ if (*meterp) {
+ return OFPERR_OFPMMFC_METER_EXISTS;
+ }
+
+ error = ofproto->ofproto_class->meter_set(ofproto, &provider_meter_id,
+ &mm->meter);
+ if (!error) {
+ ovs_assert(provider_meter_id.uint32 != UINT32_MAX);
+ *meterp = meter_create(&mm->meter, provider_meter_id);
+ }
+ return 0;
+}
+
+static enum ofperr
+handle_modify_meter(struct ofproto *ofproto, struct ofputil_meter_mod *mm)
+{
+ struct meter *meter = ofproto->meters[mm->meter.meter_id];
+ enum ofperr error;
+
+ if (!meter) {
+ return OFPERR_OFPMMFC_UNKNOWN_METER;
+ }
+
+ error = ofproto->ofproto_class->meter_set(ofproto,
+ &meter->provider_meter_id,
+ &mm->meter);
+ ovs_assert(meter->provider_meter_id.uint32 != UINT32_MAX);
+ if (!error) {
+ meter_update(meter, &mm->meter);
+ }
+ return error;
+}
+
+static enum ofperr
+handle_delete_meter(struct ofconn *ofconn, const struct ofp_header *oh,
+ struct ofputil_meter_mod *mm)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ uint32_t meter_id = mm->meter.meter_id;
+ struct rule_collection rules;
+ enum ofperr error = 0;
+ uint32_t first, last;
+
+ if (meter_id == OFPM13_ALL) {
+ first = 1;
+ last = ofproto->meter_features.max_meters;
+ } else {
+ if (!meter_id || meter_id > ofproto->meter_features.max_meters) {
+ return 0;
+ }
+ first = last = meter_id;
+ }
+
+ /* First delete the rules that use this meter. If any of those rules are
+ * currently being modified, postpone the whole operation until later. */
+ rule_collection_init(&rules);
+ for (meter_id = first; meter_id <= last; ++meter_id) {
+ struct meter *meter = ofproto->meters[meter_id];
+ if (meter && !list_is_empty(&meter->rules)) {
+ struct rule *rule;
+
+ LIST_FOR_EACH (rule, meter_list_node, &meter->rules) {
+ if (rule->pending) {
+ error = OFPROTO_POSTPONE;
+ goto exit;
+ }
+ rule_collection_add(&rules, rule);
+ }
+ }
+ }
+ if (rules.n > 0) {
+ delete_flows__(ofproto, ofconn, oh, &rules, OFPRR_METER_DELETE);
+ }
+
+ /* Delete the meters. */
+ meter_delete(ofproto, first, last);
+
+exit:
+ rule_collection_destroy(&rules);
+
+ return error;
+}
+
+static enum ofperr
+handle_meter_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ofputil_meter_mod mm;
+ uint64_t bands_stub[256 / 8];
+ struct ofpbuf bands;
+ uint32_t meter_id;
+ enum ofperr error;
+
+ error = reject_slave_controller(ofconn);
+ if (error) {
+ return error;
+ }
+
+ ofpbuf_use_stub(&bands, bands_stub, sizeof bands_stub);
+
+ error = ofputil_decode_meter_mod(oh, &mm, &bands);
+ if (error) {
+ goto exit_free_bands;
+ }
+
+ meter_id = mm.meter.meter_id;
+
+ if (mm.command != OFPMC13_DELETE) {
+ /* Fails also when meters are not implemented by the provider. */
+ if (meter_id == 0 || meter_id > OFPM13_MAX) {
+ error = OFPERR_OFPMMFC_INVALID_METER;
+ goto exit_free_bands;
+ } else if (meter_id > ofproto->meter_features.max_meters) {
+ error = OFPERR_OFPMMFC_OUT_OF_METERS;
+ goto exit_free_bands;
+ }
+ if (mm.meter.n_bands > ofproto->meter_features.max_bands) {
+ error = OFPERR_OFPMMFC_OUT_OF_BANDS;
+ goto exit_free_bands;
+ }
+ }
+
+ switch (mm.command) {
+ case OFPMC13_ADD:
+ error = handle_add_meter(ofproto, &mm);
+ break;
+
+ case OFPMC13_MODIFY:
+ error = handle_modify_meter(ofproto, &mm);
+ break;
+
+ case OFPMC13_DELETE:
+ error = handle_delete_meter(ofconn, oh, &mm);
+ break;
+
+ default:
+ error = OFPERR_OFPMMFC_BAD_COMMAND;
+ break;
+ }
+
+exit_free_bands:
+ ofpbuf_uninit(&bands);
+ return error;
+}
+
+static enum ofperr
+handle_meter_features_request(struct ofconn *ofconn,
+ const struct ofp_header *request)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ofputil_meter_features features;
+ struct ofpbuf *b;
+
+ if (ofproto->ofproto_class->meter_get_features) {
+ ofproto->ofproto_class->meter_get_features(ofproto, &features);
+ } else {
+ memset(&features, 0, sizeof features);
+ }
+ b = ofputil_encode_meter_features_reply(&features, request);
+
+ ofconn_send_reply(ofconn, b);
+ return 0;
+}
+
+static enum ofperr
+handle_meter_request(struct ofconn *ofconn, const struct ofp_header *request,
+ enum ofptype type)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct list replies;
+ uint64_t bands_stub[256 / 8];
+ struct ofpbuf bands;
+ uint32_t meter_id, first, last;
+
+ ofputil_decode_meter_request(request, &meter_id);
+
+ if (meter_id == OFPM13_ALL) {
+ first = 1;
+ last = ofproto->meter_features.max_meters;
+ } else {
+ if (!meter_id || meter_id > ofproto->meter_features.max_meters ||
+ !ofproto->meters[meter_id]) {
+ return OFPERR_OFPMMFC_UNKNOWN_METER;
+ }
+ first = last = meter_id;
+ }
+
+ ofpbuf_use_stub(&bands, bands_stub, sizeof bands_stub);
+ ofpmp_init(&replies, request);
+
+ for (meter_id = first; meter_id <= last; ++meter_id) {
+ struct meter *meter = ofproto->meters[meter_id];
+ if (!meter) {
+ continue; /* Skip non-existing meters. */
+ }
+ if (type == OFPTYPE_METER_STATS_REQUEST) {
+ struct ofputil_meter_stats stats;
+
+ stats.meter_id = meter_id;
+
+ /* Provider sets the packet and byte counts, we do the rest. */
+ stats.flow_count = list_size(&meter->rules);
+ calc_duration(meter->created, time_msec(),
+ &stats.duration_sec, &stats.duration_nsec);
+ stats.n_bands = meter->n_bands;
+ ofpbuf_clear(&bands);
+ stats.bands
+ = ofpbuf_put_uninit(&bands,
+ meter->n_bands * sizeof *stats.bands);
+
+ if (!ofproto->ofproto_class->meter_get(ofproto,
+ meter->provider_meter_id,
+ &stats)) {
+ ofputil_append_meter_stats(&replies, &stats);
+ }
+ } else { /* type == OFPTYPE_METER_CONFIG_REQUEST */
+ struct ofputil_meter_config config;
+
+ config.meter_id = meter_id;
+ config.flags = meter->flags;
+ config.n_bands = meter->n_bands;
+ config.bands = meter->bands;
+ ofputil_append_meter_config(&replies, &config);
+ }
+ }
+
+ ofconn_send_replies(ofconn, &replies);
+ ofpbuf_uninit(&bands);
+ return 0;
+}
+