X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=ofproto%2Fofproto.c;h=1173936e91476592c1dab70091bdc8aed954015b;hb=fd76a6f94338b668175336434b156827458b5e7d;hp=c7fc09bc10f23df6f0018beb9d95d1775bcb3cc4;hpb=ad3efdcb0d5812df189ab08f19dac4b39f3a9e27;p=sliver-openvswitch.git diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index c7fc09bc1..1173936e9 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "bitmap.h" #include "byte-order.h" #include "classifier.h" @@ -75,7 +76,8 @@ enum ofproto_state { enum ofoperation_type { OFOPERATION_ADD, OFOPERATION_DELETE, - OFOPERATION_MODIFY + OFOPERATION_MODIFY, + OFOPERATION_REPLACE }; /* A single OpenFlow request can execute any number of operations. The @@ -120,10 +122,8 @@ struct ofoperation { struct rule *rule; /* Rule being operated upon. */ enum ofoperation_type type; /* Type of operation. */ - /* OFOPERATION_ADD. */ - struct rule *victim; /* Rule being replaced, if any.. */ - - /* OFOPERATION_MODIFY: The old actions, if the actions are changing. */ + /* OFOPERATION_MODIFY, OFOPERATION_REPLACE: The old actions, if the actions + * are changing. */ struct ofpact *ofpacts; size_t ofpacts_len; uint32_t meter_id; @@ -132,6 +132,9 @@ struct ofoperation { enum ofp_flow_removed_reason reason; /* Reason flow was removed. */ ovs_be64 flow_cookie; /* Rule's old flow cookie. */ + uint16_t idle_timeout; /* Rule's old idle timeout. */ + uint16_t hard_timeout; /* Rule's old hard timeout. */ + bool send_flow_removed; /* Rule's old 'send_flow_removed'. */ enum ofperr error; /* 0 if no error. */ }; @@ -156,8 +159,7 @@ static void oftable_remove_rule(struct rule *rule) OVS_RELEASES(rule->evict); static void oftable_remove_rule__(struct ofproto *ofproto, struct classifier *cls, struct rule *rule) OVS_REQ_WRLOCK(cls->rwlock) OVS_RELEASES(rule->evict); -static struct rule *oftable_replace_rule(struct rule *); -static void oftable_substitute_rule(struct rule *old, struct rule *new); +static void oftable_insert_rule(struct rule *); /* A set of rules within a single OpenFlow table (oftable) that have the same * values for the oftable's eviction_fields. A rule to be evicted, when one is @@ -185,6 +187,8 @@ static bool choose_rule_to_evict(struct oftable *table, struct rule **rulep) OVS_TRY_WRLOCK(true, (*rulep)->evict); static void ofproto_evict(struct ofproto *); static uint32_t rule_eviction_priority(struct rule *); +static void eviction_group_add_rule(struct rule *); +static void eviction_group_remove_rule(struct rule *); /* ofport. */ static void ofport_destroy__(struct ofport *); @@ -203,6 +207,9 @@ static bool rule_is_modifiable(const struct rule *); static enum ofperr add_flow(struct ofproto *, struct ofconn *, struct ofputil_flow_mod *, const struct ofp_header *); +static enum ofperr modify_flows__(struct ofproto *, struct ofconn *, + struct ofputil_flow_mod *, + const struct ofp_header *, struct list *); static void delete_flow__(struct rule *rule, struct ofopgroup *, enum ofp_flow_removed_reason) OVS_RELEASES(rule->evict); @@ -229,6 +236,7 @@ static size_t n_ofproto_classes; static size_t allocated_ofproto_classes; unsigned flow_eviction_threshold = OFPROTO_FLOW_EVICTION_THRESHOLD_DEFAULT; +unsigned n_handler_threads; enum ofproto_flow_miss_model flow_miss_model = OFPROTO_HANDLE_MISS_AUTO; /* Map from datapath name to struct ofproto, for use by unixctl commands. */ @@ -430,12 +438,12 @@ ofproto_create(const char *datapath_name, const char *datapath_type, hmap_init(&ofproto->ports); shash_init(&ofproto->port_by_name); simap_init(&ofproto->ofp_requests); - ofproto->max_ports = OFPP_MAX; + ofproto->max_ports = ofp_to_u16(OFPP_MAX); ofproto->tables = NULL; ofproto->n_tables = 0; hindex_init(&ofproto->cookies); list_init(&ofproto->expirable); - ovs_mutex_init(&ofproto->expirable_mutex, PTHREAD_MUTEX_RECURSIVE); + ovs_mutex_init_recursive(&ofproto->expirable_mutex); ofproto->connmgr = connmgr_create(ofproto, datapath_name, datapath_name); ofproto->state = S_OPENFLOW; list_init(&ofproto->pending); @@ -459,7 +467,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type, /* The "max_ports" member should have been set by ->construct(ofproto). * Port 0 is not a valid OpenFlow port, so mark that as unavailable. */ - ofproto->ofp_port_ids = bitmap_allocate(ofp_to_u16(ofproto->max_ports)); + ofproto->ofp_port_ids = bitmap_allocate(ofproto->max_ports); bitmap_set1(ofproto->ofp_port_ids, 0); /* Check that hidden tables, if any, are at the end. */ @@ -518,9 +526,9 @@ ofproto_init_tables(struct ofproto *ofproto, int n_tables) * Reserved ports numbered OFPP_MAX and higher are special and not subject to * the 'max_ports' restriction. */ void -ofproto_init_max_ports(struct ofproto *ofproto, ofp_port_t max_ports) +ofproto_init_max_ports(struct ofproto *ofproto, uint16_t max_ports) { - ovs_assert(ofp_to_u16(max_ports) <= ofp_to_u16(OFPP_MAX)); + ovs_assert(max_ports <= ofp_to_u16(OFPP_MAX)); ofproto->max_ports = max_ports; } @@ -628,6 +636,18 @@ ofproto_set_mac_table_config(struct ofproto *ofproto, unsigned idle_time, } } +/* Sets number of upcall handler threads. The default is + * (number of online cores - 1). */ +void +ofproto_set_n_handler_threads(unsigned limit) +{ + if (limit) { + n_handler_threads = limit; + } else { + n_handler_threads = MAX(1, sysconf(_SC_NPROCESSORS_ONLN) - 1); + } +} + void ofproto_set_dp_desc(struct ofproto *p, const char *dp_desc) { @@ -1054,17 +1074,37 @@ ofproto_get_snoops(const struct ofproto *ofproto, struct sset *snoops) connmgr_get_snoops(ofproto->connmgr, snoops); } +/* Deletes 'rule' from 'cls' within 'ofproto'. + * + * The 'cls' argument is redundant (it is &ofproto->tables[rule->table_id].cls) + * but it allows Clang to do better checking. */ static void -ofproto_flush__(struct ofproto *ofproto) +ofproto_delete_rule(struct ofproto *ofproto, struct classifier *cls, + struct rule *rule) + OVS_REQ_WRLOCK(cls->rwlock) { struct ofopgroup *group; + + ovs_assert(!rule->pending); + ovs_assert(cls == &ofproto->tables[rule->table_id].cls); + + group = ofopgroup_create_unattached(ofproto); + ofoperation_create(group, rule, OFOPERATION_DELETE, OFPRR_DELETE); + ovs_rwlock_wrlock(&rule->evict); + oftable_remove_rule__(ofproto, cls, rule); + ofproto->ofproto_class->rule_delete(rule); + ofopgroup_submit(group); +} + +static void +ofproto_flush__(struct ofproto *ofproto) +{ struct oftable *table; if (ofproto->ofproto_class->flush) { ofproto->ofproto_class->flush(ofproto); } - group = ofopgroup_create_unattached(ofproto); OFPROTO_FOR_EACH_TABLE (table, ofproto) { struct rule *rule, *next_rule; struct cls_cursor cursor; @@ -1077,16 +1117,11 @@ ofproto_flush__(struct ofproto *ofproto) cls_cursor_init(&cursor, &table->cls, NULL); CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) { if (!rule->pending) { - ofoperation_create(group, rule, OFOPERATION_DELETE, - OFPRR_DELETE); - ovs_rwlock_wrlock(&rule->evict); - oftable_remove_rule__(ofproto, &table->cls, rule); - ofproto->ofproto_class->rule_destruct(rule); + ofproto_delete_rule(ofproto, &table->cls, rule); } } ovs_rwlock_unlock(&table->cls.rwlock); } - ofopgroup_submit(group); } static void @@ -1664,12 +1699,13 @@ bool ofproto_delete_flow(struct ofproto *ofproto, const struct match *target, unsigned int priority) { + struct classifier *cls = &ofproto->tables[0].cls; struct rule *rule; - ovs_rwlock_rdlock(&ofproto->tables[0].cls.rwlock); - rule = rule_from_cls_rule(classifier_find_match_exactly( - &ofproto->tables[0].cls, target, priority)); - ovs_rwlock_unlock(&ofproto->tables[0].cls.rwlock); + ovs_rwlock_rdlock(&cls->rwlock); + rule = rule_from_cls_rule(classifier_find_match_exactly(cls, target, + priority)); + ovs_rwlock_unlock(&cls->rwlock); if (!rule) { /* No such rule -> success. */ return true; @@ -1679,12 +1715,10 @@ ofproto_delete_flow(struct ofproto *ofproto, return false; } else { /* Initiate deletion -> success. */ - struct ofopgroup *group = ofopgroup_create_unattached(ofproto); - ofoperation_create(group, rule, OFOPERATION_DELETE, OFPRR_DELETE); - ovs_rwlock_wrlock(&rule->evict); - oftable_remove_rule(rule); - ofproto->ofproto_class->rule_destruct(rule); - ofopgroup_submit(group); + ovs_rwlock_wrlock(&cls->rwlock); + ofproto_delete_rule(ofproto, cls, rule); + ovs_rwlock_unlock(&cls->rwlock); + return true; } @@ -1728,32 +1762,28 @@ reinit_ports(struct ofproto *p) static ofp_port_t alloc_ofp_port(struct ofproto *ofproto, const char *netdev_name) { - uint16_t max_ports = ofp_to_u16(ofproto->max_ports); uint16_t port_idx; port_idx = simap_get(&ofproto->ofp_requests, netdev_name); - if (!port_idx) { - port_idx = UINT16_MAX; - } + port_idx = port_idx ? port_idx : UINT16_MAX; - if (port_idx >= max_ports + if (port_idx >= ofproto->max_ports || bitmap_is_set(ofproto->ofp_port_ids, port_idx)) { - uint16_t end_port_no = ofp_to_u16(ofproto->alloc_port_no); - uint16_t alloc_port_no = end_port_no; + uint16_t end_port_no = ofproto->alloc_port_no; /* Search for a free OpenFlow port number. We try not to * immediately reuse them to prevent problems due to old * flows. */ for (;;) { - if (++alloc_port_no >= max_ports) { - alloc_port_no = 0; + if (++ofproto->alloc_port_no >= ofproto->max_ports) { + ofproto->alloc_port_no = 0; } - if (!bitmap_is_set(ofproto->ofp_port_ids, alloc_port_no)) { - port_idx = alloc_port_no; - ofproto->alloc_port_no = u16_to_ofp(alloc_port_no); + if (!bitmap_is_set(ofproto->ofp_port_ids, + ofproto->alloc_port_no)) { + port_idx = ofproto->alloc_port_no; break; } - if (alloc_port_no == end_port_no) { + if (ofproto->alloc_port_no == end_port_no) { return OFPP_NONE; } } @@ -1765,7 +1795,7 @@ alloc_ofp_port(struct ofproto *ofproto, const char *netdev_name) static void dealloc_ofp_port(const struct ofproto *ofproto, ofp_port_t ofp_port) { - if (ofp_to_u16(ofp_port) < ofp_to_u16(ofproto->max_ports)) { + if (ofp_to_u16(ofp_port) < ofproto->max_ports) { bitmap_set0(ofproto->ofp_port_ids, ofp_to_u16(ofp_port)); } } @@ -2089,6 +2119,10 @@ init_ports(struct ofproto *p) netdev = ofport_open(p, &ofproto_port, &pp); if (netdev) { ofport_install(p, netdev, &pp); + if (ofp_to_u16(ofproto_port.ofp_port) < p->max_ports) { + p->alloc_port_no = MAX(p->alloc_port_no, + ofp_to_u16(ofproto_port.ofp_port)); + } } } } @@ -2182,6 +2216,7 @@ static void ofproto_rule_destroy__(struct rule *rule) { if (rule) { + rule->ofproto->ofproto_class->rule_destruct(rule); cls_rule_destroy(&rule->cr); free(rule->ofpacts); ovs_mutex_destroy(&rule->timeout_mutex); @@ -2191,24 +2226,18 @@ ofproto_rule_destroy__(struct rule *rule) } /* This function allows an ofproto implementation to destroy any rules that - * remain when its ->destruct() function is called. The caller must have - * already uninitialized any derived members of 'rule' (step 5 described in the - * large comment in ofproto/ofproto-provider.h titled "Life Cycle"). - * This function implements steps 6 and 7. + * remain when its ->destruct() function is called.. This function implements + * steps 4.4 and 4.5 in the section titled "Rule Life Cycle" in + * ofproto-provider.h. * * This function should only be called from an ofproto implementation's * ->destruct() function. It is not suitable elsewhere. */ void -ofproto_rule_destroy(struct ofproto *ofproto, struct classifier *cls, - struct rule *rule) OVS_REQ_WRLOCK(cls->rwlock) +ofproto_rule_delete(struct ofproto *ofproto, struct classifier *cls, + struct rule *rule) + OVS_REQ_WRLOCK(cls->rwlock) { - ovs_assert(!rule->pending); - if (!ovs_rwlock_trywrlock(&rule->evict)) { - oftable_remove_rule__(ofproto, cls, rule); - } else { - NOT_REACHED(); - } - ofproto_rule_destroy__(rule); + ofproto_delete_rule(ofproto, cls, rule); } /* Returns true if 'rule' has an OpenFlow OFPAT_OUTPUT or OFPAT_ENQUEUE action @@ -2231,12 +2260,11 @@ ofoperation_has_out_port(const struct ofoperation *op, ofp_port_t out_port) switch (op->type) { case OFOPERATION_ADD: - return op->victim && ofproto_rule_has_out_port(op->victim, out_port); - case OFOPERATION_DELETE: return false; case OFOPERATION_MODIFY: + case OFOPERATION_REPLACE: return ofpacts_output_to_port(op->ofpacts, op->ofpacts_len, out_port); } @@ -2244,8 +2272,7 @@ ofoperation_has_out_port(const struct ofoperation *op, ofp_port_t out_port) } /* Executes the actions indicated by 'rule' on 'packet' and credits 'rule''s - * statistics appropriately. 'packet' must have at least sizeof(struct - * ofp10_packet_in) bytes of headroom. + * statistics appropriately. * * 'packet' doesn't necessarily have to match 'rule'. 'rule' will be credited * with statistics for 'packet' either way. @@ -2257,8 +2284,6 @@ rule_execute(struct rule *rule, ofp_port_t in_port, struct ofpbuf *packet) struct flow flow; union flow_in_port in_port_; - ovs_assert(ofpbuf_headroom(packet) >= sizeof(struct ofp10_packet_in)); - in_port_.ofp_port = in_port; flow_extract(packet, 0, 0, NULL, &in_port_, &flow); return rule->ofproto->ofproto_class->rule_execute(rule, &flow, packet); @@ -2448,8 +2473,8 @@ ofproto_check_ofpacts(struct ofproto *ofproto, enum ofperr error; uint32_t mid; - error = ofpacts_check(ofpacts, ofpacts_len, flow, ofproto->max_ports, - table_id); + error = ofpacts_check(ofpacts, ofpacts_len, flow, + u16_to_ofp(ofproto->max_ports), table_id); if (error) { return error; } @@ -2486,7 +2511,7 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh) if (error) { goto exit_free_ofpacts; } - if (ofp_to_u16(po.in_port) >= ofp_to_u16(p->max_ports) + if (ofp_to_u16(po.in_port) >= p->max_ports && ofp_to_u16(po.in_port) < ofp_to_u16(OFPP_MAX)) { error = OFPERR_OFPBRC_BAD_PORT; goto exit_free_ofpacts; @@ -2500,8 +2525,8 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh) goto exit_free_ofpacts; } } else { - payload = xmalloc(sizeof *payload); - ofpbuf_use_const(payload, po.packet, po.packet_len); + /* Ensure that the L3 header is 32-bit aligned. */ + payload = ofpbuf_clone_data_with_headroom(po.packet, po.packet_len, 2); } /* Verify actions against packet, then send packet if successful. */ @@ -3058,17 +3083,17 @@ handle_flow_stats_request(struct ofconn *ofconn, &fs.byte_count); fs.ofpacts = rule->ofpacts; fs.ofpacts_len = rule->ofpacts_len; - fs.flags = 0; ovs_mutex_lock(&rule->timeout_mutex); fs.idle_timeout = rule->idle_timeout; fs.hard_timeout = rule->hard_timeout; ovs_mutex_unlock(&rule->timeout_mutex); + fs.flags = 0; if (rule->send_flow_removed) { - fs.flags |= OFPFF_SEND_FLOW_REM; - /* FIXME: Implement OF 1.3 flags OFPFF13_NO_PKT_COUNTS - and OFPFF13_NO_BYT_COUNTS */ + fs.flags |= OFPUTIL_FF_SEND_FLOW_REM; + /* FIXME: Implement OFPUTIL_FF_NO_PKT_COUNTS and + OFPUTIL_FF_NO_BYT_COUNTS. */ } ofputil_append_flow_stats_reply(&fs, &replies); } @@ -3323,6 +3348,34 @@ is_flow_deletion_pending(const struct ofproto *ofproto, return false; } +static enum ofperr +evict_rule_from_table(struct ofproto *ofproto, struct oftable *table) +{ + struct rule *rule; + size_t n_rules; + + ovs_rwlock_rdlock(&table->cls.rwlock); + n_rules = classifier_count(&table->cls); + ovs_rwlock_unlock(&table->cls.rwlock); + + if (n_rules < table->max_flows) { + return 0; + } else if (!choose_rule_to_evict(table, &rule)) { + return OFPERR_OFPFMFC_TABLE_FULL; + } else if (rule->pending) { + ovs_rwlock_unlock(&rule->evict); + return OFPROTO_POSTPONE; + } else { + struct ofopgroup *group; + + group = ofopgroup_create_unattached(ofproto); + delete_flow__(rule, group, OFPRR_EVICTION); + ofopgroup_submit(group); + + return 0; + } +} + /* Implements OFPFC_ADD and the cases for OFPFC_MODIFY and OFPFC_MODIFY_STRICT * in which no matching flow already exists in the flow table. * @@ -3342,10 +3395,9 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn, { struct oftable *table; struct ofopgroup *group; - struct rule *victim; + struct cls_rule cr; struct rule *rule; uint8_t table_id; - bool overlaps; int error; error = check_table_id(ofproto, fm->table_id); @@ -3378,6 +3430,28 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn, return OFPERR_OFPBRC_EPERM; } + cls_rule_init(&cr, &fm->match, fm->priority); + + /* Transform "add" into "modify" if there's an existing identical flow. */ + ovs_rwlock_rdlock(&table->cls.rwlock); + rule = rule_from_cls_rule(classifier_find_rule_exactly(&table->cls, &cr)); + ovs_rwlock_unlock(&table->cls.rwlock); + if (rule) { + cls_rule_destroy(&cr); + if (!rule_is_modifiable(rule)) { + return OFPERR_OFPBRC_EPERM; + } else if (rule->pending) { + return OFPROTO_POSTPONE; + } else { + struct list rules; + + list_init(&rules); + list_push_back(&rules, &rule->ofproto_node); + fm->modify_cookie = true; + return modify_flows__(ofproto, ofconn, fm, request, &rules); + } + } + /* Verify actions. */ error = ofproto_check_ofpacts(ofproto, fm->ofpacts, fm->ofpacts_len, &fm->match.flow, table_id); @@ -3385,49 +3459,57 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn, return error; } - /* Allocate new rule and initialize classifier rule. */ - rule = ofproto->ofproto_class->rule_alloc(); - if (!rule) { - VLOG_WARN_RL(&rl, "%s: failed to create rule (%s)", - ofproto->name, ovs_strerror(error)); - return ENOMEM; - } - cls_rule_init(&rule->cr, &fm->match, fm->priority); - /* Serialize against pending deletion. */ - if (is_flow_deletion_pending(ofproto, &rule->cr, table_id)) { - cls_rule_destroy(&rule->cr); - ofproto->ofproto_class->rule_dealloc(rule); + if (is_flow_deletion_pending(ofproto, &cr, table_id)) { + cls_rule_destroy(&cr); return OFPROTO_POSTPONE; } /* Check for overlap, if requested. */ - ovs_rwlock_rdlock(&table->cls.rwlock); - overlaps = classifier_rule_overlaps(&table->cls, &rule->cr); - ovs_rwlock_unlock(&table->cls.rwlock); - if (fm->flags & OFPFF_CHECK_OVERLAP && overlaps) { - cls_rule_destroy(&rule->cr); - ofproto->ofproto_class->rule_dealloc(rule); - return OFPERR_OFPFMFC_OVERLAP; + if (fm->flags & OFPUTIL_FF_CHECK_OVERLAP) { + bool overlaps; + + ovs_rwlock_rdlock(&table->cls.rwlock); + overlaps = classifier_rule_overlaps(&table->cls, &cr); + ovs_rwlock_unlock(&table->cls.rwlock); + + if (overlaps) { + cls_rule_destroy(&cr); + return OFPERR_OFPFMFC_OVERLAP; + } + } + + /* If necessary, evict an existing rule to clear out space. */ + error = evict_rule_from_table(ofproto, table); + if (error) { + cls_rule_destroy(&cr); + return error; } - /* FIXME: Implement OFPFF12_RESET_COUNTS */ + /* Allocate new rule. */ + rule = ofproto->ofproto_class->rule_alloc(); + if (!rule) { + cls_rule_destroy(&cr); + VLOG_WARN_RL(&rl, "%s: failed to create rule (%s)", + ofproto->name, ovs_strerror(error)); + return ENOMEM; + } + /* Initialize base state. */ rule->ofproto = ofproto; + cls_rule_move(&rule->cr, &cr); rule->pending = NULL; rule->flow_cookie = fm->new_cookie; rule->created = rule->modified = rule->used = time_msec(); - ovs_mutex_init(&rule->timeout_mutex, OVS_MUTEX_ADAPTIVE); + ovs_mutex_init(&rule->timeout_mutex); ovs_mutex_lock(&rule->timeout_mutex); rule->idle_timeout = fm->idle_timeout; rule->hard_timeout = fm->hard_timeout; ovs_mutex_unlock(&rule->timeout_mutex); rule->table_id = table - ofproto->tables; - rule->send_flow_removed = (fm->flags & OFPFF_SEND_FLOW_REM) != 0; - /* FIXME: Implement OF 1.3 flags OFPFF13_NO_PKT_COUNTS - and OFPFF13_NO_BYT_COUNTS */ + rule->send_flow_removed = (fm->flags & OFPUTIL_FF_SEND_FLOW_REM) != 0; rule->ofpacts = xmemdup(fm->ofpacts, fm->ofpacts_len); rule->ofpacts_len = fm->ofpacts_len; rule->meter_id = find_meter(rule->ofpacts, rule->ofpacts_len); @@ -3439,65 +3521,21 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn, rule->modify_seqno = 0; ovs_rwlock_init(&rule->evict); - /* Insert new rule. */ - victim = oftable_replace_rule(rule); - if (victim && !rule_is_modifiable(victim)) { - error = OFPERR_OFPBRC_EPERM; - } else if (victim && victim->pending) { - error = OFPROTO_POSTPONE; - } else { - struct ofoperation *op; - struct rule *evict; - size_t n_rules; - - ovs_rwlock_rdlock(&table->cls.rwlock); - n_rules = classifier_count(&table->cls); - ovs_rwlock_unlock(&table->cls.rwlock); - if (n_rules > table->max_flows) { - ovs_rwlock_rdlock(&rule->evict); - if (choose_rule_to_evict(table, &evict)) { - ovs_rwlock_unlock(&rule->evict); - ovs_rwlock_unlock(&evict->evict); - if (evict->pending) { - error = OFPROTO_POSTPONE; - goto exit; - } - } else { - ovs_rwlock_unlock(&rule->evict); - error = OFPERR_OFPFMFC_TABLE_FULL; - goto exit; - } - } else { - evict = NULL; - } - - group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id); - op = ofoperation_create(group, rule, OFOPERATION_ADD, 0); - op->victim = victim; - - error = ofproto->ofproto_class->rule_construct(rule); - if (error) { - op->group->n_running--; - ofoperation_destroy(rule->pending); - } else if (evict) { - /* It would be better if we maintained the lock we took in - * choose_rule_to_evict() earlier, but that confuses the thread - * safety analysis, and this code is fragile enough that we really - * need it. In the worst case, we'll have to block a little while - * before we perform the eviction, which doesn't seem like a big - * problem. */ - ovs_rwlock_wrlock(&evict->evict); - delete_flow__(evict, group, OFPRR_EVICTION); - } - ofopgroup_submit(group); - } - -exit: - /* Back out if an error occurred. */ + /* Construct rule, initializing derived state. */ + error = ofproto->ofproto_class->rule_construct(rule); if (error) { - oftable_substitute_rule(rule, victim); ofproto_rule_destroy__(rule); + return error; } + + /* Insert rule. */ + oftable_insert_rule(rule); + + group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id); + ofoperation_create(group, rule, OFOPERATION_ADD, 0); + ofproto->ofproto_class->rule_insert(rule); + ofopgroup_submit(group); + return error; } @@ -3515,17 +3553,20 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn, struct ofputil_flow_mod *fm, const struct ofp_header *request, struct list *rules) { + enum ofoperation_type type; struct ofopgroup *group; struct rule *rule; enum ofperr error; + type = fm->command == OFPFC_ADD ? OFOPERATION_REPLACE : OFOPERATION_MODIFY; group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id); error = OFPERR_OFPBRC_EPERM; LIST_FOR_EACH (rule, ofproto_node, rules) { struct ofoperation *op; bool actions_changed; + bool reset_counters; - /* FIXME: Implement OFPFF12_RESET_COUNTS */ + /* FIXME: Implement OFPFUTIL_FF_RESET_COUNTS */ if (rule_is_modifiable(rule)) { /* At least one rule is modifiable, don't report EPERM error. */ @@ -3536,7 +3577,7 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn, /* Verify actions. */ error = ofpacts_check(fm->ofpacts, fm->ofpacts_len, &fm->match.flow, - ofproto->max_ports, rule->table_id); + u16_to_ofp(ofproto->max_ports), rule->table_id); if (error) { return error; } @@ -3544,19 +3585,39 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn, actions_changed = !ofpacts_equal(fm->ofpacts, fm->ofpacts_len, rule->ofpacts, rule->ofpacts_len); - op = ofoperation_create(group, rule, OFOPERATION_MODIFY, 0); + op = ofoperation_create(group, rule, type, 0); if (fm->modify_cookie && fm->new_cookie != htonll(UINT64_MAX)) { ofproto_rule_change_cookie(ofproto, rule, fm->new_cookie); } - if (actions_changed) { + if (type == OFOPERATION_REPLACE) { + ovs_mutex_lock(&rule->timeout_mutex); + rule->idle_timeout = fm->idle_timeout; + rule->hard_timeout = fm->hard_timeout; + ovs_mutex_unlock(&rule->timeout_mutex); + + rule->send_flow_removed = (fm->flags + & OFPUTIL_FF_SEND_FLOW_REM) != 0; + + if (fm->idle_timeout || fm->hard_timeout) { + if (!rule->eviction_group) { + eviction_group_add_rule(rule); + } + } else { + eviction_group_remove_rule(rule); + } + } + + reset_counters = (fm->flags & OFPUTIL_FF_RESET_COUNTS) != 0; + if (actions_changed || reset_counters) { op->ofpacts = rule->ofpacts; op->ofpacts_len = rule->ofpacts_len; op->meter_id = rule->meter_id; rule->ofpacts = xmemdup(fm->ofpacts, fm->ofpacts_len); rule->ofpacts_len = fm->ofpacts_len; rule->meter_id = find_meter(rule->ofpacts, rule->ofpacts_len); - rule->ofproto->ofproto_class->rule_modify_actions(rule); + rule->ofproto->ofproto_class->rule_modify_actions(rule, + reset_counters); } else { ofoperation_complete(op, 0); } @@ -3641,7 +3702,7 @@ delete_flow__(struct rule *rule, struct ofopgroup *group, ofoperation_create(group, rule, OFOPERATION_DELETE, reason); oftable_remove_rule(rule); - ofproto->ofproto_class->rule_destruct(rule); + ofproto->ofproto_class->rule_delete(rule); } /* Deletes the rules listed in 'rules'. @@ -3755,17 +3816,54 @@ void ofproto_rule_expire(struct rule *rule, uint8_t reason) { struct ofproto *ofproto = rule->ofproto; - struct ofopgroup *group; + struct classifier *cls = &ofproto->tables[rule->table_id].cls; ovs_assert(reason == OFPRR_HARD_TIMEOUT || reason == OFPRR_IDLE_TIMEOUT); - ofproto_rule_send_removed(rule, reason); - group = ofopgroup_create_unattached(ofproto); - ofoperation_create(group, rule, OFOPERATION_DELETE, reason); - oftable_remove_rule(rule); - ofproto->ofproto_class->rule_destruct(rule); - ofopgroup_submit(group); + ovs_rwlock_wrlock(&cls->rwlock); + ofproto_delete_rule(ofproto, cls, rule); + ovs_rwlock_unlock(&cls->rwlock); +} + +/* Reduces '*timeout' to no more than 'max'. A value of zero in either case + * means "infinite". */ +static void +reduce_timeout(uint16_t max, uint16_t *timeout) +{ + if (max && (!*timeout || *timeout > max)) { + *timeout = max; + } +} + +/* If 'idle_timeout' is nonzero, and 'rule' has no idle timeout or an idle + * timeout greater than 'idle_timeout', lowers 'rule''s idle timeout to + * 'idle_timeout' seconds. Similarly for 'hard_timeout'. + * + * Suitable for implementing OFPACT_FIN_TIMEOUT. */ +void +ofproto_rule_reduce_timeouts(struct rule *rule, + uint16_t idle_timeout, uint16_t hard_timeout) + OVS_EXCLUDED(rule->ofproto->expirable_mutex, rule->timeout_mutex) +{ + if (!idle_timeout && !hard_timeout) { + return; + } + + ovs_mutex_lock(&rule->ofproto->expirable_mutex); + if (list_is_empty(&rule->expirable)) { + list_insert(&rule->ofproto->expirable, &rule->expirable); + } + ovs_mutex_unlock(&rule->ofproto->expirable_mutex); + + ovs_mutex_lock(&rule->timeout_mutex); + reduce_timeout(idle_timeout, &rule->idle_timeout); + reduce_timeout(hard_timeout, &rule->hard_timeout); + ovs_mutex_unlock(&rule->timeout_mutex); + + if (!rule->eviction_group) { + eviction_group_add_rule(rule); + } } static enum ofperr @@ -4018,7 +4116,7 @@ ofproto_compose_flow_refresh_update(const struct rule *rule, struct ofputil_flow_update fu; struct match match; - if (op && op->type == OFOPERATION_ADD && !op->victim) { + if (op && op->type == OFOPERATION_ADD) { /* We'll report the final flow when the operation completes. Reporting * it now would cause a duplicate report later. */ return; @@ -4047,12 +4145,10 @@ ofproto_compose_flow_refresh_update(const struct rule *rule, * actions, so that when the operation commits we report the change. */ switch (op->type) { case OFOPERATION_ADD: - /* We already verified that there was a victim. */ - fu.ofpacts = op->victim->ofpacts; - fu.ofpacts_len = op->victim->ofpacts_len; - break; + NOT_REACHED(); case OFOPERATION_MODIFY: + case OFOPERATION_REPLACE: if (op->ofpacts) { fu.ofpacts = op->ofpacts; fu.ofpacts_len = op->ofpacts_len; @@ -4863,15 +4959,27 @@ ofopgroup_complete(struct ofopgroup *group) && rule->flow_cookie == op->flow_cookie))) { /* Check that we can just cast from ofoperation_type to * nx_flow_update_event. */ - BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_ADD - == NXFME_ADDED); - BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_DELETE - == NXFME_DELETED); - BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_MODIFY - == NXFME_MODIFIED); - - ofmonitor_report(ofproto->connmgr, rule, - (enum nx_flow_update_event) op->type, + enum nx_flow_update_event event_type; + + switch (op->type) { + case OFOPERATION_ADD: + case OFOPERATION_REPLACE: + event_type = NXFME_ADDED; + break; + + case OFOPERATION_DELETE: + event_type = NXFME_DELETED; + break; + + case OFOPERATION_MODIFY: + event_type = NXFME_MODIFIED; + break; + + default: + NOT_REACHED(); + } + + ofmonitor_report(ofproto->connmgr, rule, event_type, op->reason, abbrev_ofconn, abbrev_xid); } @@ -4882,7 +4990,6 @@ ofopgroup_complete(struct ofopgroup *group) if (!op->error) { uint16_t vid_mask; - ofproto_rule_destroy__(op->victim); vid_mask = minimask_get_vid_mask(&rule->cr.match.mask); if (vid_mask == VLAN_VID_MASK) { if (ofproto->vlan_bitmap) { @@ -4896,7 +5003,8 @@ ofopgroup_complete(struct ofopgroup *group) } } } else { - oftable_substitute_rule(rule, op->victim); + ovs_rwlock_wrlock(&rule->evict); + oftable_remove_rule(rule); ofproto_rule_destroy__(rule); } break; @@ -4908,10 +5016,20 @@ ofopgroup_complete(struct ofopgroup *group) break; case OFOPERATION_MODIFY: + case OFOPERATION_REPLACE: if (!op->error) { - rule->modified = time_msec(); + long long int now = time_msec(); + + rule->modified = now; + if (op->type == OFOPERATION_REPLACE) { + rule->created = rule->used = now; + } } else { ofproto_rule_change_cookie(ofproto, rule, op->flow_cookie); + ovs_mutex_lock(&rule->timeout_mutex); + rule->idle_timeout = op->idle_timeout; + rule->hard_timeout = op->hard_timeout; + ovs_mutex_unlock(&rule->timeout_mutex); if (op->ofpacts) { free(rule->ofpacts); rule->ofpacts = op->ofpacts; @@ -4919,6 +5037,7 @@ ofopgroup_complete(struct ofopgroup *group) op->ofpacts = NULL; op->ofpacts_len = 0; } + rule->send_flow_removed = op->send_flow_removed; } break; @@ -4972,6 +5091,11 @@ ofoperation_create(struct ofopgroup *group, struct rule *rule, op->type = type; op->reason = reason; op->flow_cookie = rule->flow_cookie; + ovs_mutex_lock(&rule->timeout_mutex); + op->idle_timeout = rule->idle_timeout; + op->hard_timeout = rule->hard_timeout; + ovs_mutex_unlock(&rule->timeout_mutex); + op->send_flow_removed = rule->send_flow_removed; group->n_running++; @@ -5003,14 +5127,7 @@ ofoperation_destroy(struct ofoperation *op) * indicate success or an OpenFlow error code on failure. * * If 'error' is 0, indicating success, the operation will be committed - * permanently to the flow table. There is one interesting subcase: - * - * - If 'op' is an "add flow" operation that is replacing an existing rule in - * the flow table (the "victim" rule) by a new one, then the caller must - * have uninitialized any derived state in the victim rule, as in step 5 in - * the "Life Cycle" in ofproto/ofproto-provider.h. ofoperation_complete() - * performs steps 6 and 7 for the victim rule, most notably by calling its - * ->rule_dealloc() function. + * permanently to the flow table. * * If 'error' is nonzero, then generally the operation will be rolled back: * @@ -5042,13 +5159,6 @@ ofoperation_complete(struct ofoperation *op, enum ofperr error) ofopgroup_complete(group); } } - -struct rule * -ofoperation_get_victim(struct ofoperation *op) -{ - ovs_assert(op->type == OFOPERATION_ADD); - return op->victim; -} static uint64_t pick_datapath_id(const struct ofproto *ofproto) @@ -5106,7 +5216,7 @@ choose_rule_to_evict(struct oftable *table, struct rule **rulep) * group has no evictable rules. * * - The outer loop can exit only if table's 'max_flows' is all filled up - * by unevictable rules'. */ + * by unevictable rules. */ HEAP_FOR_EACH (evg, size_node, &table->eviction_groups_by_size) { struct rule *rule; @@ -5159,7 +5269,7 @@ ofproto_evict(struct ofproto *ofproto) ofoperation_create(group, rule, OFOPERATION_DELETE, OFPRR_EVICTION); oftable_remove_rule(rule); - ofproto->ofproto_class->rule_destruct(rule); + ofproto->ofproto_class->rule_delete(rule); } } ofopgroup_submit(group); @@ -5459,7 +5569,8 @@ oftable_enable_eviction(struct oftable *table, /* Removes 'rule' from the oftable that contains it. */ static void oftable_remove_rule__(struct ofproto *ofproto, struct classifier *cls, - struct rule *rule) OVS_REQ_WRLOCK(cls->rwlock) + struct rule *rule) + OVS_REQ_WRLOCK(cls->rwlock) OVS_RELEASES(rule->evict) { classifier_remove(cls, &rule->cr); if (rule->meter_id) { @@ -5475,6 +5586,7 @@ oftable_remove_rule__(struct ofproto *ofproto, struct classifier *cls, if (!list_is_empty(&rule->meter_list_node)) { list_remove(&rule->meter_list_node); } + ovs_rwlock_unlock(&rule->evict); } static void @@ -5488,15 +5600,13 @@ oftable_remove_rule(struct rule *rule) ovs_rwlock_unlock(&table->cls.rwlock); } -/* Inserts 'rule' into its oftable. Removes any existing rule from 'rule''s - * oftable that has an identical cls_rule. Returns the rule that was removed, - * if any, and otherwise NULL. */ -static struct rule * -oftable_replace_rule(struct rule *rule) +/* Inserts 'rule' into its oftable, which must not already contain any rule for + * the same cls_rule. */ +static void +oftable_insert_rule(struct rule *rule) { struct ofproto *ofproto = rule->ofproto; struct oftable *table = &ofproto->tables[rule->table_id]; - struct rule *victim; bool may_expire; ovs_mutex_lock(&rule->timeout_mutex); @@ -5514,35 +5624,9 @@ oftable_replace_rule(struct rule *rule) list_insert(&meter->rules, &rule->meter_list_node); } ovs_rwlock_wrlock(&table->cls.rwlock); - victim = rule_from_cls_rule(classifier_replace(&table->cls, &rule->cr)); + classifier_insert(&table->cls, &rule->cr); ovs_rwlock_unlock(&table->cls.rwlock); - if (victim) { - if (victim->meter_id) { - list_remove(&victim->meter_list_node); - } - cookies_remove(ofproto, victim); - - ovs_mutex_lock(&ofproto->expirable_mutex); - if (!list_is_empty(&victim->expirable)) { - list_remove(&victim->expirable); - } - ovs_mutex_unlock(&ofproto->expirable_mutex); - eviction_group_remove_rule(victim); - } eviction_group_add_rule(rule); - return victim; -} - -/* Removes 'old' from its oftable then, if 'new' is nonnull, inserts 'new'. */ -static void -oftable_substitute_rule(struct rule *old, struct rule *new) -{ - if (new) { - oftable_replace_rule(new); - } else { - ovs_rwlock_wrlock(&old->evict); - oftable_remove_rule(old); - } } /* unixctl commands. */