[ovs-dev] [PATCH v3 2/2] [RFC] classifier: Add support for conjunctive matches.

Ben Pfaff blp at nicira.com
Fri Jan 9 19:42:54 UTC 2015


A "conjunctive match" allows higher-level matches in the flow table, such
as set membership matches, without causing a cross-product explosion for
multidimensional matches.  Please refer to the documentation that this
commit adds to ovs-ofctl(8) for a better explanation, including an example.

Signed-off-by: Ben Pfaff <blp at nicira.com>
---
v1->v2:
  - Use 1-based dimension numbers in formatted syntax, e.g. 1/2 and 2/2,
    not 0/2 and 1/2.
  - Add new conj_id field instead of overwriting reg0.
  - Since priority is now an 'int', get rid of awkward +1s and comparisons
    on priorities in classifier_lookup().
  - Fix memory leak in classifier_replace().
  - Modify conj_id in-place in classifier_lookup() instead of copying
    entire flow.
  - Remove prototype for nonexistent cls_rule_init_conjunction().
  - Fix memory leak in classifier_lookup(), and eliminate memory allocation
    in the common case of few conjunctive matches.

v2->v2.1:
  - Rebase.

v2.1->v3:
  - Nontrivial rebase due to upstream changes.
  - Fix race condition modifying rules to add or remove conjunctions.
  - Fix race condition adding rules with conjunctions.
  - Document where one should specify extra constraints.
  - Add some tests.
---
 NEWS                         |    4 +
 lib/classifier-private.h     |    3 +-
 lib/classifier.c             |  419 +++++++++++++++++++++++++++++++++++++++---
 lib/classifier.h             |   19 +-
 lib/flow.c                   |   19 +-
 lib/flow.h                   |   11 +-
 lib/match.c                  |   15 +-
 lib/match.h                  |    3 +-
 lib/meta-flow.c              |   17 ++
 lib/meta-flow.h              |   14 ++
 lib/nx-match.c               |    8 +-
 lib/odp-util.h               |    4 +-
 lib/ofp-actions.c            |  112 ++++++++++-
 lib/ofp-actions.h            |   12 ++
 lib/ofp-errors.h             |    8 +
 lib/ofp-util.c               |    4 +-
 lib/ovs-router.c             |    4 +-
 lib/tnl-ports.c              |    4 +-
 ofproto/ofproto-dpif-xlate.c |    8 +-
 ofproto/ofproto.c            |   90 ++++++++-
 tests/automake.mk            |    2 +
 tests/classifier.at          |  158 ++++++++++++++++
 tests/ofproto.at             |    3 +-
 tests/test-classifier.c      |   16 +-
 tests/test-conjunction       |   22 +++
 utilities/ovs-ofctl.8.in     |  205 +++++++++++++++++++++
 utilities/ovs-ofctl.c        |    4 +-
 27 files changed, 1112 insertions(+), 76 deletions(-)
 create mode 100755 tests/test-conjunction

diff --git a/NEWS b/NEWS
index 4f350dc..228fec3 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,9 @@
 Post-v2.3.0
 ---------------------
+   - New support for a "conjunctive match" OpenFlow extension, which
+     allows constructing OpenFlow matches of the form "field1 in
+     {a,b,c...} AND field2 in {d,e,f...}" and generalizations.  For details,
+     see documentation fo the "conjunction" action in ovs-ofctl(8).
    - Add bash command-line completion support for ovs-appctl/ovs-dpctl/
      ovs-ofctl/ovsdb-tool commands.  Please check
      utilities/ovs-command-compgen.INSTALL.md for how to use.
diff --git a/lib/classifier-private.h b/lib/classifier-private.h
index cd64fed..4eed9e4 100644
--- a/lib/classifier-private.h
+++ b/lib/classifier-private.h
@@ -68,7 +68,7 @@ struct cls_partition {
 /* Internal representation of a rule in a "struct cls_subtable". */
 struct cls_match {
     /* Accessed by everybody. */
-    struct rculist list; /* Identical, lower-priority rules. */
+    struct rculist list; /* Identical, lower-priority "cls_match"es. */
 
     /* Accessed only by writers. */
     struct cls_partition *partition;
@@ -80,6 +80,7 @@ struct cls_match {
     /* Accessed by all readers. */
     struct cmap_node cmap_node; /* Within struct cls_subtable 'rules'. */
     const struct cls_rule *cls_rule;
+    OVSRCU_TYPE(struct cls_conjunction_set *) conj_set;
     const struct miniflow flow; /* Matching rule. Mask is in the subtable. */
     /* 'flow' must be the last field. */
 };
diff --git a/lib/classifier.c b/lib/classifier.c
index 814493e..43df4f6 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,14 +31,64 @@ VLOG_DEFINE_THIS_MODULE(classifier);
 
 struct trie_ctx;
 
+/* A collection of "struct cls_conjunction"s currently embedded into a
+ * cls_match. */
+struct cls_conjunction_set {
+    /* Link back to the cls_match.
+     *
+     * cls_conjunction_set is mostly used during classifier lookup, and, in
+     * turn, during classifier lookup the most used member of
+     * cls_conjunction_set is the rule's priority, so we cache it here for fast
+     * access. */
+    struct cls_match *match;
+    int priority;               /* Cached copy of match->priority. */
+
+    /* Conjunction information.
+     *
+     * 'min_n_clauses' allows some optimization during classifier lookup. */
+    unsigned int n;             /* Number of elements in 'conj'. */
+    unsigned int min_n_clauses; /* Smallest 'n' among elements of 'conj'. */
+    struct cls_conjunction conj[];
+};
+
 /* Ports trie depends on both ports sharing the same ovs_be32. */
 #define TP_PORTS_OFS32 (offsetof(struct flow, tp_src) / 4)
 BUILD_ASSERT_DECL(TP_PORTS_OFS32 == offsetof(struct flow, tp_dst) / 4);
 BUILD_ASSERT_DECL(TP_PORTS_OFS32 % 2 == 0);
 #define TP_PORTS_OFS64 (TP_PORTS_OFS32 / 2)
 
+static size_t
+cls_conjunction_set_size(size_t n)
+{
+    return (sizeof(struct cls_conjunction_set)
+            + n * sizeof(struct cls_conjunction));
+}
+
+static struct cls_conjunction_set *
+cls_conjunction_set_alloc(struct cls_match *match,
+                          const struct cls_conjunction conj[], size_t n)
+{
+    if (n) {
+        size_t min_n_clauses = conj[0].n_clauses;
+        for (size_t i = 1; i < n; i++) {
+            min_n_clauses = MIN(min_n_clauses, conj[i].n_clauses);
+        }
+
+        struct cls_conjunction_set *set = xmalloc(cls_conjunction_set_size(n));
+        set->match = match;
+        set->priority = match->priority;
+        set->n = n;
+        set->min_n_clauses = min_n_clauses;
+        memcpy(set->conj, conj, n * sizeof *conj);
+        return set;
+    } else {
+        return NULL;
+    }
+}
+
 static struct cls_match *
-cls_match_alloc(const struct cls_rule *rule)
+cls_match_alloc(const struct cls_rule *rule,
+                const struct cls_conjunction conj[], size_t n)
 {
     int count = count_1bits(rule->match.flow.map);
 
@@ -51,6 +101,8 @@ cls_match_alloc(const struct cls_rule *rule)
     *CONST_CAST(int *, &cls_match->priority) = rule->priority;
     miniflow_clone_inline(CONST_CAST(struct miniflow *, &cls_match->flow),
                           &rule->match.flow, count);
+    ovsrcu_set_hidden(&cls_match->conj_set,
+                      cls_conjunction_set_alloc(cls_match, conj, n));
 
     return cls_match;
 }
@@ -201,6 +253,26 @@ cls_rule_destroy(struct cls_rule *rule)
     minimatch_destroy(&rule->match);
 }
 
+void
+cls_rule_set_conjunctions(struct cls_rule *cr,
+                          const struct cls_conjunction *conj, size_t n)
+{
+    struct cls_match *match = cr->cls_match;
+    struct cls_conjunction_set *old
+        = ovsrcu_get_protected(struct cls_conjunction_set *, &match->conj_set);
+    struct cls_conjunction *old_conj = old ? old->conj : NULL;
+    unsigned int old_n = old ? old->n : 0;
+
+    if (old_n != n || (n && memcmp(old_conj, conj, n * sizeof *conj))) {
+        if (old) {
+            ovsrcu_postpone(free, old);
+        }
+        ovsrcu_set(&match->conj_set,
+                   cls_conjunction_set_alloc(match, conj, n));
+    }
+}
+
+
 /* Returns true if 'a' and 'b' match the same packets at the same priority,
  * false if they differ in some way. */
 bool
@@ -487,9 +559,10 @@ subtable_replace_head_rule(struct classifier *cls OVS_UNUSED,
  * superset of their flows and has higher priority.
  */
 const struct cls_rule *
-classifier_replace(struct classifier *cls, const struct cls_rule *rule)
+classifier_replace(struct classifier *cls, const struct cls_rule *rule,
+                   const struct cls_conjunction *conjs, size_t n_conjs)
 {
-    struct cls_match *new = cls_match_alloc(rule);
+    struct cls_match *new = cls_match_alloc(rule, conjs, n_conjs);
     struct cls_subtable *subtable;
     uint32_t ihash[CLS_MAX_INDICES];
     uint8_t prev_be64ofs = 0;
@@ -594,6 +667,14 @@ classifier_replace(struct classifier *cls, const struct cls_rule *rule)
             }
 
             if (old) {
+                struct cls_conjunction_set *conj_set;
+
+                conj_set = ovsrcu_get_protected(struct cls_conjunction_set *,
+                                                &iter->conj_set);
+                if (conj_set) {
+                    ovsrcu_postpone(free, conj_set);
+                }
+
                 ovsrcu_postpone(free, iter);
                 old->cls_match = NULL;
 
@@ -651,9 +732,11 @@ classifier_replace(struct classifier *cls, const struct cls_rule *rule)
  * fixed fields, and priority).  Use classifier_find_rule_exactly() to find
  * such a rule. */
 void
-classifier_insert(struct classifier *cls, const struct cls_rule *rule)
+classifier_insert(struct classifier *cls, const struct cls_rule *rule,
+                  const struct cls_conjunction conj[], size_t n_conj)
 {
-    const struct cls_rule *displaced_rule = classifier_replace(cls, rule);
+    const struct cls_rule *displaced_rule
+        = classifier_replace(cls, rule, conj, n_conj);
     ovs_assert(!displaced_rule);
 }
 
@@ -670,6 +753,7 @@ classifier_remove(struct classifier *cls, const struct cls_rule *rule)
 {
     struct cls_partition *partition;
     struct cls_match *cls_match;
+    struct cls_conjunction_set *conj_set;
     struct cls_subtable *subtable;
     struct cls_match *prev;
     struct cls_match *next;
@@ -780,6 +864,11 @@ check_priority:
     }
 
 free:
+    conj_set = ovsrcu_get_protected(struct cls_conjunction_set *,
+                                    &cls_match->conj_set);
+    if (conj_set) {
+        ovsrcu_postpone(free, conj_set);
+    }
     ovsrcu_postpone(free, cls_match);
     cls->n_rules--;
 
@@ -809,27 +898,107 @@ trie_ctx_init(struct trie_ctx *ctx, const struct cls_trie *trie)
     ctx->lookup_done = false;
 }
 
-/* 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.
- *
- * 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 conjunctive_match {
+    struct hmap_node hmap_node;
+    uint32_t id;
+    uint64_t clauses;
+};
+
+static struct conjunctive_match *
+find_conjunctive_match__(struct hmap *matches, uint64_t id, uint32_t hash)
+{
+    struct conjunctive_match *m;
+
+    HMAP_FOR_EACH_IN_BUCKET (m, hmap_node, hash, matches) {
+        if (m->id == id) {
+            return m;
+        }
+    }
+    return NULL;
+}
+
+static bool
+find_conjunctive_match(const struct cls_conjunction_set *set,
+                       unsigned int max_n_clauses, struct hmap *matches,
+                       struct conjunctive_match *cm_stubs, size_t n_cm_stubs,
+                       uint32_t *idp)
+{
+    const struct cls_conjunction *c;
+
+    if (max_n_clauses < set->min_n_clauses) {
+        return false;
+    }
+
+    for (c = set->conj; c < &set->conj[set->n]; c++) {
+        uint32_t hash = hash_int(c->id, 0);
+        struct conjunctive_match *cm;
+
+        if (c->n_clauses > max_n_clauses) {
+            continue;
+        }
+
+        cm = find_conjunctive_match__(matches, c->id, hash);
+        if (!cm) {
+            size_t n = hmap_count(matches);
+            cm = n < n_cm_stubs ? &cm_stubs[n++] : xmalloc(sizeof *cm);
+            hmap_insert(matches, &cm->hmap_node, hash);
+            cm->id = c->id;
+            cm->clauses = UINT64_MAX << (c->n_clauses & 63);
+        }
+        cm->clauses |= UINT64_C(1) << c->clause;
+        if (cm->clauses == UINT64_MAX) {
+            *idp = cm->id;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+free_conjunctive_matches(struct hmap *matches,
+                         struct conjunctive_match *cm_stubs, size_t n_cm_stubs)
+{
+    if (hmap_count(matches) > n_cm_stubs) {
+        struct conjunctive_match *cm, *next;
+
+        HMAP_FOR_EACH_SAFE (cm, next, hmap_node, matches) {
+            if (!(cm >= cm_stubs && cm < &cm_stubs[n_cm_stubs])) {
+                hmap_remove(matches, &cm->hmap_node);
+                free(cm);
+            }
+        }
+    }
+    hmap_destroy(matches);
+}
+
+/* Like classifier_lookup(), except that support for conjunctive matches can be
+ * configured with 'allow_conjunctive_matches'.  That feature is not exposed
+ * externally because turning off conjunctive matches is only useful to avoid
+ * recursion within this function itself.
  *
  * 'flow' is non-const to allow for temporary modifications during the lookup.
  * Any changes are restored before returning. */
-const struct cls_rule *
-classifier_lookup(const struct classifier *cls, struct flow *flow,
-                  struct flow_wildcards *wc)
+static const struct cls_rule *
+classifier_lookup__(const struct classifier *cls, struct flow *flow,
+                    struct flow_wildcards *wc, bool allow_conjunctive_matches)
 {
     const struct cls_partition *partition;
-    tag_type tags;
-    int best_priority = INT_MIN;
-    const struct cls_match *best;
     struct trie_ctx trie_ctx[CLS_MAX_TRIES];
-    struct cls_subtable *subtable;
+    const struct cls_match *match;
+    tag_type tags;
+
+    /* Highest-priority flow in 'cls' that certainly matches 'flow'. */
+    const struct cls_match *hard = NULL;
+    int hard_pri = INT_MIN;     /* hard ? hard->priority : INT_MIN. */
+
+    /* Highest-priority conjunctive flows in 'cls' matching 'flow'.  Since
+     * these are (components of) conjunctive flows, we can only know whether
+     * the full conjunctive flow matches after seeing multiple of them.  Thus,
+     * we refer to these as "soft matches". */
+    struct cls_conjunction_set *soft_stub[64];
+    struct cls_conjunction_set **soft = soft_stub;
+    size_t n_soft = 0, allocated_soft = ARRAY_SIZE(soft_stub);
+    int soft_pri = INT_MIN;    /* n_soft ? MAX(soft[*]->priority) : INT_MIN. */
 
     /* Synchronize for cls->n_tries and subtable->trie_plen.  They can change
      * when table configuration changes, which happens typically only on
@@ -865,23 +1034,213 @@ classifier_lookup(const struct classifier *cls, struct flow *flow,
         trie_ctx_init(&trie_ctx[i], &cls->tries[i]);
     }
 
-    best = NULL;
-    PVECTOR_FOR_EACH_PRIORITY(subtable, best_priority, 2,
-                              sizeof(struct cls_subtable), &cls->subtables) {
-        const struct cls_match *rule;
+    /* Main loop. */
+    struct cls_subtable *subtable;
+    PVECTOR_FOR_EACH_PRIORITY (subtable, hard_pri, 2, sizeof *subtable,
+                               &cls->subtables) {
+        struct cls_conjunction_set *conj_set;
 
+        /* Skip subtables not in our partition. */
         if (!tag_intersects(tags, subtable->tag)) {
             continue;
         }
 
-        rule = find_match_wc(subtable, flow, trie_ctx, cls->n_tries, wc);
-        if (rule && rule->priority > best_priority) {
-            best_priority = rule->priority;
-            best = rule;
+        /* Skip subtables with no match, or where the match is lower-priority
+         * than some certain match we've already found. */
+        match = find_match_wc(subtable, flow, trie_ctx, cls->n_tries, wc);
+        if (!match || match->priority <= hard_pri) {
+            continue;
+        }
+
+        conj_set = ovsrcu_get(struct cls_conjunction_set *, &match->conj_set);
+        if (!conj_set) {
+            /* 'match' isn't part of a conjunctive match.  It's the best
+             * certain match we've got so far, since we know that it's
+             * higher-priority than hard_pri.
+             *
+             * (There might be a higher-priority conjunctive match.  We can't
+             * tell yet.) */
+            hard = match;
+            hard_pri = hard->priority;
+        } else if (allow_conjunctive_matches) {
+            /* 'match' is part of a conjunctive match.  Add it to the list. */
+            if (OVS_UNLIKELY(n_soft >= allocated_soft)) {
+                struct cls_conjunction_set **old_soft = soft;
+
+                allocated_soft *= 2;
+                soft = xmalloc(allocated_soft * sizeof *soft);
+                memcpy(soft, old_soft, n_soft * sizeof *soft);
+                if (old_soft != soft_stub) {
+                    free(old_soft);
+                }
+            }
+            soft[n_soft++] = conj_set;
+
+            /* Keep track of the highest-priority soft match. */
+            if (soft_pri < match->priority) {
+                soft_pri = match->priority;
+            }
         }
     }
 
-    return best ? best->cls_rule : NULL;
+    /* In the common case, at this point we have no soft matches and we can
+     * return immediately.  (We do the same thing if we have potential soft
+     * matches but none of them are higher-priority than our hard match.)*/
+    if (hard_pri >= soft_pri) {
+        if (soft != soft_stub) {
+            free(soft);
+        }
+        return hard ? hard->cls_rule : NULL;
+    }
+
+    /* At this point, we have some soft matches.  We might also have a hard
+     * match; if so, its priority is lower than the highest-priority soft
+     * match. */
+
+    /* Soft match loop.
+     *
+     * Check whether soft matches are real matches. */
+    for (;;) {
+        /* Delete soft matches that are null.  This only happens in second and
+         * subsequent iterations of the soft match loop, when we drop back from
+         * a high-priority soft match to a lower-priority one.
+         *
+         * Also, delete soft matches whose priority is less than or equal to
+         * the hard match's priority.  In the first iteration of the soft
+         * match, these can be in 'soft' because the earlier main loop found
+         * the soft match before the hard match.  In second and later iteration
+         * of the soft match loop, these can be in 'soft' because we dropped
+         * back from a high-priority soft match to a lower-priority soft match.
+         *
+         * Also, delete soft matches that cannot be satisfied because there are
+         * fewer soft matches than required to satisfy any of their
+         * conjunctions.  Since deleting soft matches can cause this condition
+         * to become true for new soft matches, we iterate until we've deleted
+         * as many as possible. */
+        bool deleted;
+        do {
+            deleted = false;
+            for (int i = 0; i < n_soft; ) {
+                if (!soft[i]
+                    || soft[i]->priority <= hard_pri
+                    || n_soft < soft[i]->min_n_clauses) {
+                    deleted = true;
+                    soft[i] = soft[--n_soft];
+                } else {
+                    i++;
+                }
+            }
+        } while (deleted);
+        if (n_soft < 2) {
+            break;
+        }
+
+        /* Find the highest priority among the soft matches.  (We know this
+         * must be higher than the hard match's priority; otherwise we would
+         * have deleted all of the soft matches in the previous loop.)  Count
+         * the number of soft matches that have that priority. */
+        soft_pri = INT_MIN;
+        int n_soft_pri = 0;
+        for (int i = 0; i < n_soft; i++) {
+            if (soft[i]->priority > soft_pri) {
+                soft_pri = soft[i]->priority;
+                n_soft_pri = 1;
+            } else if (soft[i]->priority == soft_pri) {
+                n_soft_pri++;
+            }
+        }
+        ovs_assert(soft_pri > hard_pri);
+
+        /* Look for a real match among the highest-priority soft matches.
+         *
+         * It's unusual to have many conjunctive matches, so we use stubs to
+         * avoid calling malloc() in the common case.  An hmap has a built-in
+         * stub for up to 2 hmap_nodes; possibly, we would benefit a variant
+         * with a bigger stub. */
+        struct conjunctive_match cm_stubs[16];
+        struct hmap matches;
+
+        hmap_init(&matches);
+        for (int i = 0; i < n_soft; i++) {
+            uint32_t id;
+
+            if (soft[i]->priority == soft_pri
+                && find_conjunctive_match(soft[i], n_soft_pri, &matches,
+                                          cm_stubs, ARRAY_SIZE(cm_stubs),
+                                          &id)) {
+                uint32_t saved_conj_id = flow->conj_id;
+                const struct cls_rule *rule;
+
+                flow->conj_id = id;
+                rule = classifier_lookup__(cls, flow, wc, false);
+                flow->conj_id = saved_conj_id;
+
+                if (rule) {
+                    free_conjunctive_matches(&matches,
+                                             cm_stubs, ARRAY_SIZE(cm_stubs));
+                    if (soft != soft_stub) {
+                        free(soft);
+                    }
+                    return rule;
+                }
+            }
+        }
+        free_conjunctive_matches(&matches, cm_stubs, ARRAY_SIZE(cm_stubs));
+
+        /* There's no real match among the highest-priority soft matches.
+         * However, if any of those soft matches has a lower-priority but
+         * otherwise identical flow match, then we need to consider those for
+         * soft or hard matches.
+         *
+         * The next iteration of the soft match loop will delete any null
+         * pointers we put into 'soft' (and some others too). */
+        for (int i = 0; i < n_soft; i++) {
+            if (soft[i]->priority != soft_pri) {
+                continue;
+            }
+
+            /* Find next-lower-priority flow with identical flow match. */
+            match = next_rule_in_list(soft[i]->match);
+            if (match) {
+                soft[i] = ovsrcu_get(struct cls_conjunction_set *,
+                                     &match->conj_set);
+                if (!soft[i]) {
+                    /* The flow is a hard match; don't treat as a soft
+                     * match. */
+                    if (match->priority > hard_pri) {
+                        hard = match;
+                        hard_pri = hard->priority;
+                    }
+                }
+            } else {
+                /* No such lower-priority flow (probably the common case). */
+                soft[i] = NULL;
+            }
+        }
+    }
+
+    if (soft != soft_stub) {
+        free(soft);
+    }
+    return hard ? hard->cls_rule : NULL;
+}
+
+/* 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.
+ *
+ * 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()).
+ *
+ * 'flow' is non-const to allow for temporary modifications during the lookup.
+ * Any changes are restored before returning. */
+const struct cls_rule *
+classifier_lookup(const struct classifier *cls, struct flow *flow,
+                  struct flow_wildcards *wc)
+{
+    return classifier_lookup__(cls, flow, wc, true);
 }
 
 /* Finds and returns a rule in 'cls' with exactly the same priority and
diff --git a/lib/classifier.h b/lib/classifier.h
index 9ebc506..f9af33e 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -255,6 +255,12 @@ struct classifier {
     bool publish;                   /* Make changes visible to lookups? */
 };
 
+struct cls_conjunction {
+    uint32_t id;
+    uint8_t clause;
+    uint8_t n_clauses;
+};
+
 /* A rule to be inserted to the classifier. */
 struct cls_rule {
     struct rculist node;         /* In struct cls_subtable 'rules_list'. */
@@ -269,6 +275,10 @@ void cls_rule_init_from_minimatch(struct cls_rule *, const struct minimatch *,
 void cls_rule_clone(struct cls_rule *, const struct cls_rule *);
 void cls_rule_move(struct cls_rule *dst, struct cls_rule *src);
 void cls_rule_destroy(struct cls_rule *);
+
+void cls_rule_set_conjunctions(struct cls_rule *,
+                               const struct cls_conjunction *, size_t n);
+
 bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *);
 uint32_t cls_rule_hash(const struct cls_rule *, uint32_t basis);
 void cls_rule_format(const struct cls_rule *, struct ds *);
@@ -284,9 +294,12 @@ void classifier_destroy(struct classifier *);
 bool classifier_set_prefix_fields(struct classifier *,
                                   const enum mf_field_id *trie_fields,
                                   unsigned int n_trie_fields);
-void classifier_insert(struct classifier *, const struct cls_rule *);
+void classifier_insert(struct classifier *, const struct cls_rule *,
+                       const struct cls_conjunction *, size_t n_conjunctions);
 const struct cls_rule *classifier_replace(struct classifier *,
-                                          const struct cls_rule *);
+                                          const struct cls_rule *,
+                                          const struct cls_conjunction *,
+                                          size_t n_conjunctions);
 const struct cls_rule *classifier_remove(struct classifier *,
                                          const struct cls_rule *);
 static inline void classifier_defer(struct classifier *);
diff --git a/lib/flow.c b/lib/flow.c
index d8a7981..43bb003 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -119,7 +119,7 @@ struct mf_ctx {
  * away.  Some GCC versions gave warnings on ALWAYS_INLINE, so these are
  * defined as macros. */
 
-#if (FLOW_WC_SEQ != 29)
+#if (FLOW_WC_SEQ != 30)
 #define MINIFLOW_ASSERT(X) ovs_assert(X)
 BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
                "assertions enabled. Consider updating FLOW_WC_SEQ after "
@@ -446,7 +446,7 @@ miniflow_extract(struct ofpbuf *packet, const struct pkt_metadata *md,
         miniflow_push_uint32(mf, in_port, odp_to_u32(md->in_port.odp_port));
         if (md->recirc_id) {
             miniflow_push_uint32(mf, recirc_id, md->recirc_id);
-            miniflow_pad_to_64(mf, actset_output);
+            miniflow_pad_to_64(mf, conj_id);
         }
     }
 
@@ -762,7 +762,7 @@ flow_unwildcard_tp_ports(const struct flow *flow, struct flow_wildcards *wc)
 void
 flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     fmd->dp_hash = flow->dp_hash;
     fmd->recirc_id = flow->recirc_id;
@@ -909,7 +909,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
     memset(&wc->masks, 0x0, sizeof wc->masks);
 
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     if (flow->tunnel.ip_dst) {
         if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
@@ -926,7 +926,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
         WC_MASK_FIELD(wc, tunnel.tun_id);
     }
 
-    /* metadata and regs wildcarded. */
+    /* metadata, regs, and conj_id wildcarded. */
 
     WC_MASK_FIELD(wc, skb_priority);
     WC_MASK_FIELD(wc, pkt_mark);
@@ -1006,7 +1006,7 @@ uint64_t
 flow_wc_map(const struct flow *flow)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     uint64_t map = (flow->tunnel.ip_dst) ? MINIFLOW_MAP(tunnel) : 0;
 
@@ -1058,11 +1058,12 @@ void
 flow_wildcards_clear_non_packet_fields(struct flow_wildcards *wc)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     memset(&wc->masks.metadata, 0, sizeof wc->masks.metadata);
     memset(&wc->masks.regs, 0, sizeof wc->masks.regs);
     wc->masks.actset_output = 0;
+    wc->masks.conj_id = 0;
 }
 
 /* Returns true if 'wc' matches every packet, false if 'wc' fixes any bits or
@@ -1616,7 +1617,7 @@ flow_push_mpls(struct flow *flow, int n, ovs_be16 mpls_eth_type,
         flow->mpls_lse[0] = set_mpls_lse_values(ttl, tc, 1, htonl(label));
 
         /* Clear all L3 and L4 fields and dp_hash. */
-        BUILD_ASSERT(FLOW_WC_SEQ == 29);
+        BUILD_ASSERT(FLOW_WC_SEQ == 30);
         memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
                sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
         flow->dp_hash = 0;
diff --git a/lib/flow.h b/lib/flow.h
index 17b9b86..dd989ee 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ struct pkt_metadata;
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 29
+#define FLOW_WC_SEQ 30
 
 /* Number of Open vSwitch extension 32-bit registers. */
 #define FLOW_N_REGS 8
@@ -105,8 +105,9 @@ struct flow {
                                  * computation is opaque to the user space. */
     union flow_in_port in_port; /* Input port.*/
     uint32_t recirc_id;         /* Must be exact match. */
+    uint32_t conj_id;           /* Conjunction ID. */
     ofp_port_t actset_output;   /* Output port in action set. */
-    ovs_be16 pad1;              /* Pad to 64 bits. */
+    uint8_t pad1[6];            /* Pad to 64 bits. */
 
     /* L2, Order the same as in the Ethernet header! (64-bit aligned) */
     uint8_t dl_dst[ETH_ADDR_LEN]; /* Ethernet destination address. */
@@ -154,8 +155,8 @@ BUILD_ASSERT_DECL(sizeof(struct flow) % sizeof(uint64_t) == 0);
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
 BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
-                  == sizeof(struct flow_tnl) + 184
-                  && FLOW_WC_SEQ == 29);
+                  == sizeof(struct flow_tnl) + 192
+                  && FLOW_WC_SEQ == 30);
 
 /* Incremental points at which flow classification may be performed in
  * segments.
diff --git a/lib/match.c b/lib/match.c
index b5bea5d..76ccb43 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -89,6 +89,13 @@ match_set_recirc_id(struct match *match, uint32_t value)
 }
 
 void
+match_set_conj_id(struct match *match, uint32_t value)
+{
+    match->flow.conj_id = value;
+    match->wc.masks.conj_id = UINT32_MAX;
+}
+
+void
 match_set_reg(struct match *match, unsigned int reg_idx, uint32_t value)
 {
     match_set_reg_masked(match, reg_idx, value, UINT32_MAX);
@@ -870,7 +877,7 @@ match_format(const struct match *match, struct ds *s, int priority)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%d,", priority);
@@ -888,6 +895,10 @@ match_format(const struct match *match, struct ds *s, int priority)
                              wc->masks.dp_hash);
     }
 
+    if (wc->masks.conj_id) {
+        ds_put_format(s, "conj_id=%"PRIu32",", f->conj_id);
+    }
+
     if (wc->masks.skb_priority) {
         ds_put_format(s, "skb_priority=%#"PRIx32",", f->skb_priority);
     }
diff --git a/lib/match.h b/lib/match.h
index a245bcf..452b5e7 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -46,7 +46,8 @@ void match_set_dp_hash(struct match *, uint32_t value);
 void match_set_dp_hash_masked(struct match *, uint32_t value, uint32_t mask);
 
 void match_set_recirc_id(struct match *, uint32_t value);
-void match_set_recirc_id_masked(struct match *, uint32_t value, uint32_t mask);
+
+void match_set_conj_id(struct match *, uint32_t value);
 
 void match_set_reg(struct match *, unsigned int reg_idx, uint32_t value);
 void match_set_reg_masked(struct match *, unsigned int reg_idx,
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 90dd27c..67115b2 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -108,6 +108,8 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
         return !wc->masks.dp_hash;
     case MFF_RECIRC_ID:
         return !wc->masks.recirc_id;
+    case MFF_CONJ_ID:
+        return !wc->masks.conj_id;
     case MFF_TUN_SRC:
         return !wc->masks.tunnel.ip_src;
     case MFF_TUN_DST:
@@ -363,6 +365,7 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
     switch (mf->id) {
     case MFF_DP_HASH:
     case MFF_RECIRC_ID:
+    case MFF_CONJ_ID:
     case MFF_TUN_ID:
     case MFF_TUN_SRC:
     case MFF_TUN_DST:
@@ -464,6 +467,9 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
     case MFF_RECIRC_ID:
         value->be32 = htonl(flow->recirc_id);
         break;
+    case MFF_CONJ_ID:
+        value->be32 = htonl(flow->conj_id);
+        break;
     case MFF_TUN_ID:
         value->be64 = flow->tunnel.tun_id;
         break;
@@ -669,6 +675,9 @@ mf_set_value(const struct mf_field *mf,
     case MFF_RECIRC_ID:
         match_set_recirc_id(match, ntohl(value->be32));
         break;
+    case MFF_CONJ_ID:
+        match_set_conj_id(match, ntohl(value->be32));
+        break;
     case MFF_TUN_ID:
         match_set_tun_id(match, value->be64);
         break;
@@ -898,6 +907,9 @@ mf_set_flow_value(const struct mf_field *mf,
     case MFF_RECIRC_ID:
         flow->recirc_id = ntohl(value->be32);
         break;
+    case MFF_CONJ_ID:
+        flow->conj_id = ntohl(value->be32);
+        break;
     case MFF_TUN_ID:
         flow->tunnel.tun_id = value->be64;
         break;
@@ -1152,6 +1164,10 @@ mf_set_wild(const struct mf_field *mf, struct match *match)
         match->flow.recirc_id = 0;
         match->wc.masks.recirc_id = 0;
         break;
+    case MFF_CONJ_ID:
+        match->flow.conj_id = 0;
+        match->wc.masks.conj_id = 0;
+        break;
     case MFF_TUN_ID:
         match_set_tun_id_masked(match, htonll(0), htonll(0));
         break;
@@ -1373,6 +1389,7 @@ mf_set(const struct mf_field *mf,
 
     switch (mf->id) {
     case MFF_RECIRC_ID:
+    case MFF_CONJ_ID:
     case MFF_IN_PORT:
     case MFF_IN_PORT_OXM:
     case MFF_ACTSET_OUTPUT:
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 62e9c79..1ee5c75 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -299,6 +299,20 @@ enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_RECIRC_ID,
 
+    /* "conj_id".
+     *
+     * ID for "conjunction" actions.  Please refer to ovs-ofctl(8)
+     * documentation of "conjunction" for details.
+     *
+     * Type: be32.
+     * Maskable: no.
+     * Formatting: decimal.
+     * Prerequisites: none.
+     * Access: read-only.
+     * NXM: NXM_NX_CONJ_ID(37) since v2.4.
+     * OXM: none. */
+    MFF_CONJ_ID,
+
     /* "tun_id" (aka "tunnel_id").
      *
      * The "key" or "tunnel ID" or "VNI" in a packet received via a keyed
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 1f72a84..114c35b 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -817,7 +817,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     /* Metadata. */
     if (match->wc.masks.dp_hash) {
@@ -829,6 +829,10 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
         nxm_put_32(b, MFF_RECIRC_ID, oxm, htonl(flow->recirc_id));
     }
 
+    if (match->wc.masks.conj_id) {
+        nxm_put_32(b, MFF_CONJ_ID, oxm, htonl(flow->conj_id));
+    }
+
     if (match->wc.masks.in_port.ofp_port) {
         ofp_port_t in_port = flow->in_port.ofp_port;
         if (oxm) {
diff --git a/lib/odp-util.h b/lib/odp-util.h
index b361795..178fa11 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -133,7 +133,7 @@ void odp_portno_names_destroy(struct hmap *portno_names);
  * add another field and forget to adjust this value.
  */
 #define ODPUTIL_FLOW_KEY_BYTES 512
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
 /* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
  * key.  An array of "struct nlattr" might not, in theory, be sufficiently
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 4680d81..e694fd9 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -281,6 +281,9 @@ enum ofp_raw_action_type {
 
     /* NX1.0+(29): struct nx_action_sample. */
     NXAST_RAW_SAMPLE,
+
+    /* NX1.0+(34): struct nx_action_conjunction. */
+    NXAST_RAW_CONJUNCTION,
 };
 
 /* OpenFlow actions are always a multiple of 8 bytes in length. */
@@ -3898,6 +3901,89 @@ format_LEARN(const struct ofpact_learn *a, struct ds *s)
     learn_format(a, s);
 }
 
+/* Action structure for NXAST_CONJUNCTION. */
+struct nx_action_conjunction {
+    ovs_be16 type;                  /* OFPAT_VENDOR. */
+    ovs_be16 len;                   /* At least 16. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* See enum ofp_raw_action_type. */
+    uint8_t clause;
+    uint8_t n_clauses;
+    ovs_be32 id;
+};
+OFP_ASSERT(sizeof(struct nx_action_conjunction) == 16);
+
+static void
+add_conjunction(struct ofpbuf *out,
+                uint32_t id, uint8_t clause, uint8_t n_clauses)
+{
+    struct ofpact_conjunction *oc;
+
+    oc = ofpact_put_CONJUNCTION(out);
+    oc->id = id;
+    oc->clause = clause;
+    oc->n_clauses = n_clauses;
+}
+
+static enum ofperr
+decode_NXAST_RAW_CONJUNCTION(const struct nx_action_conjunction *nac,
+                             struct ofpbuf *out)
+{
+    if (nac->n_clauses < 2 || nac->n_clauses > 64
+        || nac->clause >= nac->n_clauses) {
+        return OFPERR_NXBAC_BAD_CONJUNCTION;
+    } else {
+        add_conjunction(out, ntohl(nac->id), nac->clause, nac->n_clauses);
+        return 0;
+    }
+}
+
+static void
+encode_CONJUNCTION(const struct ofpact_conjunction *oc,
+                   enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+    struct nx_action_conjunction *nac = put_NXAST_CONJUNCTION(out);
+    nac->clause = oc->clause;
+    nac->n_clauses = oc->n_clauses;
+    nac->id = htonl(oc->id);
+}
+
+static void
+format_CONJUNCTION(const struct ofpact_conjunction *oc, struct ds *s)
+{
+    ds_put_format(s, "conjunction(%"PRIu32",%"PRIu8"/%"PRIu8")",
+                  oc->id, oc->clause + 1, oc->n_clauses);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_CONJUNCTION(const char *arg, struct ofpbuf *ofpacts,
+                  enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    uint8_t n_clauses;
+    uint8_t clause;
+    uint32_t id;
+    int n;
+
+    if (!ovs_scan(arg, "%"SCNi32" , %"SCNu8" / %"SCNu8" %n",
+                  &id, &clause, &n_clauses, &n) || n != strlen(arg)) {
+        return xstrdup("\"conjunction\" syntax is \"conjunction(id,i/n)\"");
+    }
+
+    if (n_clauses < 2) {
+        return xstrdup("conjunction must have at least 2 clauses");
+    } else if (n_clauses > 64) {
+        return xstrdup("conjunction must have at most 64 clauses");
+    } else if (clause < 1) {
+        return xstrdup("clause index must be positive");
+    } else if (clause > n_clauses) {
+        return xstrdup("clause index must be less than or equal to "
+                       "number of clauses");
+    }
+
+    add_conjunction(ofpacts, id, clause - 1, n_clauses);
+    return NULL;
+}
+
 /* Action structure for NXAST_MULTIPATH.
  *
  * This action performs the following steps in sequence:
@@ -4644,6 +4730,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
     case OFPACT_GOTO_TABLE:
     case OFPACT_GROUP:
     case OFPACT_LEARN:
+    case OFPACT_CONJUNCTION:
     case OFPACT_METER:
     case OFPACT_MULTIPATH:
     case OFPACT_NOTE:
@@ -4710,6 +4797,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
     case OFPACT_EXIT:
     case OFPACT_FIN_TIMEOUT:
     case OFPACT_LEARN:
+    case OFPACT_CONJUNCTION:
     case OFPACT_MULTIPATH:
     case OFPACT_NOTE:
     case OFPACT_OUTPUT_REG:
@@ -4925,6 +5013,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_FIN_TIMEOUT:
     case OFPACT_RESUBMIT:
     case OFPACT_LEARN:
+    case OFPACT_CONJUNCTION:
     case OFPACT_MULTIPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
@@ -5455,6 +5544,9 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_LEARN:
         return learn_check(ofpact_get_LEARN(a), flow);
 
+    case OFPACT_CONJUNCTION:
+        return 0;
+
     case OFPACT_MULTIPATH:
         return multipath_check(ofpact_get_MULTIPATH(a), flow);
 
@@ -5576,8 +5668,12 @@ ofpacts_check_consistency(struct ofpact ofpacts[], size_t ofpacts_len,
             : 0);
 }
 
-/* Verifies that the 'ofpacts_len' bytes of actions in 'ofpacts' are
- * in the appropriate order as defined by the OpenFlow spec. */
+/* Verifies that the 'ofpacts_len' bytes of actions in 'ofpacts' are in the
+ * appropriate order as defined by the OpenFlow spec and as required by Open
+ * vSwitch.
+ *
+ * 'allowed_ovsinsts' is a bitmap of OVSINST_* values, in which 1-bits indicate
+ * instructions that are allowed within 'ofpacts[]'. */
 static enum ofperr
 ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len,
                uint32_t allowed_ovsinsts)
@@ -5589,6 +5685,17 @@ ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len,
     OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
         enum ovs_instruction_type next;
 
+        if (a->type == OFPACT_CONJUNCTION) {
+            OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
+                if (a->type != OFPACT_CONJUNCTION) {
+                    VLOG_WARN("when %s action is present, it must be the only "
+                              "kind of action used", ofpact_name(a->type));
+                    return OFPERR_NXBAC_BAD_CONJUNCTION;
+                }
+            }
+            return 0;
+        }
+
         next = ovs_instruction_type_from_ofpact_type(a->type);
         if (a > ofpacts
             && (inst == OVSINST_OFPIT11_APPLY_ACTIONS
@@ -5887,6 +5994,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_FIN_TIMEOUT:
     case OFPACT_RESUBMIT:
     case OFPACT_LEARN:
+    case OFPACT_CONJUNCTION:
     case OFPACT_MULTIPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 8362aa8..5458583 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -19,6 +19,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include "classifier.h"
 #include "meta-flow.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
@@ -96,6 +97,7 @@
     /* Flow table interaction. */                                       \
     OFPACT(RESUBMIT,        ofpact_resubmit,    ofpact, "resubmit")     \
     OFPACT(LEARN,           ofpact_learn,       specs, "learn")         \
+    OFPACT(CONJUNCTION,     ofpact_conjunction, ofpact, "conjunction")  \
                                                                         \
     /* Arithmetic. */                                                   \
     OFPACT(MULTIPATH,       ofpact_multipath,   ofpact, "multipath")    \
@@ -611,6 +613,16 @@ enum nx_mp_algorithm {
     NX_MP_ALG_ITER_HASH = 3,
 };
 
+/* OFPACT_CONJUNCTION.
+ *
+ * Used for NXAST_CONJUNCTION. */
+struct ofpact_conjunction {
+    struct ofpact ofpact;
+    uint8_t clause;
+    uint8_t n_clauses;
+    uint32_t id;
+};
+
 /* OFPACT_MULTIPATH.
  *
  * Used for NXAST_MULTIPATH. */
diff --git a/lib/ofp-errors.h b/lib/ofp-errors.h
index 238fded..56b7652 100644
--- a/lib/ofp-errors.h
+++ b/lib/ofp-errors.h
@@ -230,6 +230,14 @@ enum ofperr {
      * value. */
     OFPERR_NXBAC_MUST_BE_ZERO,
 
+    /* NX1.0-1.1(2,526), NX1.2+(15).  Conjunction action must be only action
+     * present.  Conjunction action must have at least one clause. */
+    OFPERR_NXBAC_BAD_CONJUNCTION,
+
+    /* NX1.0-1.1(2,527), NX1.2+(16).  Conjunction actions may not be modified.
+     * (Instead, remove the flow and add a new one in its place.) */
+    OFPERR_NXBAC_READONLY_CONJUNCTION,
+
 /* ## --------------------- ## */
 /* ## OFPET_BAD_INSTRUCTION ## */
 /* ## --------------------- ## */
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 53982d5..2270a93 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -186,7 +186,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
 void
 ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index a121354..8de2e2d 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Nicira, Inc.
+ * Copyright (c) 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -118,7 +118,7 @@ ovs_router_insert__(uint8_t priority, ovs_be32 ip_dst, uint8_t plen,
     cls_rule_init(&p->cr, &match, priority); /* Longest prefix matches first. */
 
     ovs_mutex_lock(&mutex);
-    cr = classifier_replace(&cls, &p->cr);
+    cr = classifier_replace(&cls, &p->cr, NULL, 0);
     ovs_mutex_unlock(&mutex);
 
     if (cr) {
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
index ff616a2..e6739b9 100644
--- a/lib/tnl-ports.c
+++ b/lib/tnl-ports.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Nicira, Inc.
+ * Copyright (c) 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ tnl_port_map_insert(odp_port_t port, ovs_be32 ip_dst, ovs_be16 udp_port,
         ovs_refcount_init(&p->ref_cnt);
         strncpy(p->dev_name, dev_name, IFNAMSIZ);
 
-        classifier_insert(&cls, &p->cr);
+        classifier_insert(&cls, &p->cr, NULL, 0);
     }
     ovs_mutex_unlock(&mutex);
 }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 4a5b7fd..2efcbb9 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+/* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -2635,7 +2635,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
 
     /* If 'struct flow' gets additional metadata, we'll need to zero it out
      * before traversing a patch port. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 29);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 30);
     memset(&flow_tnl, 0, sizeof flow_tnl);
 
     if (!xport) {
@@ -3742,6 +3742,7 @@ ofpact_needs_recirculation_after_mpls(const struct ofpact *a, struct xlate_ctx *
     case OFPACT_SET_TUNNEL:
     case OFPACT_SET_QUEUE:
     case OFPACT_POP_QUEUE:
+    case OFPACT_CONJUNCTION:
     case OFPACT_NOTE:
     case OFPACT_OUTPUT_REG:
     case OFPACT_EXIT:
@@ -4055,6 +4056,9 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             xlate_learn_action(ctx, ofpact_get_LEARN(a));
             break;
 
+        case OFPACT_CONJUNCTION:
+            break;
+
         case OFPACT_EXIT:
             ctx->exit = true;
             break;
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 0815a26..ba4263e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -4147,6 +4147,54 @@ evict_rules_from_table(struct oftable *table, unsigned int extra_space)
     return error;
 }
 
+static bool
+is_conjunction(const struct ofpact *ofpacts, size_t ofpacts_len)
+{
+    return ofpacts_len > 0 && ofpacts->type == OFPACT_CONJUNCTION;
+}
+
+static void
+get_conjunctions(const struct ofputil_flow_mod *fm,
+                 struct cls_conjunction **conjsp, size_t *n_conjsp)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct cls_conjunction *conjs = NULL;
+    int n_conjs = 0;
+
+    if (is_conjunction(fm->ofpacts, fm->ofpacts_len)) {
+        const struct ofpact *ofpact;
+        int i;
+
+        n_conjs = 0;
+        OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
+            n_conjs++;
+        }
+
+        conjs = xzalloc(n_conjs * sizeof *conjs);
+        i = 0;
+        OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
+            struct ofpact_conjunction *oc = ofpact_get_CONJUNCTION(ofpact);
+            conjs[i].clause = oc->clause;
+            conjs[i].n_clauses = oc->n_clauses;
+            conjs[i].id = oc->id;
+            i++;
+        }
+    }
+
+    *conjsp = conjs;
+    *n_conjsp = n_conjs;
+}
+
+static void
+set_conjunctions(struct rule *rule, const struct cls_conjunction *conjs,
+                 size_t n_conjs)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct cls_rule *cr = CONST_CAST(struct cls_rule *, &rule->cr);
+
+    cls_rule_set_conjunctions(cr, conjs, n_conjs);
+}
+
 /* Implements OFPFC_ADD and the cases for OFPFC_MODIFY and OFPFC_MODIFY_STRICT
  * in which no matching flow already exists in the flow table.
  *
@@ -4293,7 +4341,12 @@ add_flow(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
     }
 
     classifier_defer(&table->cls);
-    classifier_insert(&table->cls, &rule->cr);
+
+    struct cls_conjunction *conjs;
+    size_t n_conjs;
+    get_conjunctions(fm, &conjs, &n_conjs);
+    classifier_insert(&table->cls, &rule->cr, conjs, n_conjs);
+    free(conjs);
 
     error = ofproto->ofproto_class->rule_insert(rule);
     if (error) {
@@ -4405,8 +4458,43 @@ modify_flows__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
         }
 
         if (change_actions) {
+           /* We have to change the actions.  The rule's conjunctive match set
+            * is a function of its actions, so we need to update that too.  The
+            * conjunctive match set is used in the lookup process to figure
+            * which (if any) collection of conjunctive sets the packet matches
+            * with.  However, a rule with conjunction actions is never to be
+            * returned as a classifier lookup result.  To make sure a rule with
+            * conjunction actions is not returned as a lookup result, we update
+            * them in a carefully chosen order:
+            *
+            * - If we're adding a conjunctive match set where there wasn't one
+            *   before, we have to make the conjunctive match set available to
+            *   lookups before the rule's actions are changed, as otherwise
+            *   rule with a conjunction action could be returned as a lookup
+            *   result.
+            *
+            * - To clear some nonempty conjunctive set, we set the rule's
+            *   actions first, so that a lookup can't return a rule with
+            *   conjunction actions.
+            *
+            * - Otherwise, order doesn't matter for changing one nonempty
+            *   conjunctive match set to some other nonempty set, since the
+            *   rule's actions are not seen by the classifier, and hence don't
+            *   matter either before or after the change. */
+            struct cls_conjunction *conjs;
+            size_t n_conjs;
+            get_conjunctions(fm, &conjs, &n_conjs);
+
+            if (n_conjs) {
+                set_conjunctions(rule, conjs, n_conjs);
+            }
             ovsrcu_set(&rule->actions, rule_actions_create(fm->ofpacts,
                                                            fm->ofpacts_len));
+            if (!conjs) {
+                set_conjunctions(rule, conjs, n_conjs);
+            }
+
+            free(conjs);
         }
 
         if (change_actions || reset_counters) {
diff --git a/tests/automake.mk b/tests/automake.mk
index 33502bc..35ccf9e 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -322,3 +322,5 @@ clean-pki:
 	rm -f tests/pki/stamp
 	rm -rf tests/pki
 endif
+
+EXTRA_DIST += tests/test-conjunction
diff --git a/tests/classifier.at b/tests/classifier.at
index eb97022..9489da6 100644
--- a/tests/classifier.at
+++ b/tests/classifier.at
@@ -124,3 +124,161 @@ Datapath actions: 3
 ])
 OVS_VSWITCHD_STOP(["/'prefixes' with incompatible field: ipv6_label/d"])
 AT_CLEANUP
+
+AT_BANNER([conjunctive match])
+
+AT_SETUP([single conjunctive match])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], 1, 2, 3, 4, 5)
+AT_DATA([flows.txt], [dnl
+conj_id=1,actions=3
+priority=100,ip,ip_src=10.0.0.1,actions=conjunction(1,1/2)
+priority=100,ip,ip_src=10.0.0.4,actions=conjunction(1,1/2)
+priority=100,ip,ip_src=10.0.0.6,actions=conjunction(1,1/2)
+priority=100,ip,ip_src=10.0.0.7,actions=conjunction(1,1/2)
+priority=100,ip,ip_dst=10.0.0.2,actions=conjunction(1,2/2)
+priority=100,ip,ip_dst=10.0.0.5,actions=conjunction(1,2/2)
+priority=100,ip,ip_dst=10.0.0.7,actions=conjunction(1,2/2)
+priority=100,ip,ip_dst=10.0.0.8,actions=conjunction(1,2/2)
+priority=100,ip,ip_src=10.0.0.1,ip_dst=10.0.0.4,actions=4
+priority=100,ip,ip_src=10.0.0.3,ip_dst=10.0.0.5,actions=5
+
+priority=0 actions=2
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+for src in 0 1 2 3 4 5 6 7; do
+    for dst in 0 1 2 3 4 5 6 7; do
+        if test $src$dst = 14; then
+            out=4
+        elif test $src$dst = 35; then
+            out=5
+        else
+            out=2
+            case $src in [[1467]]) case $dst in [[2578]]) out=3 ;; esac ;; esac
+        fi
+        AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=1,dl_type=0x0800,nw_src=10.0.0.$src,nw_dst=10.0.0.$dst"], [0], [stdout])
+        AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: $out
+])
+    done
+done
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([multiple conjunctive match])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], 1, 2, 3, 4, 5)
+AT_DATA([flows.txt], [dnl
+conj_id=1,actions=1
+conj_id=2,actions=2
+conj_id=3,actions=3
+
+priority=5,ip,ip_src=20.0.0.0/8,actions=conjunction(1,1/2),conjunction(2,1/2)
+priority=5,ip,ip_src=10.1.0.0/16,actions=conjunction(1,1/2),conjunction(3,2/3)
+priority=5,ip,ip_src=10.2.0.0/16,actions=conjunction(1,1/2),conjunction(2,1/2)
+priority=5,ip,ip_src=10.1.3.0/24,actions=conjunction(1,1/2),conjunction(3,2/3)
+priority=5,ip,ip_src=10.1.4.5/32,actions=conjunction(1,1/2),conjunction(2,1/2)
+
+priority=5,ip,ip_dst=20.0.0.0/8,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=10.1.0.0/16,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=10.2.0.0/16,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=10.1.3.0/24,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=10.1.4.5/32,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=30.0.0.0/8,actions=conjunction(2,2/2),conjunction(3,1/3)
+priority=5,ip,ip_dst=40.5.0.0/16,actions=conjunction(2,2/2),conjunction(3,1/3)
+
+priority=5,tcp,tcp_dst=80,actions=conjunction(3,3/3)
+priority=5,tcp,tcp_dst=443,actions=conjunction(3,3/3)
+
+priority=5,tcp,tcp_src=80,actions=conjunction(3,3/3)
+priority=5,tcp,tcp_src=443,actions=conjunction(3,3/3)
+
+priority=0,actions=4
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+for a0 in \
+    '1 20.0.0.1' \
+    '2 10.1.0.1' \
+    '3 10.2.0.1' \
+    '4 10.1.3.1' \
+    '5 10.1.4.5' \
+    '6 1.2.3.4'
+do
+    for b0 in \
+        '1 20.0.0.1' \
+        '2 10.1.0.1' \
+        '3 10.2.0.1' \
+        '4 10.1.3.1' \
+        '5 10.1.4.5' \
+        '6 30.0.0.1' \
+        '7 40.5.0.1' \
+        '8 1.2.3.4'
+    do
+        for c0 in '1 80' '2 443' '3 8080'; do
+            for d0 in '1 80' '2 443' '3 8080'; do
+                set $a0; a=$1 ip_src=$2
+                set $b0; b=$1 ip_dst=$2
+                set $c0; c=$1 tcp_src=$2
+                set $d0; d=$1 tcp_dst=$2
+                case $a$b$c$d in
+                    [[12345]][[12345]]??) out=1 ;;
+                    [[135]][[67]]??) out=2 ;;
+                    [[24]][[67]][[12]]? | [[24]][[67]]?[[12]]) out=3 ;;
+                    *) out=4
+                esac
+                AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=5,dl_type=0x0800,nw_proto=6,nw_src=$ip_src,nw_dst=$ip_dst,tcp_src=$tcp_src,tcp_dst=$tcp_dst"], [0], [stdout])
+                AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: $out
+])
+            done
+        done
+    done
+done
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+# In conjunctive match, we can find some soft matches that turn out not to be a
+# real match.  Usually, that's the end of the road--there is no real match.
+# But if there is a flow identical to one of the flows that was a soft match,
+# except with a lower priority, then we have to try again with that lower
+# priority flow.  This test checks this special case.
+AT_SETUP([conjunctive match priority fallback])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], 1, 2, 3, 4, 5, 6)
+AT_DATA([flows.txt], [dnl
+conj_id=1,actions=1
+conj_id=3,actions=3
+
+priority=5,ip,ip_src=10.0.0.1,actions=conjunction(1,1/2)
+priority=5,ip,ip_src=10.0.0.2,actions=conjunction(1,1/2)
+priority=5,ip,ip_dst=10.0.0.1,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=10.0.0.2,actions=conjunction(1,2/2)
+priority=5,ip,ip_dst=10.0.0.3,actions=conjunction(1,2/2)
+
+priority=4,ip,ip_src=10.0.0.3,ip_dst=10.0.0.2,actions=2
+
+priority=3,ip,ip_src=10.0.0.1,actions=conjunction(3,1/2)
+priority=3,ip,ip_src=10.0.0.3,actions=conjunction(3,1/2)
+priority=3,ip,ip_dst=10.0.0.2,actions=conjunction(3,2/2)
+priority=3,ip,ip_dst=10.0.0.3,actions=conjunction(3,2/2)
+priority=3,ip,ip_dst=10.0.0.4,actions=conjunction(3,2/2)
+
+priority=2,ip,ip_src=10.0.0.1,ip_dst=10.0.0.5,actions=4
+
+priority=0,actions=5
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+for src in 0 1 2 3; do
+    for dst in 0 1 2 3 4 5; do
+        case $src$dst in
+            [[12]][[123]]) out=1 ;;
+            32) out=2 ;;
+            [[13]][[234]]) out=3 ;;
+            15) out=4 ;;
+            *) out=5
+        esac
+        AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=6,dl_type=0x0800,nw_src=10.0.0.$src,nw_dst=10.0.0.$dst"], [0], [stdout])
+        AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: $out
+])
+    done
+done
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 2f5a9dd..386cb67 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1469,6 +1469,7 @@ OVS_VSWITCHD_START
     matching:
       dp_hash: arbitrary mask
       recirc_id: exact match or wildcard
+      conj_id: exact match or wildcard
       tun_id: arbitrary mask
       tun_src: arbitrary mask
       tun_dst: arbitrary mask
@@ -1546,7 +1547,7 @@ AT_CHECK(
 # Check that the configuration was updated.
 mv expout orig-expout
 sed 's/classifier/main/
-74s/1000000/1024/' < orig-expout > expout
+75s/1000000/1024/' < orig-expout > expout
 AT_CHECK([ovs-ofctl -O OpenFlow13 dump-table-features br0 | sed '/^$/d
 /^OFPST_TABLE_FEATURES/d'], [0], [expout])
 OVS_VSWITCHD_STOP
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index a15a612..6748da7 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -736,7 +736,7 @@ test_single_rule(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         tcls_init(&tcls);
 
         tcls_rule = tcls_insert(&tcls, rule);
-        classifier_insert(&cls, &rule->cls_rule);
+        classifier_insert(&cls, &rule->cls_rule, NULL, 0);
         compare_classifiers(&cls, &tcls);
         check_tables(&cls, 1, 1, 0);
 
@@ -773,7 +773,7 @@ test_rule_replacement(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         set_prefix_fields(&cls);
         tcls_init(&tcls);
         tcls_insert(&tcls, rule1);
-        classifier_insert(&cls, &rule1->cls_rule);
+        classifier_insert(&cls, &rule1->cls_rule, NULL, 0);
         compare_classifiers(&cls, &tcls);
         check_tables(&cls, 1, 1, 0);
         tcls_destroy(&tcls);
@@ -782,7 +782,8 @@ test_rule_replacement(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         tcls_insert(&tcls, rule2);
 
         assert(test_rule_from_cls_rule(
-                   classifier_replace(&cls, &rule2->cls_rule)) == rule1);
+                   classifier_replace(&cls, &rule2->cls_rule,
+                                      NULL, 0)) == rule1);
         ovsrcu_postpone(free_rule, rule1);
         compare_classifiers(&cls, &tcls);
         check_tables(&cls, 1, 1, 0);
@@ -897,7 +898,8 @@ test_many_rules_in_one_list (int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 
                     tcls_rules[j] = tcls_insert(&tcls, rules[j]);
                     displaced_rule = test_rule_from_cls_rule(
-                        classifier_replace(&cls, &rules[j]->cls_rule));
+                        classifier_replace(&cls, &rules[j]->cls_rule,
+                                           NULL, 0));
                     if (pri_rules[pris[j]] >= 0) {
                         int k = pri_rules[pris[j]];
                         assert(displaced_rule != NULL);
@@ -1000,7 +1002,7 @@ test_many_rules_in_one_table(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
             rules[i] = make_rule(wcf, priority, value_pats[i]);
             tcls_rules[i] = tcls_insert(&tcls, rules[i]);
 
-            classifier_insert(&cls, &rules[i]->cls_rule);
+            classifier_insert(&cls, &rules[i]->cls_rule, NULL, 0);
             compare_classifiers(&cls, &tcls);
 
             check_tables(&cls, 1, i + 1, 0);
@@ -1059,7 +1061,7 @@ test_many_rules_in_n_tables(int n_tables)
             int value_pat = random_uint32() & ((1u << CLS_N_FIELDS) - 1);
             rule = make_rule(wcf, priority, value_pat);
             tcls_insert(&tcls, rule);
-            classifier_insert(&cls, &rule->cls_rule);
+            classifier_insert(&cls, &rule->cls_rule, NULL, 0);
             compare_classifiers(&cls, &tcls);
             check_tables(&cls, -1, i + 1, -1);
         }
diff --git a/tests/test-conjunction b/tests/test-conjunction
new file mode 100755
index 0000000..83dbe37
--- /dev/null
+++ b/tests/test-conjunction
@@ -0,0 +1,22 @@
+#! /bin/sh
+ovs-vsctl --may-exist add-br br0
+ovs-ofctl del-flows br0
+ovs-ofctl add-flows br0 - <<EOF
+conj_id=1,ip,actions=mod_dl_src:00:11:22:33:44:55,local
+ip,ip_src=10.0.0.1,actions=conjunction(1,1/2)
+ip,ip_src=10.0.0.4,actions=conjunction(1,1/2)
+ip,ip_src=10.0.0.6,actions=conjunction(1,1/2)
+ip,ip_src=10.0.0.7,actions=conjunction(1,1/2)
+ip,ip_dst=10.0.0.2,actions=conjunction(1,2/2)
+ip,ip_dst=10.0.0.5,actions=conjunction(1,2/2)
+ip,ip_dst=10.0.0.7,actions=conjunction(1,2/2)
+ip,ip_dst=10.0.0.8,actions=conjunction(1,2/2)
+EOF
+
+# This should match the conjunctive flow and thus change the Ethernet
+# source address and output to local.
+ovs-appctl ofproto/trace br0 tcp,ip_src=10.0.0.1,ip_dst=10.0.0.5
+printf "%s\n\n" '------------------------------------------------------------'
+
+# This should not match anything and thus get dropped.
+ovs-appctl ofproto/trace br0 tcp,ip_src=10.0.0.2,ip_dst=10.0.0.5
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 7ffbeaa..aa6da29 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1141,6 +1141,11 @@ output group in the OpenFlow action set), then the value will be
 .IP
 This field was introduced in Open vSwitch 2.4 to conform with the
 OpenFlow 1.5 (draft) specification.
+.
+.IP \fBconj_id=\fIvalue\fR
+Matches the given 32-bit \fIvalue\fR against the conjunction ID.  This
+is used only with the \fBconjunction\fR action, documented later.
+.
 .PP
 Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires
 support for NXM.  The following shorthand notations are available for
@@ -1783,6 +1788,206 @@ unaffected.  Any further actions, including those which may be in
 other tables, or different levels of the \fBresubmit\fR call stack,
 are ignored.  Actions in the action set is still executed (specify
 \fBclear_actions\fR before \fBexit\fR to discard them).
+.
+.IP "\fBconjunction(\fIid\fB, \fIk\fB/\fIn\fR\fB)\fR"
+An individual OpenFlow flow can match only a single value for each
+field.  However, situations often arise where one wants to match one
+of a set of values within a field or fields.  For matching a single
+field against a set, it is straightforward and efficient to add
+multiple flows to the flow table, one for each value in the set.  For
+example, one might use the following flows to send packets with IP
+source address \fIa\fR, \fIb\fR, \fIc\fR, or \fId\fR to the OpenFlow
+controller:
+.RS +1in
+.br
+\fBip,ip_src=\fIa\fB actions=controller\fR
+.br
+\fBip,ip_src=\fIb\fB actions=controller\fR
+.br
+\fBip,ip_src=\fIc\fB actions=controller\fR
+.br
+\fBip,ip_src=\fId\fB actions=controller\fR
+.br
+.RE
+.IP
+Similarly, these flows send packets with IP destination address
+\fIe\fR, \fIf\fR, \fIg\fR, or \fIh\fR to the OpenFlow controller:
+.RS +1in
+.br
+\fBip,ip_dst=\fIe\fB actions=controller\fR
+.br
+\fBip,ip_dst=\fIf\fB actions=controller\fR
+.br
+\fBip,ip_dst=\fIg\fB actions=controller\fR
+.br
+\fBip,ip_dst=\fIh\fB actions=controller\fR
+.br
+.RE
+.IP
+Installing all of the above flows in a single flow table yields a
+disjunctive effect: a packet is sent to the controller if \fBip_src\fR
+\[mo] {\fIa\fR,\fIb\fR,\fIc\fR,\fId\fR} or \fBip_dst\fR \[mo]
+{\fIe\fR,\fIf\fR,\fIg\fR,\fIh\fR} (or both).  (Pedantically, if both
+of the above sets of flows are present in the flow table, they should
+have different priorities, because OpenFlow says that the results are
+undefined when two flows with same priority can both match a single
+packet.)
+.IP
+Suppose, on the other hand, one wishes to match conjunctively, that
+is, to send a packet to the controller only if both \fBip_src\fR \[mo]
+{\fIa\fR,\fIb\fR,\fIc\fR,\fId\fR} and \fBip_dst\fR \[mo]
+{\fIe\fR,\fIf\fR,\fIg\fR,\fIh\fR}.  This requires 4 \[mu] 4 = 16
+flows, one for each possible pairing of \fBip_src\fR and \fBip_dst\fR.
+That is acceptable for our small example, but it does not gracefully
+extend to larger sets or greater numbers of dimensions.
+.IP
+The \fBconjunction\fR action is a solution for conjunctive matches
+that is built into Open vSwitch.  A \fBconjunction\fR action ties
+groups of individual OpenFlow flows into higher-level ``conjunctive
+flows''.  Each group corresponds to one dimension, and each flow
+within the group matches one possible value for the dimension.  A
+packet that matches one flow from each group matches the conjunctive
+flow.
+.IP
+To implement a conjunctive flow with \fBconjunction\fR, assign the
+conjunctive flow a 32-bit \fIid\fR, which must be unique within an
+OpenFlow table.  Assign each of the \fIn\fR \[>=] 2 dimensions a
+unique number from 1 to \fIn\fR; the ordering is unimportant.  Add one
+flow to the OpenFlow flow table for each possible value of each
+dimension with \fBconjunction(\fIid, \fIk\fB/\fIn\fB)\fR as the flow's
+actions, where \fIk\fR is the number assigned to the flow's dimension.
+Together, these flows specify the conjunctive flow's match condition.
+When the conjunctive match condition is met, Open vSwitch looks up one
+more flow that specifies the conjunctive flow's actions and receives
+its statistics.  This flow is found by setting \fBconj_id\fR to the
+specified \fIid\fR and then again searching the flow table.
+.IP
+The following flows provide an example.  Whenever the IP source is one
+of the values in the flows that match on the IP source (dimension 1 of
+2), \fIand\fR the IP destination is one of the values in the flows
+that match on IP destination (dimension 2 of 2), Open vSwitch searches
+for a flow that matches \fBconj_id\fR against the conjunction ID
+(1234), finding the first flow listed below.
+.RS +1in
+.br
+.B "conj_id=1234 actions=controller"
+.br
+.B "ip,ip_src=10.0.0.1 actions=conjunction(1234, 1/2)"
+.br
+.B "ip,ip_src=10.0.0.4 actions=conjunction(1234, 1/2)"
+.br
+.B "ip,ip_src=10.0.0.6 actions=conjunction(1234, 1/2)"
+.br
+.B "ip,ip_src=10.0.0.7 actions=conjunction(1234, 1/2)"
+.br
+.B "ip,ip_dst=10.0.0.2 actions=conjunction(1234, 2/2)"
+.br
+.B "ip,ip_dst=10.0.0.5 actions=conjunction(1234, 2/2)"
+.br
+.B "ip,ip_dst=10.0.0.7 actions=conjunction(1234, 2/2)"
+.br
+.B "ip,ip_dst=10.0.0.8 actions=conjunction(1234, 2/2)"
+.RE
+.IP
+Many subtleties exist:
+.RS
+.IP \(bu
+In the example above, every flow in a single dimension has the same
+form, that is, dimension 1 matches on \fBip_src\fR, dimension 2 on
+\fBip_dst\fR, but this is not a requirement.  Different flows within a
+dimension may match on different bits within a field (e.g. IP network
+prefixes of different lengths, or TCP/UDP port ranges as bitwise
+matches), or even on entirely different fields (e.g. to match packets
+for TCP source port 80 or TCP destination port 80).
+.IP \(bu
+The flows within a dimension can vary their matches across more than
+one field, e.g. to match only specific pairs of IP source and
+destination addresses or L4 port numbers.
+.IP \(bu
+A flow may have multiple \fBconjunction\fR actions, with different
+\fIid\fR values.  This is useful for multiple conjunctive flows with
+overlapping sets.  If one conjunctive flow matches packets with both
+\fBip_src\fR \[mo] {\fIa\fR,\fIb\fR} and \fBip_dst\fR \[mo]
+{\fId\fR,\fIe\fR} and a second conjunctive flow matches \fBip_src\fR
+\[mo] {\fIb\fR,\fIc\fR} and \fBip_dst\fR \[mo] {\fIf\fR,\fIg\fR}, for
+example, then the flow that matches \fBip_src=\fIb\fR would have two
+\fBconjunction\fR actions, one for each conjunctive flow.  The order
+of \fBconjunction\fR actions within a list of actions is not
+significant.
+.IP \(bu
+A flow with \fBconjunction\fR actions may not have any other actions.
+(It would not be useful.)
+.IP \(bu
+All of the flows that constitute a conjunctive flow with a given
+\fIid\fR must have the same priority.  (Flows with the same \fIid\fR
+but different priorities are currently treated as different
+conjunctive flows, that is, currently \fIid\fR values need only be
+unique within an OpenFlow table at a given priority.  This behavior
+isn't guaranteed to stay the same in later releases, so please use
+\fIid\fR values unique within an OpenFlow table.)
+.IP \(bu
+Conjunctive flows must not overlap with each other, at a given
+priority, that is, any given packet must be able to match at most one
+conjunctive flow at a given priority.  Overlapping conjunctive flows
+yield unpredictable results.
+.IP \(bu
+Following a conjunctive flow match, the search for the flow with
+\fBconj_id=\fIid\fR is done in the same general-purpose way as other flow
+table searches, so one can use flows with \fBconj_id=\fIid\fR to act
+differently depending on circumstances.  (One exception is that the
+search for the \fBconj_id=\fIid\fR flow itself ignores conjunctive flows,
+to avoid recursion.) If the search with \fBconj_id=\fIid\fR fails, Open
+vSwitch acts as if the conjunctive flow had not matched at all, and
+continues searching the flow table for other matching flows.
+.IP \(bu
+OpenFlow prerequisite checking occurs for the flow with
+\fBconj_id=\fIid\fR in the same way as any other flow, e.g. in an
+OpenFlow 1.1+ context, putting a \fBmod_nw_src\fR action into the
+example above would require adding an \fBip\fR match, like this:
+.RS +1in
+.br
+.B "conj_id=1234,ip actions=mod_nw_src:1.2.3.4,controller"
+.br
+.RE
+.IP \(bu
+OpenFlow prerequisite checking also occurs for the individual flows
+that comprise a conjunctive match in the same way as any other flow.
+.IP \(bu
+The flows that constitute a conjunctive flow do not have useful
+statistics.  They are never updated with byte or packet counts, and so
+on.  (For such a flow, therefore, the idle and hard timeouts work much
+the same way.)
+.IP \(bu
+Conjunctive flows can be a useful building block for negation, that
+is, inequality matches like \fBtcp_src\fR \[!=] 80.  To implement an
+inequality match, convert it to a pair of range matches, e.g. 0 \[<=]
+\fBtcp_src\ < 80 and 80 < \fBtcp_src\fR \[<=] 65535, then convert each
+of the range matches into a collection of bitwise matches as explained
+above in the description of \fBtcp_src\fR.
+.IP \(bu
+Sometimes there is a choice of which flows include a particular match.
+For example, suppose that we added an extra constraint to our example,
+to match on \fBip_src\fR \[mo] {\fIa\fR,\fIb\fR,\fIc\fR,\fId\fR} and
+\fBip_dst\fR \[mo] {\fIe\fR,\fIf\fR,\fIg\fR,\fIh\fR} and \fBtcp_dst\fR
+= \fIi\fR.  One way to implement this is to add the new constraint to
+the \fBconj_id\fR flow, like this:
+.RS +1in
+.br
+\fBconj_id=1234,tcp,tcp_dst=\fIi\fB actions=mod_nw_src:1.2.3.4,controller\fR
+.br
+.RE
+.IP
+\fIbut this is not recommended\fR because of the cost of the extra
+flow table lookup.  Instead, add the constraint to the individual
+flows, either in one of the dimensions or (slightly better) all of
+them.
+.IP \(bu
+A conjunctive match must have \fIn\fR \[>=] 2 dimensions (otherwise a
+conjunctive match is not necessary).  Open vSwitch enforces this.
+.IP \(bu
+Each dimension within a conjunctive match should ordinarily have more
+than one flow.  Open vSwitch does not enforce this.
+.RE
 .RE
 .
 .PP
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 35cb2e8..b5ace5a 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -2391,7 +2391,7 @@ fte_insert(struct classifier *cls, const struct match *match,
     cls_rule_init(&fte->rule, match, priority);
     fte->versions[index] = version;
 
-    old = fte_from_cls_rule(classifier_replace(cls, &fte->rule));
+    old = fte_from_cls_rule(classifier_replace(cls, &fte->rule, NULL, 0));
     if (old) {
         fte->versions[!index] = old->versions[!index];
         old->versions[!index] = NULL;
-- 
1.7.10.4




More information about the dev mailing list