#include "ofproto-provider.h"
#include "openflow/nicira-ext.h"
#include "openflow/openflow.h"
+#include "ovs-rcu.h"
#include "packets.h"
#include "pinsched.h"
#include "pktbuf.h"
};
/* rule. */
-static void ofproto_rule_destroy__(struct rule *);
static void ofproto_rule_send_removed(struct rule *, uint8_t reason);
-static bool rule_is_modifiable(const struct rule *);
+static bool rule_is_modifiable(const struct rule *rule,
+ enum ofputil_flow_mod_flags flag);
/* OpenFlow. */
static enum ofperr add_flow(struct ofproto *, struct ofconn *,
struct ofputil_flow_mod *,
const struct ofp_header *);
+static void do_add_flow(struct ofproto *, struct ofconn *,
+ const struct ofp_header *request, uint32_t buffer_id,
+ struct rule *);
static enum ofperr modify_flows__(struct ofproto *, struct ofconn *,
struct ofputil_flow_mod *,
const struct ofp_header *,
}
}
-/* Populates 'status' with key value pairs indicating the status of the BFD
- * session on 'ofp_port'. This information is intended to be populated in the
- * OVS database. Has no effect if 'ofp_port' is not na OpenFlow port in
- * 'ofproto'. */
+/* Populates 'status' with the status of BFD on 'ofport'. Returns 0 on
+ * success. Returns a negative number if there is no status change since
+ * last update. Returns a positive errno otherwise. Has no effect if
+ * 'ofp_port' is not an OpenFlow port in 'ofproto'.
+ *
+ * The caller must provide and own '*status'. */
int
ofproto_port_get_bfd_status(struct ofproto *ofproto, ofp_port_t ofp_port,
struct smap *status)
return ofproto->n_tables;
}
+/* Returns the number of Controller visible OpenFlow tables
+ * in 'ofproto'. This number will exclude Hidden tables.
+ * This funtion's return value should be less or equal to that of
+ * ofproto_get_n_tables() . */
+uint8_t
+ofproto_get_n_visible_tables(const struct ofproto *ofproto)
+{
+ uint8_t n = ofproto->n_tables;
+
+ /* Count only non-hidden tables in the number of tables. (Hidden tables,
+ * if present, are always at the end.) */
+ while(n && (ofproto->tables[n - 1].flags & OFTABLE_HIDDEN)) {
+ n--;
+ }
+
+ return n;
+}
+
/* Configures the OpenFlow table in 'ofproto' with id 'table_id' with the
* settings from 's'. 'table_id' must be in the range 0 through the number of
* OpenFlow tables in 'ofproto' minus 1, inclusive.
ovs_assert(list_is_empty(&ofproto->pending));
destroy_rule_executes(ofproto);
- guarded_list_destroy(&ofproto->rule_executes);
-
delete_group(ofproto, OFPG_ALL);
+
+ guarded_list_destroy(&ofproto->rule_executes);
ovs_rwlock_destroy(&ofproto->groups_rwlock);
hmap_destroy(&ofproto->groups);
}
p->ofproto_class->destruct(p);
- ofproto_destroy__(p);
+ /* Destroying rules is deferred, must have 'ofproto' around for them. */
+ ovsrcu_postpone(ofproto_destroy__, p);
}
/* Destroys the datapath with the respective 'name' and 'type'. With the Linux
* need this two-phase approach. */
sset_init(&devnames);
HMAP_FOR_EACH (ofport, hmap_node, &p->ports) {
- sset_add(&devnames, netdev_get_name(ofport->netdev));
+ uint64_t port_change_seq;
+
+ port_change_seq = netdev_get_change_seq(ofport->netdev);
+ if (ofport->change_seq != port_change_seq) {
+ ofport->change_seq = port_change_seq;
+ sset_add(&devnames, netdev_get_name(ofport->netdev));
+ }
}
SSET_FOR_EACH (devname, &devnames) {
update_port(p, devname);
rule = rule_from_cls_rule(classifier_find_match_exactly(
&ofproto->tables[0].cls, match, priority));
if (rule) {
- ovs_mutex_lock(&rule->mutex);
- must_add = !ofpacts_equal(rule->actions->ofpacts,
- rule->actions->ofpacts_len,
+ struct rule_actions *actions = rule_get_actions(rule);
+ must_add = !ofpacts_equal(actions->ofpacts, actions->ofpacts_len,
ofpacts, ofpacts_len);
- ovs_mutex_unlock(&rule->mutex);
} else {
must_add = true;
}
/* Reading many of the rule fields and writing on 'modified'
* requires the rule->mutex. Also, rule->actions may change
* if rule->mutex is not held. */
+ const struct rule_actions *actions;
+
ovs_mutex_lock(&rule->mutex);
+ actions = rule_get_actions(rule);
if (rule->idle_timeout == fm->idle_timeout
&& rule->hard_timeout == fm->hard_timeout
&& rule->flags == (fm->flags & OFPUTIL_FF_STATE)
&& (!fm->modify_cookie || (fm->new_cookie == rule->flow_cookie))
&& ofpacts_equal(fm->ofpacts, fm->ofpacts_len,
- rule->actions->ofpacts,
- rule->actions->ofpacts_len)) {
+ actions->ofpacts, actions->ofpacts_len)) {
/* Rule already exists and need not change, only update the
modified timestamp. */
rule->modified = time_msec();
return handle_flow_mod__(ofproto, NULL, fm, NULL);
}
+/* Resets the modified time for 'rule' or an equivalent rule. If 'rule' is not
+ * in the classifier, but an equivalent rule is, unref 'rule' and ref the new
+ * rule. Otherwise if 'rule' is no longer installed in the classifier,
+ * reinstall it.
+ *
+ * Returns the rule whose modified time has been reset. */
+struct rule *
+ofproto_refresh_rule(struct rule *rule)
+{
+ const struct oftable *table = &rule->ofproto->tables[rule->table_id];
+ const struct cls_rule *cr = &rule->cr;
+ struct rule *r;
+
+ /* do_add_flow() requires that the rule is not installed. We lock the
+ * ofproto_mutex here so that another thread cannot add the flow before
+ * we get a chance to add it.*/
+ ovs_mutex_lock(&ofproto_mutex);
+
+ fat_rwlock_rdlock(&table->cls.rwlock);
+ r = rule_from_cls_rule(classifier_find_rule_exactly(&table->cls, cr));
+ if (r != rule) {
+ ofproto_rule_ref(r);
+ }
+ fat_rwlock_unlock(&table->cls.rwlock);
+
+ if (!r) {
+ do_add_flow(rule->ofproto, NULL, NULL, 0, rule);
+ } else if (r != rule) {
+ ofproto_rule_unref(rule);
+ rule = r;
+ }
+ ovs_mutex_unlock(&ofproto_mutex);
+
+ /* Refresh the modified time for the rule. */
+ ovs_mutex_lock(&rule->mutex);
+ rule->modified = MAX(rule->modified, time_msec());
+ ovs_mutex_unlock(&rule->mutex);
+
+ return rule;
+}
+
/* Searches for a rule with matching criteria exactly equal to 'target' in
* ofproto's table 0 and, if it finds one, deletes it.
*
}
ofport->ofproto = p;
ofport->netdev = netdev;
+ ofport->change_seq = netdev_get_change_seq(netdev);
ofport->pp = *pp;
ofport->ofp_port = pp->port_no;
ofport->created = time_msec();
* Don't close the old netdev yet in case port_modified has to
* remove a retained reference to it.*/
port->netdev = netdev;
+ port->change_seq = netdev_get_change_seq(netdev);
if (port->ofproto->ofproto_class->port_modified) {
port->ofproto->ofproto_class->port_modified(port);
}
}
\f
+static void
+ofproto_rule_destroy__(struct rule *rule)
+ OVS_NO_THREAD_SAFETY_ANALYSIS
+{
+ cls_rule_destroy(CONST_CAST(struct cls_rule *, &rule->cr));
+ rule_actions_destroy(rule_get_actions(rule));
+ ovs_mutex_destroy(&rule->mutex);
+ rule->ofproto->ofproto_class->rule_dealloc(rule);
+}
+
+static void
+rule_destroy_cb(struct rule *rule)
+{
+ rule->ofproto->ofproto_class->rule_destruct(rule);
+ ofproto_rule_destroy__(rule);
+}
+
void
ofproto_rule_ref(struct rule *rule)
{
}
}
+/* Decrements 'rule''s ref_count and schedules 'rule' to be destroyed if the
+ * ref_count reaches 0.
+ *
+ * Use of RCU allows short term use (between RCU quiescent periods) without
+ * keeping a reference. A reference must be taken if the rule needs to
+ * stay around accross the RCU quiescent periods. */
void
ofproto_rule_unref(struct rule *rule)
{
if (rule && ovs_refcount_unref(&rule->ref_count) == 1) {
- rule->ofproto->ofproto_class->rule_destruct(rule);
- ofproto_rule_destroy__(rule);
+ ovsrcu_postpone(rule_destroy_cb, rule);
}
}
-struct rule_actions *
-rule_get_actions(const struct rule *rule)
- OVS_EXCLUDED(rule->mutex)
-{
- struct rule_actions *actions;
-
- ovs_mutex_lock(&rule->mutex);
- actions = rule_get_actions__(rule);
- ovs_mutex_unlock(&rule->mutex);
-
- return actions;
-}
-
-struct rule_actions *
-rule_get_actions__(const struct rule *rule)
- OVS_REQUIRES(rule->mutex)
-{
- rule_actions_ref(rule->actions);
- return rule->actions;
-}
-
-static void
-ofproto_rule_destroy__(struct rule *rule)
- OVS_NO_THREAD_SAFETY_ANALYSIS
-{
- cls_rule_destroy(CONST_CAST(struct cls_rule *, &rule->cr));
- rule_actions_unref(rule->actions);
- ovs_mutex_destroy(&rule->mutex);
- ovs_refcount_destroy(&rule->ref_count);
- rule->ofproto->ofproto_class->rule_dealloc(rule);
-}
-
static uint32_t get_provider_meter_id(const struct ofproto *,
uint32_t of_meter_id);
struct rule_actions *actions;
actions = xmalloc(sizeof *actions);
- ovs_refcount_init(&actions->ref_count);
actions->ofpacts = xmemdup(ofpacts, ofpacts_len);
actions->ofpacts_len = ofpacts_len;
actions->provider_meter_id
return actions;
}
-/* Increments 'actions''s ref_count. */
-void
-rule_actions_ref(struct rule_actions *actions)
+static void
+rule_actions_destroy_cb(struct rule_actions *actions)
{
- if (actions) {
- ovs_refcount_ref(&actions->ref_count);
- }
+ free(actions->ofpacts);
+ free(actions);
}
/* Decrements 'actions''s ref_count and frees 'actions' if the ref_count
* reaches 0. */
void
-rule_actions_unref(struct rule_actions *actions)
+rule_actions_destroy(struct rule_actions *actions)
{
- if (actions && ovs_refcount_unref(&actions->ref_count) == 1) {
- ovs_refcount_destroy(&actions->ref_count);
- free(actions->ofpacts);
- free(actions);
+ if (actions) {
+ ovsrcu_postpone(rule_actions_destroy_cb, actions);
}
}
ofproto_rule_has_out_port(const struct rule *rule, ofp_port_t port)
OVS_REQUIRES(ofproto_mutex)
{
- return (port == OFPP_ANY
- || ofpacts_output_to_port(rule->actions->ofpacts,
- rule->actions->ofpacts_len, port));
+ if (port == OFPP_ANY) {
+ return true;
+ } else {
+ const struct rule_actions *actions = rule_get_actions(rule);
+ return ofpacts_output_to_port(actions->ofpacts,
+ actions->ofpacts_len, port);
+ }
}
/* Returns true if 'rule' has group and equals group_id. */
ofproto_rule_has_out_group(const struct rule *rule, uint32_t group_id)
OVS_REQUIRES(ofproto_mutex)
{
- return (group_id == OFPG11_ANY
- || ofpacts_output_to_group(rule->actions->ofpacts,
- rule->actions->ofpacts_len, group_id));
+ if (group_id == OFPG_ANY) {
+ return true;
+ } else {
+ const struct rule_actions *actions = rule_get_actions(rule);
+ return ofpacts_output_to_group(actions->ofpacts,
+ actions->ofpacts_len, group_id);
+ }
}
/* Returns true if a rule related to 'op' has an OpenFlow OFPAT_OUTPUT or
static bool
ofproto_rule_is_hidden(const struct rule *rule)
{
- return rule->cr.priority > UINT16_MAX;
+ return (rule->cr.priority > UINT16_MAX);
}
-static enum oftable_flags
-rule_get_flags(const struct rule *rule)
+static bool
+oftable_is_modifiable(const struct oftable *table,
+ enum ofputil_flow_mod_flags flags)
{
- return rule->ofproto->tables[rule->table_id].flags;
+ if (flags & OFPUTIL_FF_NO_READONLY) {
+ return true;
+ }
+
+ return !(table->flags & OFTABLE_READONLY);
}
static bool
-rule_is_modifiable(const struct rule *rule)
+rule_is_modifiable(const struct rule *rule, enum ofputil_flow_mod_flags flags)
{
- return !(rule_get_flags(rule) & OFTABLE_READONLY);
+ const struct oftable *rule_table;
+
+ rule_table = &rule->ofproto->tables[rule->table_id];
+ return oftable_is_modifiable(rule_table, flags);
}
\f
static enum ofperr
struct ofport *port;
bool arp_match_ip;
struct ofpbuf *b;
- int n_tables;
- int i;
ofproto->ofproto_class->get_features(ofproto, &arp_match_ip,
&features.actions);
ovs_assert(features.actions & OFPUTIL_A_OUTPUT); /* sanity check */
- /* Count only non-hidden tables in the number of tables. (Hidden tables,
- * if present, are always at the end.) */
- n_tables = ofproto->n_tables;
- for (i = 0; i < ofproto->n_tables; i++) {
- if (ofproto->tables[i].flags & OFTABLE_HIDDEN) {
- n_tables = i;
- break;
- }
- }
-
features.datapath_id = ofproto->datapath_id;
features.n_buffers = pktbuf_capacity();
- features.n_tables = n_tables;
+ features.n_tables = ofproto_get_n_visible_tables(ofproto);
features.capabilities = (OFPUTIL_C_FLOW_STATS | OFPUTIL_C_TABLE_STATS |
OFPUTIL_C_PORT_STATS | OFPUTIL_C_QUEUE_STATS);
if (arp_match_ip) {
static uint32_t
hash_cookie(ovs_be64 cookie)
{
- return hash_2words((OVS_FORCE uint64_t)cookie >> 32,
- (OVS_FORCE uint64_t)cookie);
+ return hash_uint64((OVS_FORCE uint64_t)cookie);
}
static void
fs.hard_timeout = rule->hard_timeout;
created = rule->created;
modified = rule->modified;
- actions = rule_get_actions__(rule);
+ actions = rule_get_actions(rule);
flags = rule->flags;
ovs_mutex_unlock(&rule->mutex);
fs.flags = flags;
ofputil_append_flow_stats_reply(&fs, &replies);
-
- rule_actions_unref(actions);
}
rule_collection_unref(&rules);
&byte_count, &used);
ovs_mutex_lock(&rule->mutex);
- actions = rule_get_actions__(rule);
+ actions = rule_get_actions(rule);
created = rule->created;
ovs_mutex_unlock(&rule->mutex);
ofpacts_format(actions->ofpacts, actions->ofpacts_len, results);
ds_put_cstr(results, "\n");
-
- rule_actions_unref(actions);
}
/* Adds a pretty-printed description of all flows to 'results', including
ofproto->ofproto_class->get_netflow_ids(ofproto, engine_type, engine_id);
}
-/* Checks the status of CFM configured on 'ofp_port' within 'ofproto'. Returns
- * true if the port's CFM status was successfully stored into '*status'.
- * Returns false if the port did not have CFM configured, in which case
- * '*status' is indeterminate.
+/* Checks the status of CFM configured on 'ofp_port' within 'ofproto'.
+ * Returns 0 if the port's CFM status was successfully stored into
+ * '*status'. Returns positive errno if the port did not have CFM
+ * configured. Returns negative number if there is no status change
+ * since last update.
*
- * The caller must provide and owns '*status', and must free 'status->rmps'. */
-bool
+ * The caller must provide and own '*status', and must free 'status->rmps'.
+ * '*status' is indeterminate if the return value is non-zero. */
+int
ofproto_port_get_cfm_status(const struct ofproto *ofproto, ofp_port_t ofp_port,
struct ofproto_cfm_status *status)
{
struct ofport *ofport = ofproto_get_port(ofproto, ofp_port);
- return (ofport
- && ofproto->ofproto_class->get_cfm_status
- && ofproto->ofproto_class->get_cfm_status(ofport, status));
+ return (ofport && ofproto->ofproto_class->get_cfm_status
+ ? ofproto->ofproto_class->get_cfm_status(ofport, status)
+ : EOPNOTSUPP);
}
static enum ofperr
OVS_REQUIRES(ofproto_mutex)
{
struct oftable *table;
- struct ofopgroup *group;
struct cls_rule cr;
struct rule *rule;
uint8_t table_id;
table = &ofproto->tables[table_id];
- if (table->flags & OFTABLE_READONLY) {
+ if (!oftable_is_modifiable(table, fm->flags)) {
return OFPERR_OFPBRC_EPERM;
}
+ if (!(fm->flags & OFPUTIL_FF_HIDDEN_FIELDS)) {
+ if (!match_has_default_hidden_fields(&fm->match)) {
+ VLOG_WARN_RL(&rl, "%s: (add_flow) only internal flows can set "
+ "non-default values to hidden fields", ofproto->name);
+ return OFPERR_OFPBRC_EPERM;
+ }
+ }
+
cls_rule_init(&cr, &fm->match, fm->priority);
/* Transform "add" into "modify" if there's an existing identical flow. */
fat_rwlock_unlock(&table->cls.rwlock);
if (rule) {
cls_rule_destroy(&cr);
- if (!rule_is_modifiable(rule)) {
+ if (!rule_is_modifiable(rule, fm->flags)) {
return OFPERR_OFPBRC_EPERM;
} else if (rule->pending) {
return OFPROTO_POSTPONE;
*CONST_CAST(uint8_t *, &rule->table_id) = table - ofproto->tables;
rule->flags = fm->flags & OFPUTIL_FF_STATE;
- rule->actions = rule_actions_create(ofproto, fm->ofpacts, fm->ofpacts_len);
+ ovsrcu_set(&rule->actions,
+ rule_actions_create(ofproto, fm->ofpacts, fm->ofpacts_len));
list_init(&rule->meter_list_node);
rule->eviction_group = NULL;
list_init(&rule->expirable);
}
/* Insert rule. */
+ do_add_flow(ofproto, ofconn, request, fm->buffer_id, rule);
+
+ return error;
+}
+
+static void
+do_add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
+ const struct ofp_header *request, uint32_t buffer_id,
+ struct rule *rule)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ struct ofopgroup *group;
+
oftable_insert_rule(rule);
- group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
+ group = ofopgroup_create(ofproto, ofconn, request, buffer_id);
ofoperation_create(group, rule, OFOPERATION_ADD, 0);
ofproto->ofproto_class->rule_insert(rule);
ofopgroup_submit(group);
-
- return error;
}
\f
/* OFPFC_MODIFY and OFPFC_MODIFY_STRICT. */
error = OFPERR_OFPBRC_EPERM;
for (i = 0; i < rules->n; i++) {
struct rule *rule = rules->rules[i];
+ const struct rule_actions *actions;
struct ofoperation *op;
bool actions_changed;
bool reset_counters;
/* FIXME: Implement OFPFUTIL_FF_RESET_COUNTS */
- if (rule_is_modifiable(rule)) {
+ if (rule_is_modifiable(rule, fm->flags)) {
/* At least one rule is modifiable, don't report EPERM error. */
error = 0;
} else {
continue;
}
+ actions = rule_get_actions(rule);
actions_changed = !ofpacts_equal(fm->ofpacts, fm->ofpacts_len,
- rule->actions->ofpacts,
- rule->actions->ofpacts_len);
+ actions->ofpacts,
+ actions->ofpacts_len);
op = ofoperation_create(group, rule, type, 0);
if (actions_changed || reset_counters) {
struct rule_actions *new_actions;
- op->actions = rule->actions;
+ op->actions = rule_get_actions(rule);
new_actions = rule_actions_create(ofproto,
fm->ofpacts, fm->ofpacts_len);
- ovs_mutex_lock(&rule->mutex);
- rule->actions = new_actions;
- ovs_mutex_unlock(&rule->mutex);
+ ovsrcu_set(&rule->actions, new_actions);
rule->ofproto->ofproto_class->rule_modify_actions(rule,
reset_counters);
if (!(flags & NXFMF_ACTIONS)) {
actions = NULL;
} else if (!op) {
- actions = rule->actions;
+ actions = rule_get_actions(rule);
} else {
/* An operation is in progress. Use the previous version of the flow's
* actions, so that when the operation commits we report the change. */
case OFOPERATION_MODIFY:
case OFOPERATION_REPLACE:
- actions = op->actions ? op->actions : rule->actions;
+ actions = op->actions ? op->actions : rule_get_actions(rule);
break;
case OFOPERATION_DELETE:
- actions = rule->actions;
+ actions = rule_get_actions(rule);
break;
default:
}
}
+enum ofproto_table_config
+ofproto_table_get_config(const struct ofproto *ofproto, uint8_t table_id)
+{
+ unsigned int value;
+ atomic_read(&ofproto->tables[table_id].config, &value);
+ return (enum ofproto_table_config)value;
+}
+
static enum ofperr
table_mod(struct ofproto *ofproto, const struct ofputil_table_mod *tm)
{
- /* XXX Reject all configurations because none are currently supported */
- return OFPERR_OFPTMFC_BAD_CONFIG;
+ /* Only accept currently supported configurations */
+ if (tm->config & ~OFPTC11_TABLE_MISS_MASK) {
+ return OFPERR_OFPTMFC_BAD_CONFIG;
+ }
if (tm->table_id == OFPTT_ALL) {
int i;
handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
OVS_EXCLUDED(ofproto_mutex)
{
- const struct ofp_header *oh = msg->data;
+ const struct ofp_header *oh = ofpbuf_data(msg);
enum ofptype type;
enum ofperr error;
{
int error = handle_openflow__(ofconn, ofp_msg);
if (error && error != OFPROTO_POSTPONE) {
- ofconn_send_error(ofconn, ofp_msg->data, error);
+ ofconn_send_error(ofconn, ofpbuf_data(ofp_msg), error);
}
COVERAGE_INC(ofproto_recv_openflow);
return error != OFPROTO_POSTPONE;
struct rule_actions *old_actions;
ovs_mutex_lock(&rule->mutex);
- old_actions = rule->actions;
- rule->actions = op->actions;
+ old_actions = rule_get_actions(rule);
+ ovsrcu_set(&rule->actions, op->actions);
ovs_mutex_unlock(&rule->mutex);
op->actions = NULL;
- rule_actions_unref(old_actions);
+ rule_actions_destroy(old_actions);
}
rule->flags = op->flags;
}
hmap_remove(&group->ofproto->deletions, &op->hmap_node);
}
list_remove(&op->group_node);
- rule_actions_unref(op->actions);
+ rule_actions_destroy(op->actions);
free(op);
}
memset(table, 0, sizeof *table);
classifier_init(&table->cls, flow_segment_u32s);
table->max_flows = UINT_MAX;
- atomic_init(&table->config, (unsigned int)OFPTC11_TABLE_MISS_CONTROLLER);
+ atomic_init(&table->config, (unsigned int)OFPROTO_TABLE_MISS_DEFAULT);
}
/* Destroys 'table', including its classifier and eviction groups.
oftable_disable_eviction(table);
classifier_destroy(&table->cls);
free(table->name);
- atomic_destroy(&table->config);
}
/* Changes the name of 'table' to 'name'. If 'name' is NULL or the empty
{
struct ofproto *ofproto = rule->ofproto;
struct oftable *table = &ofproto->tables[rule->table_id];
+ struct rule_actions *actions;
bool may_expire;
ovs_mutex_lock(&rule->mutex);
cookies_insert(ofproto, rule);
- if (rule->actions->provider_meter_id != UINT32_MAX) {
- uint32_t meter_id = ofpacts_get_meter(rule->actions->ofpacts,
- rule->actions->ofpacts_len);
+ actions = rule_get_actions(rule);
+ if (actions->provider_meter_id != UINT32_MAX) {
+ uint32_t meter_id = ofpacts_get_meter(actions->ofpacts,
+ actions->ofpacts_len);
struct meter *meter = ofproto->meters[meter_id];
list_insert(&meter->rules, &rule->meter_list_node);
}