+/* Flow expiration. */
+
+struct expire_cbdata {
+ struct ofproto *ofproto;
+};
+
+static void ofproto_update_used(struct ofproto *);
+static void rule_expire(struct cls_rule *, void *cbdata);
+
+/* This function is called periodically by ofproto_run(). Its job is to
+ * collect updates for the flows that have been installed into the datapath,
+ * most importantly when they last were used, and then use that information to
+ * expire flows that have not been used recently. */
+static void
+ofproto_expire(struct ofproto *ofproto)
+{
+ struct expire_cbdata cbdata;
+
+ /* Update 'used' for each flow in the datapath. */
+ ofproto_update_used(ofproto);
+
+ /* Expire idle flows. */
+ cbdata.ofproto = ofproto;
+ classifier_for_each(&ofproto->cls, CLS_INC_ALL, rule_expire, &cbdata);
+
+ /* Let the hook know that we're at a stable point: all outstanding data
+ * in existing flows has been accounted to the account_cb. Thus, the
+ * hook can now reasonably do operations that depend on having accurate
+ * flow volume accounting (currently, that's just bond rebalancing). */
+ if (ofproto->ofhooks->account_checkpoint_cb) {
+ ofproto->ofhooks->account_checkpoint_cb(ofproto->aux);
+ }
+}
+
+/* Update 'used' member of each flow currently installed into the datapath. */
+static void
+ofproto_update_used(struct ofproto *p)
+{
+ struct odp_flow *flows;
+ size_t n_flows;
+ size_t i;
+ int error;
+
+ error = dpif_flow_list_all(p->dpif, &flows, &n_flows);
+ if (error) {
+ return;
+ }
+
+ for (i = 0; i < n_flows; i++) {
+ struct odp_flow *f = &flows[i];
+ struct rule *rule;
+
+ rule = rule_from_cls_rule(
+ classifier_find_rule_exactly(&p->cls, &f->key, 0, UINT16_MAX));
+
+ if (rule && rule->installed) {
+ update_time(p, rule, &f->stats);
+ rule_account(p, rule, f->stats.n_bytes);
+ } else {
+ /* There's a flow in the datapath that we know nothing about.
+ * Delete it. */
+ COVERAGE_INC(ofproto_unexpected_rule);
+ dpif_flow_del(p->dpif, f);
+ }
+
+ }
+ free(flows);
+}
+
+static void
+rule_active_timeout(struct ofproto *ofproto, struct rule *rule)
+{
+ if (ofproto->netflow && !is_controller_rule(rule) &&
+ netflow_active_timeout_expired(ofproto->netflow, &rule->nf_flow)) {
+ struct ofexpired expired;
+ struct odp_flow odp_flow;
+
+ /* Get updated flow stats.
+ *
+ * XXX We could avoid this call entirely if (1) ofproto_update_used()
+ * updated TCP flags and (2) the dpif_flow_list_all() in
+ * ofproto_update_used() zeroed TCP flags. */
+ memset(&odp_flow, 0, sizeof odp_flow);
+ if (rule->installed) {
+ odp_flow.key = rule->cr.flow;
+ odp_flow.flags = ODPFF_ZERO_TCP_FLAGS;
+ dpif_flow_get(ofproto->dpif, &odp_flow);
+
+ if (odp_flow.stats.n_packets) {
+ update_time(ofproto, rule, &odp_flow.stats);
+ netflow_flow_update_flags(&rule->nf_flow,
+ odp_flow.stats.tcp_flags);
+ }
+ }
+
+ expired.flow = rule->cr.flow;
+ expired.packet_count = rule->packet_count +
+ odp_flow.stats.n_packets;
+ expired.byte_count = rule->byte_count + odp_flow.stats.n_bytes;
+ expired.used = rule->used;
+
+ netflow_expire(ofproto->netflow, &rule->nf_flow, &expired);
+
+ /* Schedule us to send the accumulated records once we have
+ * collected all of them. */
+ poll_immediate_wake();
+ }
+}
+
+/* If 'cls_rule' is an OpenFlow rule, that has expired according to OpenFlow
+ * rules, then delete it entirely.
+ *
+ * If 'cls_rule' is a subrule, that has not been used recently, remove it from
+ * the datapath and fold its statistics back into its super-rule.
+ *
+ * (This is a callback function for classifier_for_each().) */
+static void
+rule_expire(struct cls_rule *cls_rule, void *cbdata_)
+{
+ struct expire_cbdata *cbdata = cbdata_;
+ struct ofproto *ofproto = cbdata->ofproto;
+ struct rule *rule = rule_from_cls_rule(cls_rule);
+ long long int hard_expire, idle_expire, expire, now;
+
+ /* Calculate OpenFlow expiration times for 'rule'. */
+ hard_expire = (rule->hard_timeout
+ ? rule->created + rule->hard_timeout * 1000
+ : LLONG_MAX);
+ idle_expire = (rule->idle_timeout
+ && (rule->super || list_is_empty(&rule->list))
+ ? rule->used + rule->idle_timeout * 1000
+ : LLONG_MAX);
+ expire = MIN(hard_expire, idle_expire);
+
+ now = time_msec();
+ if (now < expire) {
+ /* 'rule' has not expired according to OpenFlow rules. */
+ if (rule->installed && now >= rule->used + 5000) {
+ /* This rule is idle, so uninstall it from the datapath. */
+ if (rule->super) {
+ rule_remove(ofproto, rule);
+ } else {
+ rule_uninstall(ofproto, rule);
+ }
+ } else if (!rule->cr.wc.wildcards) {
+ /* Send NetFlow active timeout if appropriate. */
+ rule_active_timeout(cbdata->ofproto, rule);
+ }
+ } else {
+ /* 'rule' has expired according to OpenFlow rules. */
+ COVERAGE_INC(ofproto_expired);
+
+ /* Update stats. (This is a no-op if the rule expired due to an idle
+ * timeout, because that only happens when the rule has no subrules
+ * left.) */
+ if (rule->cr.wc.wildcards) {
+ struct rule *subrule, *next;
+ LIST_FOR_EACH_SAFE (subrule, next, struct rule, list,
+ &rule->list) {
+ rule_remove(cbdata->ofproto, subrule);
+ }
+ } else {
+ rule_uninstall(cbdata->ofproto, rule);
+ }
+
+ /* Get rid of the rule. */
+ if (!rule_is_hidden(rule)) {
+ send_flow_removed(cbdata->ofproto, rule, now,
+ (now >= hard_expire
+ ? OFPRR_HARD_TIMEOUT : OFPRR_IDLE_TIMEOUT));
+ }
+ rule_remove(cbdata->ofproto, rule);
+ }
+}
+\f