X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=ofproto%2Fofproto.c;h=23ebae3bfb56356805b147d937b811bad0180e25;hb=c5cd8b2ef14fa258f57ac5046ca3e85d97cd7650;hp=3d2989a624a4e067ae67222ab9b1d8e480ac1818;hpb=eedc0097f475a15297375a2aba39313c0f98f330;p=openvswitch diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index 3d2989a6..23ebae3b 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -324,11 +324,10 @@ static const struct ofhooks default_ofhooks; static uint64_t pick_datapath_id(const struct ofproto *); static uint64_t pick_fallback_dpid(void); -static void update_used(struct ofproto *); +static int ofproto_expire(struct ofproto *); + static void update_stats(struct ofproto *, struct rule *, const struct odp_flow_stats *); -static void expire_rule(struct cls_rule *, void *ofproto); -static void active_timeout(struct ofproto *ofproto, struct rule *rule); static bool revalidate_rule(struct ofproto *p, struct rule *rule); static void revalidate_cb(struct cls_rule *rule_, void *p_); @@ -1148,19 +1147,9 @@ ofproto_run1(struct ofproto *p) } if (time_msec() >= p->next_expiration) { + int delay = ofproto_expire(p); + p->next_expiration = time_msec() + delay; COVERAGE_INC(ofproto_expiration); - p->next_expiration = time_msec() + 1000; - update_used(p); - - classifier_for_each(&p->cls, CLS_INC_ALL, expire_rule, p); - - /* 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 (p->ofhooks->account_checkpoint_cb) { - p->ofhooks->account_checkpoint_cb(p->aux); - } } if (p->netflow) { @@ -1871,8 +1860,10 @@ rule_create(struct ofproto *ofproto, struct rule *super, } else { list_init(&rule->list); } - rule->n_actions = n_actions; - rule->actions = xmemdup(actions, n_actions * sizeof *actions); + if (n_actions > 0) { + rule->n_actions = n_actions; + rule->actions = xmemdup(actions, n_actions * sizeof *actions); + } netflow_flow_clear(&rule->nf_flow); netflow_flow_update_time(ofproto->netflow, &rule->nf_flow, rule->created); @@ -2081,6 +2072,17 @@ rule_create_subrule(struct ofproto *ofproto, struct rule *rule, return subrule; } +/* Remove 'rule' from 'ofproto' and free up the associated memory: + * + * - If 'rule' was installed in the datapath, uninstalls it and updates + * 'rule''s statistics (or its super-rule's statistics, if it is a + * subrule), via rule_uninstall(). + * + * - Removes 'rule' from the classifier. + * + * - If 'rule' is a super-rule that has subrules, revalidates (and possibly + * uninstalls and destroys) its subrules, via rule_destroy(). + */ static void rule_remove(struct ofproto *ofproto, struct rule *rule) { @@ -2218,6 +2220,14 @@ rule_account(struct ofproto *ofproto, struct rule *rule, uint64_t extra_bytes) } } +/* 'rule' must be an exact-match rule in 'p'. + * + * If 'rule' is installed in the datapath, uninstalls it and updates's + * statistics. If 'rule' is a subrule, the statistics that are updated are + * actually its super-rule's statistics; otherwise 'rule''s own statistics are + * updated. + * + * If 'rule' is not installed, this function has no effect. */ static void rule_uninstall(struct ofproto *p, struct rule *rule) { @@ -3052,17 +3062,6 @@ handle_desc_stats_request(struct ofproto *p, struct ofconn *ofconn, return 0; } -static void -count_subrules(struct cls_rule *cls_rule, void *n_subrules_) -{ - struct rule *rule = rule_from_cls_rule(cls_rule); - int *n_subrules = n_subrules_; - - if (rule->super) { - (*n_subrules)++; - } -} - static int handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn, struct ofp_stats_request *request) @@ -3071,12 +3070,17 @@ handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn, struct ofpbuf *msg; struct odp_stats dpstats; int n_exact, n_subrules, n_wild; + struct rule *rule; msg = start_stats_reply(request, sizeof *ots * 2); /* Count rules of various kinds. */ n_subrules = 0; - classifier_for_each(&p->cls, CLS_INC_EXACT, count_subrules, &n_subrules); + CLASSIFIER_FOR_EACH_EXACT_RULE (rule, cr, &p->cls) { + if (rule->super) { + n_subrules++; + } + } n_exact = classifier_count_exact(&p->cls) - n_subrules; n_wild = classifier_count(&p->cls) - classifier_count_exact(&p->cls); @@ -3266,7 +3270,9 @@ flow_stats_cb(struct cls_rule *rule_, void *cbdata_) memset(ofs->pad2, 0, sizeof ofs->pad2); ofs->packet_count = htonll(packet_count); ofs->byte_count = htonll(byte_count); - memcpy(ofs->actions, rule->actions, act_len); + if (rule->n_actions > 0) { + memcpy(ofs->actions, rule->actions, act_len); + } } static int @@ -3335,7 +3341,9 @@ flow_stats_ds_cb(struct cls_rule *rule_, void *cbdata_) ds_put_format(results, "n_packets=%"PRIu64", ", packet_count); ds_put_format(results, "n_bytes=%"PRIu64", ", byte_count); ofp_print_match(results, &match, true); - ofp_print_actions(results, &rule->actions->header, act_len); + if (act_len > 0) { + ofp_print_actions(results, &rule->actions->header, act_len); + } ds_put_cstr(results, "\n"); } @@ -3763,14 +3771,14 @@ modify_flow(struct ofproto *p, const struct ofp_flow_mod *ofm, /* If the actions are the same, do nothing. */ if (n_actions == rule->n_actions - && !memcmp(ofm->actions, rule->actions, actions_len)) + && (!n_actions || !memcmp(ofm->actions, rule->actions, actions_len))) { return 0; } /* Replace actions. */ free(rule->actions); - rule->actions = xmemdup(ofm->actions, actions_len); + rule->actions = n_actions ? xmemdup(ofm->actions, actions_len) : NULL; rule->n_actions = n_actions; /* Make sure that the datapath gets updated properly. */ @@ -4223,6 +4231,285 @@ handle_odp_msg(struct ofproto *p, struct ofpbuf *packet) } } +/* Flow expiration. */ + +struct expire_cbdata { + struct ofproto *ofproto; + int dp_max_idle; +}; + +static int ofproto_dp_max_idle(const struct 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. + * + * Returns the number of milliseconds after which it should be called again. */ +static int +ofproto_expire(struct ofproto *ofproto) +{ + struct expire_cbdata cbdata; + + /* Update 'used' for each flow in the datapath. */ + ofproto_update_used(ofproto); + + /* Expire idle flows. + * + * A wildcarded flow is idle only when all of its subrules have expired due + * to becoming idle, so iterate through the exact-match flows first. */ + cbdata.ofproto = ofproto; + cbdata.dp_max_idle = ofproto_dp_max_idle(ofproto); + classifier_for_each(&ofproto->cls, CLS_INC_EXACT, rule_expire, &cbdata); + classifier_for_each(&ofproto->cls, CLS_INC_WILD, 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); + } + + return MIN(cbdata.dp_max_idle, 1000); +} + +/* 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); +} + +/* Calculates and returns the number of milliseconds of idle time after which + * flows should expire from the datapath and we should fold their statistics + * into their parent rules in userspace. */ +static int +ofproto_dp_max_idle(const struct ofproto *ofproto) +{ + /* + * Idle time histogram. + * + * Most of the time a switch has a relatively small number of flows. When + * this is the case we might as well keep statistics for all of them in + * userspace and to cache them in the kernel datapath for performance as + * well. + * + * As the number of flows increases, the memory required to maintain + * statistics about them in userspace and in the kernel becomes + * significant. However, with a large number of flows it is likely that + * only a few of them are "heavy hitters" that consume a large amount of + * bandwidth. At this point, only heavy hitters are worth caching in the + * kernel and maintaining in userspaces; other flows we can discard. + * + * The technique used to compute the idle time is to build a histogram with + * N_BUCKETS bucket whose width is BUCKET_WIDTH msecs each. Each flow that + * is installed in the kernel gets dropped in the appropriate bucket. + * After the histogram has been built, we compute the cutoff so that only + * the most-recently-used 1% of flows (but at least 1000 flows) are kept + * cached. At least the most-recently-used bucket of flows is kept, so + * actually an arbitrary number of flows can be kept in any given + * expiration run (though the next run will delete most of those unless + * they receive additional data). + * + * This requires a second pass through the exact-match flows, in addition + * to the pass made by ofproto_update_used(), because the former function + * never looks at uninstallable flows. + */ + enum { BUCKET_WIDTH = ROUND_UP(100, TIME_UPDATE_INTERVAL) }; + enum { N_BUCKETS = 5000 / BUCKET_WIDTH }; + int buckets[N_BUCKETS] = { 0 }; + int total, bucket; + struct rule *rule; + long long int now; + int i; + + total = classifier_count_exact(&ofproto->cls); + if (total <= 1000) { + return N_BUCKETS * BUCKET_WIDTH; + } + + /* Build histogram. */ + now = time_msec(); + CLASSIFIER_FOR_EACH_EXACT_RULE (rule, cr, &ofproto->cls) { + long long int idle = now - rule->used; + int bucket = (idle <= 0 ? 0 + : idle >= BUCKET_WIDTH * N_BUCKETS ? N_BUCKETS - 1 + : (unsigned int) idle / BUCKET_WIDTH); + buckets[bucket]++; + } + + /* Find the first bucket whose flows should be expired. */ + for (bucket = 0; bucket < N_BUCKETS; bucket++) { + if (buckets[bucket]) { + int subtotal = 0; + do { + subtotal += buckets[bucket++]; + } while (bucket < N_BUCKETS && subtotal < MAX(1000, total / 100)); + break; + } + } + + if (VLOG_IS_DBG_ENABLED()) { + struct ds s; + + ds_init(&s); + ds_put_cstr(&s, "keep"); + for (i = 0; i < N_BUCKETS; i++) { + if (i == bucket) { + ds_put_cstr(&s, ", drop"); + } + if (buckets[i]) { + ds_put_format(&s, " %d:%d", i * BUCKET_WIDTH, buckets[i]); + } + } + VLOG_INFO("%s: %s (msec:count)", + dpif_name(ofproto->dpif), ds_cstr(&s)); + ds_destroy(&s); + } + + return bucket * BUCKET_WIDTH; +} + +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); + } +} + +/* 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->cr.wc.wildcards) { + if (now >= rule->used + cbdata->dp_max_idle) { + /* This rule is idle, so drop it to free up resources. */ + if (rule->super) { + /* It's not part of the OpenFlow flow table, so we can + * delete it entirely and fold its statistics into its + * super-rule. */ + rule_remove(ofproto, rule); + } else { + /* It is part of the OpenFlow flow table, so we have to + * keep the rule but we can at least uninstall it from the + * datapath. */ + rule_uninstall(ofproto, rule); + } + } else { + /* 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, 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); + } +} + static void revalidate_cb(struct cls_rule *sub_, void *cbdata_) { @@ -4289,19 +4576,6 @@ compose_flow_removed(struct ofproto *p, const struct rule *rule, return buf; } -static void -uninstall_idle_flow(struct ofproto *ofproto, struct rule *rule) -{ - assert(rule->installed); - assert(!rule->cr.wc.wildcards); - - if (rule->super) { - rule_remove(ofproto, rule); - } else { - rule_uninstall(ofproto, rule); - } -} - static void send_flow_removed(struct ofproto *p, struct rule *rule, long long int now, uint8_t reason) @@ -4310,6 +4584,10 @@ send_flow_removed(struct ofproto *p, struct rule *rule, struct ofconn *prev; struct ofpbuf *buf = NULL; + if (!rule->send_flow_removed) { + return; + } + /* We limit the maximum number of queued flow expirations it by accounting * them under the counter for replies. That works because preventing * OpenFlow requests from being processed also prevents new flows from @@ -4318,7 +4596,7 @@ send_flow_removed(struct ofproto *p, struct rule *rule, prev = NULL; LIST_FOR_EACH (ofconn, node, &p->all_conns) { - if (rule->send_flow_removed && rconn_is_connected(ofconn->rconn) + if (rconn_is_connected(ofconn->rconn) && ofconn_receives_async_msgs(ofconn)) { if (prev) { queue_tx(ofpbuf_clone(buf), prev, prev->reply_counter); @@ -4333,122 +4611,6 @@ send_flow_removed(struct ofproto *p, struct rule *rule, } } - -static void -expire_rule(struct cls_rule *cls_rule, void *p_) -{ - struct ofproto *p = p_; - struct rule *rule = rule_from_cls_rule(cls_rule); - long long int hard_expire, idle_expire, expire, now; - - 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) { - if (rule->installed && now >= rule->used + 5000) { - uninstall_idle_flow(p, rule); - } else if (!rule->cr.wc.wildcards) { - active_timeout(p, rule); - } - - return; - } - - COVERAGE_INC(ofproto_expired); - - /* Update stats. This code will be a no-op if the rule expired - * due to an idle timeout. */ - if (rule->cr.wc.wildcards) { - struct rule *subrule, *next; - LIST_FOR_EACH_SAFE (subrule, next, list, &rule->list) { - rule_remove(p, subrule); - } - } else { - rule_uninstall(p, rule); - } - - if (!rule_is_hidden(rule)) { - send_flow_removed(p, rule, now, - (now >= hard_expire - ? OFPRR_HARD_TIMEOUT : OFPRR_IDLE_TIMEOUT)); - } - rule_remove(p, rule); -} - -static void -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. */ - 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(); - } -} - -static void -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) { - COVERAGE_INC(ofproto_unexpected_rule); - dpif_flow_del(p->dpif, f); - continue; - } - - update_time(p, rule, &f->stats); - rule_account(p, rule, f->stats.n_bytes); - } - free(flows); -} - /* pinsched callback for sending 'packet' on 'ofconn'. */ static void do_send_packet_in(struct ofpbuf *packet, void *ofconn_)