X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fclassifier.c;h=558714145c10765d967404d037fa8dca245925c2;hb=57ad4e9ed5da05b81eba8a7dd3e712201792002d;hp=719260226d338b6d9282fe46e6e31ac220056f20;hpb=cb22974d773942d66da42b700b8bca0db27a0920;p=sliver-openvswitch.git diff --git a/lib/classifier.c b/lib/classifier.c index 719260226..558714145 100644 --- a/lib/classifier.c +++ b/lib/classifier.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc. + * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,19 +25,28 @@ #include "odp-util.h" #include "ofp-util.h" #include "packets.h" +#include "ovs-thread.h" -static struct cls_table *find_table(const struct classifier *, - const struct minimask *); -static struct cls_table *insert_table(struct classifier *, - const struct minimask *); +static struct cls_subtable *find_subtable(const struct classifier *, + const struct minimask *); +static struct cls_subtable *insert_subtable(struct classifier *, + const struct minimask *); -static void destroy_table(struct classifier *, struct cls_table *); +static void destroy_subtable(struct classifier *, struct cls_subtable *); -static struct cls_rule *find_match(const struct cls_table *, +static void update_subtables_after_insertion(struct classifier *, + struct cls_subtable *, + unsigned int new_priority); +static void update_subtables_after_removal(struct classifier *, + struct cls_subtable *, + unsigned int del_priority); + +static struct cls_rule *find_match(const struct cls_subtable *, const struct flow *); -static struct cls_rule *find_equal(struct cls_table *, +static struct cls_rule *find_equal(struct cls_subtable *, const struct miniflow *, uint32_t hash); -static struct cls_rule *insert_rule(struct cls_table *, struct cls_rule *); +static struct cls_rule *insert_rule(struct classifier *, + struct cls_subtable *, struct cls_rule *); /* Iterates RULE over HEAD and all of the cls_rules on HEAD->list. */ #define FOR_EACH_RULE_IN_LIST(RULE, HEAD) \ @@ -80,7 +89,7 @@ cls_rule_init_from_minimatch(struct cls_rule *rule, /* Initializes 'dst' as a copy of 'src'. * - * The caller must eventually destroy 'rule' with cls_rule_destroy(). */ + * The caller must eventually destroy 'dst' with cls_rule_destroy(). */ void cls_rule_clone(struct cls_rule *dst, const struct cls_rule *src) { @@ -88,6 +97,16 @@ cls_rule_clone(struct cls_rule *dst, const struct cls_rule *src) dst->priority = src->priority; } +/* Initializes 'dst' with the data in 'src', destroying 'src'. + * + * The caller must eventually destroy 'dst' with cls_rule_destroy(). */ +void +cls_rule_move(struct cls_rule *dst, struct cls_rule *src) +{ + minimatch_move(&dst->match, &src->match); + dst->priority = src->priority; +} + /* Frees memory referenced by 'rule'. Doesn't free 'rule' itself (it's * normally embedded into a larger structure). * @@ -133,7 +152,10 @@ void classifier_init(struct classifier *cls) { cls->n_rules = 0; - hmap_init(&cls->tables); + hmap_init(&cls->subtables); + list_init(&cls->subtables_priority); + hmap_init(&cls->partitions); + ovs_rwlock_init(&cls->rwlock); } /* Destroys 'cls'. Rules within 'cls', if any, are not freed; this is the @@ -142,12 +164,22 @@ void classifier_destroy(struct classifier *cls) { if (cls) { - struct cls_table *table, *next_table; + struct cls_subtable *partition, *next_partition; + struct cls_subtable *subtable, *next_subtable; + + HMAP_FOR_EACH_SAFE (subtable, next_subtable, hmap_node, + &cls->subtables) { + destroy_subtable(cls, subtable); + } + hmap_destroy(&cls->subtables); - HMAP_FOR_EACH_SAFE (table, next_table, hmap_node, &cls->tables) { - destroy_table(cls, table); + HMAP_FOR_EACH_SAFE (partition, next_partition, hmap_node, + &cls->partitions) { + hmap_remove(&cls->partitions, &partition->hmap_node); + free(partition); } - hmap_destroy(&cls->tables); + hmap_destroy(&cls->partitions); + ovs_rwlock_destroy(&cls->rwlock); } } @@ -165,6 +197,44 @@ classifier_count(const struct classifier *cls) return cls->n_rules; } +static uint32_t +hash_metadata(ovs_be64 metadata_) +{ + uint64_t metadata = (OVS_FORCE uint64_t) metadata_; + return hash_2words(metadata, metadata >> 32); +} + +static struct cls_partition * +find_partition(const struct classifier *cls, ovs_be64 metadata, uint32_t hash) +{ + struct cls_partition *partition; + + HMAP_FOR_EACH_IN_BUCKET (partition, hmap_node, hash, &cls->partitions) { + if (partition->metadata == metadata) { + return partition; + } + } + + return NULL; +} + +static struct cls_partition * +create_partition(struct classifier *cls, struct cls_subtable *subtable, + ovs_be64 metadata) +{ + uint32_t hash = hash_metadata(metadata); + struct cls_partition *partition = find_partition(cls, metadata, hash); + if (!partition) { + partition = xmalloc(sizeof *partition); + partition->metadata = metadata; + partition->tags = 0; + tag_tracker_init(&partition->tracker); + hmap_insert(&cls->partitions, &partition->hmap_node, hash); + } + tag_tracker_add(&partition->tracker, &partition->tags, subtable->tag); + return partition; +} + /* Inserts 'rule' into 'cls'. Until 'rule' is removed from 'cls', the caller * must not modify or free it. * @@ -182,17 +252,26 @@ struct cls_rule * classifier_replace(struct classifier *cls, struct cls_rule *rule) { struct cls_rule *old_rule; - struct cls_table *table; + struct cls_subtable *subtable; - table = find_table(cls, &rule->match.mask); - if (!table) { - table = insert_table(cls, &rule->match.mask); + subtable = find_subtable(cls, &rule->match.mask); + if (!subtable) { + subtable = insert_subtable(cls, &rule->match.mask); } - old_rule = insert_rule(table, rule); + old_rule = insert_rule(cls, subtable, rule); if (!old_rule) { - table->n_table_rules++; + if (minimask_get_metadata_mask(&rule->match.mask) == OVS_BE64_MAX) { + ovs_be64 metadata = miniflow_get_metadata(&rule->match.flow); + rule->partition = create_partition(cls, subtable, metadata); + } else { + rule->partition = NULL; + } + + subtable->n_rules++; cls->n_rules++; + } else { + rule->partition = old_rule->partition; } return old_rule; } @@ -216,44 +295,117 @@ classifier_insert(struct classifier *cls, struct cls_rule *rule) void classifier_remove(struct classifier *cls, struct cls_rule *rule) { + struct cls_partition *partition; struct cls_rule *head; - struct cls_table *table; + struct cls_subtable *subtable; - table = find_table(cls, &rule->match.mask); - head = find_equal(table, &rule->match.flow, rule->hmap_node.hash); + subtable = find_subtable(cls, &rule->match.mask); + head = find_equal(subtable, &rule->match.flow, rule->hmap_node.hash); if (head != rule) { list_remove(&rule->list); } else if (list_is_empty(&rule->list)) { - hmap_remove(&table->rules, &rule->hmap_node); + hmap_remove(&subtable->rules, &rule->hmap_node); } else { struct cls_rule *next = CONTAINER_OF(rule->list.next, struct cls_rule, list); list_remove(&rule->list); - hmap_replace(&table->rules, &rule->hmap_node, &next->hmap_node); + hmap_replace(&subtable->rules, &rule->hmap_node, &next->hmap_node); } - if (--table->n_table_rules == 0) { - destroy_table(cls, table); + partition = rule->partition; + if (partition) { + tag_tracker_subtract(&partition->tracker, &partition->tags, + subtable->tag); + if (!partition->tags) { + hmap_remove(&cls->partitions, &partition->hmap_node); + free(partition); + } } + if (--subtable->n_rules == 0) { + destroy_subtable(cls, subtable); + } else { + update_subtables_after_removal(cls, subtable, rule->priority); + } cls->n_rules--; } /* Finds and returns the highest-priority rule in 'cls' that matches 'flow'. * Returns a null pointer if no rules in 'cls' match 'flow'. If multiple rules - * of equal priority match 'flow', returns one arbitrarily. */ + * of equal priority match 'flow', returns one arbitrarily. + * + * If a rule is found and 'wc' is non-null, bitwise-OR's 'wc' with the + * set of bits that were significant in the lookup. At some point + * earlier, 'wc' should have been initialized (e.g., by + * flow_wildcards_init_catchall()). */ struct cls_rule * -classifier_lookup(const struct classifier *cls, const struct flow *flow) +classifier_lookup(const struct classifier *cls, const struct flow *flow, + struct flow_wildcards *wc) { - struct cls_table *table; + const struct cls_partition *partition; + struct cls_subtable *subtable; struct cls_rule *best; + tag_type tags; + + /* Determine 'tags' such that, if 'subtable->tag' doesn't intersect them, + * then 'flow' cannot possibly match in 'subtable': + * + * - If flow->metadata maps to a given 'partition', then we can use + * 'tags' for 'partition->tags'. + * + * - If flow->metadata has no partition, then no rule in 'cls' has an + * exact-match for flow->metadata. That means that we don't need to + * search any subtable that includes flow->metadata in its mask. + * + * In either case, we always need to search any cls_subtables that do not + * include flow->metadata in its mask. One way to do that would be to + * check the "cls_subtable"s explicitly for that, but that would require an + * extra branch per subtable. Instead, we mark such a cls_subtable's + * 'tags' as TAG_ALL and make sure that 'tags' is never empty. This means + * that 'tags' always intersects such a cls_subtable's 'tags', so we don't + * need a special case. + */ + partition = (hmap_is_empty(&cls->partitions) + ? NULL + : find_partition(cls, flow->metadata, + hash_metadata(flow->metadata))); + tags = partition ? partition->tags : TAG_ARBITRARY; best = NULL; - HMAP_FOR_EACH (table, hmap_node, &cls->tables) { - struct cls_rule *rule = find_match(table, flow); - if (rule && (!best || rule->priority > best->priority)) { + LIST_FOR_EACH (subtable, list_node, &cls->subtables_priority) { + struct cls_rule *rule; + + if (!tag_intersects(tags, subtable->tag)) { + continue; + } + + rule = find_match(subtable, flow); + if (wc) { + flow_wildcards_fold_minimask(wc, &subtable->mask); + } + if (rule) { best = rule; + LIST_FOR_EACH_CONTINUE (subtable, list_node, + &cls->subtables_priority) { + if (subtable->max_priority <= best->priority) { + /* Subtables are in descending priority order, + * can not find anything better. */ + return best; + } + if (!tag_intersects(tags, subtable->tag)) { + continue; + } + + rule = find_match(subtable, flow); + if (wc) { + flow_wildcards_fold_minimask(wc, &subtable->mask); + } + if (rule && rule->priority > best->priority) { + best = rule; + } + } + break; } } return best; @@ -267,14 +419,19 @@ classifier_find_rule_exactly(const struct classifier *cls, const struct cls_rule *target) { struct cls_rule *head, *rule; - struct cls_table *table; + struct cls_subtable *subtable; + + subtable = find_subtable(cls, &target->match.mask); + if (!subtable) { + return NULL; + } - table = find_table(cls, &target->match.mask); - if (!table) { + /* Skip if there is no hope. */ + if (target->priority > subtable->max_priority) { return NULL; } - head = find_equal(table, &target->match.flow, + head = find_equal(subtable, &target->match.flow, miniflow_hash_in_minimask(&target->match.flow, &target->match.mask, 0)); FOR_EACH_RULE_IN_LIST (rule, head) { @@ -310,18 +467,26 @@ bool classifier_rule_overlaps(const struct classifier *cls, const struct cls_rule *target) { - struct cls_table *table; + struct cls_subtable *subtable; - HMAP_FOR_EACH (table, hmap_node, &cls->tables) { + /* Iterate subtables in the descending max priority order. */ + LIST_FOR_EACH (subtable, list_node, &cls->subtables_priority) { uint32_t storage[FLOW_U32S]; struct minimask mask; struct cls_rule *head; - minimask_combine(&mask, &target->match.mask, &table->mask, storage); - HMAP_FOR_EACH (head, hmap_node, &table->rules) { + if (target->priority > subtable->max_priority) { + break; /* Can skip this and the rest of the subtables. */ + } + + minimask_combine(&mask, &target->match.mask, &subtable->mask, storage); + HMAP_FOR_EACH (head, hmap_node, &subtable->rules) { struct cls_rule *rule; FOR_EACH_RULE_IN_LIST (rule, head) { + if (rule->priority < target->priority) { + break; /* Rules in descending priority order. */ + } if (rule->priority == target->priority && miniflow_equal_in_minimask(&target->match.flow, &rule->match.flow, &mask)) { @@ -388,12 +553,13 @@ rule_matches(const struct cls_rule *rule, const struct cls_rule *target) } static struct cls_rule * -search_table(const struct cls_table *table, const struct cls_rule *target) +search_subtable(const struct cls_subtable *subtable, + const struct cls_rule *target) { - if (!target || !minimask_has_extra(&table->mask, &target->match.mask)) { + if (!target || !minimask_has_extra(&subtable->mask, &target->match.mask)) { struct cls_rule *rule; - HMAP_FOR_EACH (rule, hmap_node, &table->rules) { + HMAP_FOR_EACH (rule, hmap_node, &subtable->rules) { if (rule_matches(rule, target)) { return rule; } @@ -423,12 +589,12 @@ cls_cursor_init(struct cls_cursor *cursor, const struct classifier *cls, struct cls_rule * cls_cursor_first(struct cls_cursor *cursor) { - struct cls_table *table; + struct cls_subtable *subtable; - HMAP_FOR_EACH (table, hmap_node, &cursor->cls->tables) { - struct cls_rule *rule = search_table(table, cursor->target); + HMAP_FOR_EACH (subtable, hmap_node, &cursor->cls->subtables) { + struct cls_rule *rule = search_subtable(subtable, cursor->target); if (rule) { - cursor->table = table; + cursor->subtable = subtable; return rule; } } @@ -439,9 +605,10 @@ cls_cursor_first(struct cls_cursor *cursor) /* Returns the next matching cls_rule in 'cursor''s iteration, or a null * pointer if there are no more matches. */ struct cls_rule * -cls_cursor_next(struct cls_cursor *cursor, struct cls_rule *rule) +cls_cursor_next(struct cls_cursor *cursor, const struct cls_rule *rule_) { - const struct cls_table *table; + struct cls_rule *rule = CONST_CAST(struct cls_rule *, rule_); + const struct cls_subtable *subtable; struct cls_rule *next; next = next_rule_in_list__(rule); @@ -450,20 +617,20 @@ cls_cursor_next(struct cls_cursor *cursor, struct cls_rule *rule) } /* 'next' is the head of the list, that is, the rule that is included in - * the table's hmap. (This is important when the classifier contains rules - * that differ only in priority.) */ + * the subtable's hmap. (This is important when the classifier contains + * rules that differ only in priority.) */ rule = next; - HMAP_FOR_EACH_CONTINUE (rule, hmap_node, &cursor->table->rules) { + HMAP_FOR_EACH_CONTINUE (rule, hmap_node, &cursor->subtable->rules) { if (rule_matches(rule, cursor->target)) { return rule; } } - table = cursor->table; - HMAP_FOR_EACH_CONTINUE (table, hmap_node, &cursor->cls->tables) { - rule = search_table(table, cursor->target); + subtable = cursor->subtable; + HMAP_FOR_EACH_CONTINUE (subtable, hmap_node, &cursor->cls->subtables) { + rule = search_subtable(subtable, cursor->target); if (rule) { - cursor->table = table; + cursor->subtable = subtable; return rule; } } @@ -471,51 +638,150 @@ cls_cursor_next(struct cls_cursor *cursor, struct cls_rule *rule) return NULL; } -static struct cls_table * -find_table(const struct classifier *cls, const struct minimask *mask) +static struct cls_subtable * +find_subtable(const struct classifier *cls, const struct minimask *mask) { - struct cls_table *table; + struct cls_subtable *subtable; - HMAP_FOR_EACH_IN_BUCKET (table, hmap_node, minimask_hash(mask, 0), - &cls->tables) { - if (minimask_equal(mask, &table->mask)) { - return table; + HMAP_FOR_EACH_IN_BUCKET (subtable, hmap_node, minimask_hash(mask, 0), + &cls->subtables) { + if (minimask_equal(mask, &subtable->mask)) { + return subtable; } } return NULL; } -static struct cls_table * -insert_table(struct classifier *cls, const struct minimask *mask) +static struct cls_subtable * +insert_subtable(struct classifier *cls, const struct minimask *mask) { - struct cls_table *table; + uint32_t hash = minimask_hash(mask, 0); + struct cls_subtable *subtable; - table = xzalloc(sizeof *table); - hmap_init(&table->rules); - minimask_clone(&table->mask, mask); - hmap_insert(&cls->tables, &table->hmap_node, minimask_hash(mask, 0)); + subtable = xzalloc(sizeof *subtable); + hmap_init(&subtable->rules); + minimask_clone(&subtable->mask, mask); + hmap_insert(&cls->subtables, &subtable->hmap_node, minimask_hash(mask, 0)); + list_push_back(&cls->subtables_priority, &subtable->list_node); + subtable->tag = (minimask_get_metadata_mask(mask) == OVS_BE64_MAX + ? tag_create_deterministic(hash) + : TAG_ALL); - return table; + return subtable; } static void -destroy_table(struct classifier *cls, struct cls_table *table) +destroy_subtable(struct classifier *cls, struct cls_subtable *subtable) { - minimask_destroy(&table->mask); - hmap_remove(&cls->tables, &table->hmap_node); - hmap_destroy(&table->rules); - free(table); + minimask_destroy(&subtable->mask); + hmap_remove(&cls->subtables, &subtable->hmap_node); + hmap_destroy(&subtable->rules); + list_remove(&subtable->list_node); + free(subtable); +} + +/* This function performs the following updates for 'subtable' in 'cls' + * following the addition of a new rule with priority 'new_priority' to + * 'subtable': + * + * - Update 'subtable->max_priority' and 'subtable->max_count' if necessary. + * + * - Update 'subtable''s position in 'cls->subtables_priority' if necessary. + * + * This function should only be called after adding a new rule, not after + * replacing a rule by an identical one or modifying a rule in-place. */ +static void +update_subtables_after_insertion(struct classifier *cls, + struct cls_subtable *subtable, + unsigned int new_priority) +{ + if (new_priority == subtable->max_priority) { + ++subtable->max_count; + } else if (new_priority > subtable->max_priority) { + struct cls_subtable *iter; + + subtable->max_priority = new_priority; + subtable->max_count = 1; + + /* Possibly move 'subtable' earlier in the priority list. If we break + * out of the loop, then 'subtable' should be moved just after that + * 'iter'. If the loop terminates normally, then 'iter' will be the + * list head and we'll move subtable just after that (e.g. to the front + * of the list). */ + iter = subtable; + LIST_FOR_EACH_REVERSE_CONTINUE (iter, list_node, + &cls->subtables_priority) { + if (iter->max_priority >= subtable->max_priority) { + break; + } + } + + /* Move 'subtable' just after 'iter' (unless it's already there). */ + if (iter->list_node.next != &subtable->list_node) { + list_splice(iter->list_node.next, + &subtable->list_node, subtable->list_node.next); + } + } +} + +/* This function performs the following updates for 'subtable' in 'cls' + * following the deletion of a rule with priority 'del_priority' from + * 'subtable': + * + * - Update 'subtable->max_priority' and 'subtable->max_count' if necessary. + * + * - Update 'subtable''s position in 'cls->subtables_priority' if necessary. + * + * This function should only be called after removing a rule, not after + * replacing a rule by an identical one or modifying a rule in-place. */ +static void +update_subtables_after_removal(struct classifier *cls, + struct cls_subtable *subtable, + unsigned int del_priority) +{ + struct cls_subtable *iter; + + if (del_priority == subtable->max_priority && --subtable->max_count == 0) { + struct cls_rule *head; + + subtable->max_priority = 0; + HMAP_FOR_EACH (head, hmap_node, &subtable->rules) { + if (head->priority > subtable->max_priority) { + subtable->max_priority = head->priority; + subtable->max_count = 1; + } else if (head->priority == subtable->max_priority) { + ++subtable->max_count; + } + } + + /* Possibly move 'subtable' later in the priority list. If we break + * out of the loop, then 'subtable' should be moved just before that + * 'iter'. If the loop terminates normally, then 'iter' will be the + * list head and we'll move subtable just before that (e.g. to the back + * of the list). */ + iter = subtable; + LIST_FOR_EACH_CONTINUE (iter, list_node, &cls->subtables_priority) { + if (iter->max_priority <= subtable->max_priority) { + break; + } + } + + /* Move 'subtable' just before 'iter' (unless it's already there). */ + if (iter->list_node.prev != &subtable->list_node) { + list_splice(&iter->list_node, + &subtable->list_node, subtable->list_node.next); + } + } } static struct cls_rule * -find_match(const struct cls_table *table, const struct flow *flow) +find_match(const struct cls_subtable *subtable, const struct flow *flow) { - uint32_t hash = flow_hash_in_minimask(flow, &table->mask, 0); + uint32_t hash = flow_hash_in_minimask(flow, &subtable->mask, 0); struct cls_rule *rule; - HMAP_FOR_EACH_WITH_HASH (rule, hmap_node, hash, &table->rules) { - if (miniflow_equal_flow_in_minimask(&rule->match.flow, flow, - &table->mask)) { + HMAP_FOR_EACH_WITH_HASH (rule, hmap_node, hash, &subtable->rules) { + if (minimatch_matches_flow(&rule->match, flow)) { return rule; } } @@ -524,11 +790,12 @@ find_match(const struct cls_table *table, const struct flow *flow) } static struct cls_rule * -find_equal(struct cls_table *table, const struct miniflow *flow, uint32_t hash) +find_equal(struct cls_subtable *subtable, const struct miniflow *flow, + uint32_t hash) { struct cls_rule *head; - HMAP_FOR_EACH_WITH_HASH (head, hmap_node, hash, &table->rules) { + HMAP_FOR_EACH_WITH_HASH (head, hmap_node, hash, &subtable->rules) { if (miniflow_equal(&head->match.flow, flow)) { return head; } @@ -537,18 +804,20 @@ find_equal(struct cls_table *table, const struct miniflow *flow, uint32_t hash) } static struct cls_rule * -insert_rule(struct cls_table *table, struct cls_rule *new) +insert_rule(struct classifier *cls, struct cls_subtable *subtable, + struct cls_rule *new) { struct cls_rule *head; + struct cls_rule *old = NULL; new->hmap_node.hash = miniflow_hash_in_minimask(&new->match.flow, &new->match.mask, 0); - head = find_equal(table, &new->match.flow, new->hmap_node.hash); + head = find_equal(subtable, &new->match.flow, new->hmap_node.hash); if (!head) { - hmap_insert(&table->rules, &new->hmap_node, new->hmap_node.hash); + hmap_insert(&subtable->rules, &new->hmap_node, new->hmap_node.hash); list_init(&new->list); - return NULL; + goto out; } else { /* Scan the list for the insertion point that will keep the list in * order of decreasing priority. */ @@ -557,24 +826,30 @@ insert_rule(struct cls_table *table, struct cls_rule *new) if (new->priority >= rule->priority) { if (rule == head) { /* 'new' is the new highest-priority flow in the list. */ - hmap_replace(&table->rules, + hmap_replace(&subtable->rules, &rule->hmap_node, &new->hmap_node); } if (new->priority == rule->priority) { list_replace(&new->list, &rule->list); - return rule; + old = rule; + goto out; } else { list_insert(&rule->list, &new->list); - return NULL; + goto out; } } } /* Insert 'new' at the end of the list. */ list_push_back(&head->list, &new->list); - return NULL; } + + out: + if (!old) { + update_subtables_after_insertion(cls, subtable, new->priority); + } + return old; } static struct cls_rule *