From: Numan Siddique <num...@ovn.org>

Signed-off-by: Numan Siddique <num...@ovn.org>
---
 br-controller/automake.mk   |    2 +
 br-controller/br-flow-mgr.c | 1302 +++++++++++++++++++++++++++++++++++
 br-controller/br-flow-mgr.h |   65 ++
 3 files changed, 1369 insertions(+)
 create mode 100644 br-controller/br-flow-mgr.c
 create mode 100644 br-controller/br-flow-mgr.h

diff --git a/br-controller/automake.mk b/br-controller/automake.mk
index b77e2abcff..ea515a85e4 100644
--- a/br-controller/automake.mk
+++ b/br-controller/automake.mk
@@ -1,5 +1,7 @@
 bin_PROGRAMS += br-controller/ovn-br-controller
 br_controller_ovn_br_controller_SOURCES = \
+       br-controller/br-flow-mgr.c \
+       br-controller/br-flow-mgr.h \
        br-controller/en-bridge-data.c \
        br-controller/en-bridge-data.h \
        br-controller/en-lflow.c \
diff --git a/br-controller/br-flow-mgr.c b/br-controller/br-flow-mgr.c
new file mode 100644
index 0000000000..2c8c6a4e92
--- /dev/null
+++ b/br-controller/br-flow-mgr.c
@@ -0,0 +1,1302 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+/* OVS includes. */
+#include "lib/byte-order.h"
+#include "lib/uuid.h"
+#include "lib/hash.h"
+#include "lib/hmapx.h"
+#include "lib/ovs-atomic.h"
+#include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-flow.h"
+#include "openvswitch/ofp-group.h"
+#include "openvswitch/ofp-match.h"
+#include "openvswitch/ofp-msgs.h"
+#include "openvswitch/ofp-meter.h"
+#include "openvswitch/ofp-packet.h"
+#include "openvswitch/ofp-print.h"
+#include "openvswitch/ofp-util.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "br-flow-mgr.h"
+
+VLOG_DEFINE_THIS_MODULE(brflowmgr);
+
+static struct hmap br_flow_tables = HMAP_INITIALIZER(&br_flow_tables);
+
+#define CONJ_ACT_COOKIE UINT64_MAX
+
+/* Flow handling. */
+struct ovn_flow;
+
+/* An ovn_flow action. */
+struct ovn_flow_action {
+    struct ovs_list flow_uuid_action_node;
+
+    struct ofpact *ofpacts;
+    size_t ofpacts_len;
+    uint64_t cookie;
+    struct uuid flow_uuid;
+    bool stale;
+
+    struct ovs_list list_node;
+    struct ovn_flow *flow;
+};
+
+/* An OpenFlow flow. */
+struct ovn_flow {
+    struct hmap_node hmap_node;
+    struct hmap *match_flow_table; /* Points to the match flow table hmap for
+                                    * easy access. */
+    struct hmap *uuid_action_flow_table; /* Points to the uuid action flow
+                                          * table hmap for easy access. */
+    struct ovs_list flow_list_node; /* List node in
+                                     * br_flow_table.flows_list[]. */
+
+    /* Flow list in which this 'ovn_flow.flow_list_node' is present
+     * (for easy access). */
+    struct ovs_list *flow_list;
+
+    /* Key. */
+    uint8_t table_id;
+    uint16_t priority;
+    struct minimatch match;
+
+    /* Hash. */
+    uint32_t hash;
+
+    /* Actions associated with the flow.
+     * An ovn_flow can have
+     *   - A normal action - has precendence over all the other below actions
+     *                       if set.
+     *   - a list of allow actions - has precedence over drop and conjuctive
+     *                               actions.
+     *   - a list of drop actions  - has precedence over conjuctive actions.
+     *   - a list of conjuctive actions.
+     */
+    struct ovn_flow_action *normal_act;
+    struct ovs_list allow_act_list;
+    struct ovs_list drop_act_list;
+    struct ovs_list conj_act_list;
+
+    /* Presently installed ofpacts. */
+    struct ofpact *installed_ofpacts;
+    size_t installed_ofpacts_len;
+
+    /* Cookie of the ovn_action which is presently active. */
+    uint64_t active_cookie;
+};
+
+/* Represents a list of 'ovn flow actions' associated with
+ * a flow uuid. */
+struct flow_uuid_to_acts {
+    struct hmap_node hmap_node; /* Node in
+                                 * ovn_flow_table.uuid_flow_table. */
+    struct uuid flow_uuid;
+    struct ovs_list acts; /* A list of struct ovn_flow_action nodes that
+                           * are referenced by the uuid. */
+};
+
+/* Represents a flow table. */
+struct ovn_flow_table {
+    /* Hash map of flow table using flow match conditions as hash key.*/
+    struct hmap match_flow_table;
+
+    /* Hash map of ovn_flow_action list table using uuid as hash key.*/
+    struct hmap uuid_action_flow_table;
+};
+
+/* Represents a bridge flow table.
+ *
+ * We maintain 2 lists for openflows list. One represents an active flow
+ * list and the other represents an old flow list.  When a flow is added to
+ * the br flow table, it is always added to the active flow list. If that flow
+ * is present in the old flow list, it is removed from the old one.
+ *
+ * The function br_flow_populate_oflow_msgs() clears all the
+ * flows present in the old flow list.
+ *
+ * User can swap the lists by calling br_flow_switch_oflow_tables().
+ *
+ */
+struct br_flow_table {
+    struct hmap_node hmap_node;
+    char *bridge; /* key. */
+
+    struct ovn_flow_table lflow_table; /* For logical flows. */
+    struct ovn_flow_table pflow_table; /* For physical flows. */
+
+    struct ovs_list lflows_list[2];
+    struct ovs_list pflows_list[2];
+
+    struct ovs_list *active_lflows; /* Points to one of the element in
+                                     * lflows_list. */
+    struct ovs_list *old_lflows; /* Points to other element in
+                                  * lflows_list. */
+
+    struct ovs_list *active_pflows;
+    struct ovs_list *old_pflows;
+
+    struct hmapx modified_lflows;
+    struct hmapx modified_pflows;
+};
+
+/* Static functions. */
+static void br_flow_switch_lflow_table__(struct br_flow_table *);
+static void br_flow_switch_pflow_table__(struct br_flow_table *);
+static void br_flow_table_destroy__(struct br_flow_table *);
+
+static void br_flow_add_openflow__(struct br_flow_table *,
+                                   bool lflow_table,
+                                   uint8_t table_id, uint16_t priority,
+                                   uint64_t cookie, const struct match *match,
+                                   const struct ofpbuf *actions,
+                                   const struct uuid *flow_uuid);
+static void br_flows_remove(struct br_flow_table *, bool lflow_table,
+                            const struct uuid *flow_uuid);
+
+static void ovn_flow_table_init(struct ovn_flow_table *);
+static void ovn_flow_table_clear(struct ovn_flow_table *);
+static void ovn_flow_table_destroy(struct ovn_flow_table *);
+
+static uint32_t ovn_flow_match_hash__(uint8_t table_id, uint16_t priority,
+                                      const struct minimatch *);
+static uint32_t ovn_flow_match_hash(const struct ovn_flow *);
+
+static void ovn_flow_init(struct ovn_flow *, uint8_t table_id,
+                          uint16_t priority, const struct match *);
+static void ovn_flow_uninit(struct ovn_flow *);
+static struct ovn_flow *ovn_flow_alloc(uint8_t table_id, uint16_t priority,
+                                       const struct match *);
+static void ovn_flow_destroy(struct ovn_flow *);
+
+static struct ovn_flow_action *ovn_flow_action_alloc(const struct ofpbuf *,
+                                                     uint64_t cookie,
+                                                     const struct uuid *);
+static void ovn_flow_action_init(struct ovn_flow_action *,
+                                 const struct ofpbuf *, uint64_t cookie,
+                                 const struct uuid *);
+static void ovn_flow_action_destroy(struct ovn_flow_action *);
+static struct ovn_flow *ovn_flow_lookup(struct ovn_flow_table *,
+                                        uint8_t table_id, uint16_t priority,
+                                        const struct minimatch *);
+static void ovn_flow_invalidate_all_actions(struct ovn_flow *f);
+static void ovn_flow_clear_actions_from_list(struct ovs_list *act_list);
+static void ovn_flow_invalidate_actions_from_list(struct ovs_list *act_list);
+static void ovn_flow_clear_actions(struct ovn_flow *);
+static bool ovn_flow_has_active_actions(struct ovn_flow *);
+
+static bool ovn_flow_action_is_normal(const struct ovn_flow_action *);
+static bool ovn_flow_action_is_allow(const struct ovn_flow_action *);
+static bool ovn_flow_action_is_drop(const struct ovn_flow_action *);
+static bool ovn_flow_action_is_conj(const struct ovn_flow_action *);
+
+static bool ovn_flow_has_action(struct ovn_flow *, struct ovn_flow_action *);
+static struct flow_uuid_to_acts *flow_uuid_to_acts_lookup(
+    struct hmap *uuid_action_table, const struct uuid *flow_uuid);
+static void ovn_flow_action_link_to_flow_uuid(
+    struct hmap *uuid_action_table, struct ovn_flow_action *);
+static void ovn_flow_unref_action__(struct ovn_flow *,
+                                    struct ovn_flow_action *);
+static struct ovn_flow_action * ovn_flow_action_get_matching_in_list(
+    struct ovs_list *act_list, struct ovn_flow_action *a);
+static void ovn_flow_unref_action(struct ovn_flow *, struct ovn_flow_action *);
+static struct ovn_flow_action *ovn_flow_get_matching_action(
+    struct ovn_flow *, struct ovn_flow_action *);
+
+static bool ovn_flow_update_actions(struct ovn_flow_table *,
+                                    struct ovn_flow *,
+                                    struct ovn_flow_action *);
+
+static void ovn_flow_prepare_ofmsg(struct ovn_flow *, struct ovs_list *msgs);
+static void br_flow_populate_oflow_msgs__(struct br_flow_table *,
+                                          struct ovs_list *msgs);
+
+static struct ofpbuf *encode_flow_mod(struct ofputil_flow_mod *);
+static void add_flow_mod(struct ofputil_flow_mod *, struct ovs_list *msgs);
+static void ovn_flow_add_oflow(struct ovn_flow *, struct ovn_flow_action *,
+                               struct ovs_list *msgs);
+static void ovn_flow_mod_oflow(struct ovn_flow *, struct ovn_flow_action *,
+                               struct ovs_list *msgs);
+static void ovn_flow_del_oflow(struct ovn_flow *, struct ovs_list *msgs);
+static void ovn_flow_log(const struct ovn_flow *);
+static char * ovn_flow_to_string(const struct ovn_flow *);
+
+void
+br_flow_tables_init(void)
+{
+
+}
+
+void
+br_flow_tables_destroy(void)
+{
+    struct br_flow_table *br_table;
+    HMAP_FOR_EACH_POP (br_table, hmap_node, &br_flow_tables) {
+        br_flow_table_destroy__(br_table);
+    }
+
+    hmap_destroy(&br_flow_tables);
+}
+
+struct br_flow_table *
+br_flow_table_get(const char *bridge)
+{
+    struct br_flow_table *br_table;
+    uint32_t hash = hash_string(bridge, 0);
+    HMAP_FOR_EACH_WITH_HASH (br_table, hmap_node, hash, &br_flow_tables) {
+        if (!strcmp(br_table->bridge, bridge)) {
+            return br_table;
+        }
+    }
+
+    return NULL;
+}
+
+struct br_flow_table *
+br_flow_table_alloc(const char *bridge)
+{
+    struct br_flow_table *br_table = xzalloc(sizeof *br_table);
+    ovn_flow_table_init(&br_table->lflow_table);
+    ovn_flow_table_init(&br_table->pflow_table);
+
+    ovs_list_init(&br_table->lflows_list[0]);
+    ovs_list_init(&br_table->lflows_list[1]);
+
+    ovs_list_init(&br_table->pflows_list[0]);
+    ovs_list_init(&br_table->pflows_list[1]);
+
+    hmapx_init(&br_table->modified_lflows);
+    hmapx_init(&br_table->modified_pflows);
+
+    br_table->active_lflows = &br_table->lflows_list[0];
+    br_table->old_lflows = &br_table->lflows_list[1];
+
+    br_table->active_pflows = &br_table->pflows_list[0];
+    br_table->old_pflows = &br_table->pflows_list[1];
+
+    br_table->bridge = xstrdup(bridge);
+    hmap_insert(&br_flow_tables, &br_table->hmap_node, hash_string(bridge, 0));
+    return br_table;
+}
+
+void br_flow_table_destroy(const char *bridge)
+{
+    struct br_flow_table *br_table = br_flow_table_get(bridge);
+    if (br_table) {
+        hmap_remove(&br_flow_tables, &br_table->hmap_node);
+        br_flow_table_destroy__(br_table);
+    }
+}
+
+void
+br_flow_switch_logical_oflow_tables(void)
+{
+    struct br_flow_table *br_ftable;
+    HMAP_FOR_EACH (br_ftable, hmap_node, &br_flow_tables) {
+        br_flow_switch_lflow_table__(br_ftable);
+    }
+}
+
+void
+br_flow_switch_logical_oflow_table(const char *bridge)
+{
+    struct br_flow_table *br_ftable;
+    br_ftable = br_flow_table_get(bridge);
+    if (br_ftable) {
+        br_flow_switch_lflow_table__(br_ftable);
+    }
+}
+
+void
+br_flow_switch_physical_oflow_tables(void)
+{
+    struct br_flow_table *br_ftable;
+    HMAP_FOR_EACH (br_ftable, hmap_node, &br_flow_tables) {
+        br_flow_switch_pflow_table__(br_ftable);
+    }
+}
+
+void
+br_flow_switch_physical_oflow_table(const char *bridge)
+{
+    struct br_flow_table *br_ftable;
+    br_ftable = br_flow_table_get(bridge);
+    if (br_ftable) {
+        br_flow_switch_pflow_table__(br_ftable);
+    }
+}
+
+void
+br_flow_add_logical_oflow(const char *bridge, uint8_t table_id,
+                          uint16_t priority, uint64_t cookie,
+                          const struct match *match,
+                          const struct ofpbuf *actions,
+                          const struct uuid *flow_uuid)
+{
+    struct br_flow_table *br_ftable = br_flow_table_get(bridge);
+    if (!br_ftable) {
+        br_ftable = br_flow_table_alloc(bridge);
+    }
+
+    br_flow_add_openflow__(br_ftable, true, table_id, priority, cookie, match,
+                           actions, flow_uuid);
+}
+
+void
+br_flow_add_physical_oflow(const char *bridge, uint8_t table_id,
+                           uint16_t priority, uint64_t cookie,
+                           const struct match *match,
+                           const struct ofpbuf *actions,
+                           const struct uuid *flow_uuid)
+{
+    struct br_flow_table *br_ftable = br_flow_table_get(bridge);
+    if (!br_ftable) {
+        br_ftable = br_flow_table_alloc(bridge);
+    }
+
+    br_flow_add_openflow__(br_ftable, false, table_id, priority, cookie, match,
+                           actions, flow_uuid);
+}
+
+void
+br_flow_remove_logical_oflows_all(const struct uuid *flow_uuid)
+{
+    struct br_flow_table *br_ftable;
+    HMAP_FOR_EACH (br_ftable, hmap_node, &br_flow_tables) {
+        br_flows_remove(br_ftable, true, flow_uuid);
+    }
+}
+
+void
+br_flow_remove_logical_oflows(const char *bridge, const struct uuid *flow_uuid)
+{
+    struct br_flow_table *br_ftable = br_flow_table_get(bridge);
+    if (br_ftable) {
+        br_flows_remove(br_ftable, true, flow_uuid);
+    }
+}
+
+void
+br_flow_remove_physical_oflows(const char *bridge,
+                               const struct uuid *flow_uuid)
+{
+    struct br_flow_table *br_ftable = br_flow_table_get(bridge);
+    if (br_ftable) {
+        br_flows_remove(br_ftable, false, flow_uuid);
+    }
+}
+
+void
+br_flow_flush_all_oflows(void)
+{
+    struct br_flow_table *br_table;
+    HMAP_FOR_EACH_POP (br_table, hmap_node, &br_flow_tables) {
+        br_flow_table_destroy__(br_table);
+    }
+
+    hmap_destroy(&br_flow_tables);
+    hmap_init(&br_flow_tables);
+}
+
+void
+br_flow_flush_oflows(const char *bridge)
+{
+    struct br_flow_table *br_ftable = br_flow_table_get(bridge);
+    if (br_ftable) {
+        hmap_remove(&br_flow_tables, &br_ftable->hmap_node);
+        br_flow_table_destroy__(br_ftable);
+    }
+}
+
+void
+br_flow_populate_oflow_msgs(const char *bridge, struct ovs_list *msgs)
+{
+    struct br_flow_table *br_ftable = br_flow_table_get(bridge);
+    if (br_ftable) {
+        br_flow_populate_oflow_msgs__(br_ftable, msgs);
+    }
+}
+
+/* Static functions. */
+
+static void
+br_flow_switch_lflow_table__(struct br_flow_table *br_ftable)
+{
+    struct ovs_list *t = br_ftable->active_lflows;
+    br_ftable->active_lflows = br_ftable->old_lflows;
+    br_ftable->old_lflows = t;
+
+    hmapx_clear(&br_ftable->modified_lflows);
+}
+
+static void
+br_flow_switch_pflow_table__(struct br_flow_table *br_ftable)
+{
+    struct ovs_list *t = br_ftable->active_pflows;
+    br_ftable->active_pflows = br_ftable->old_pflows;
+    br_ftable->old_pflows = t;
+
+    hmapx_clear(&br_ftable->modified_pflows);
+}
+
+static void
+br_flow_table_destroy__(struct br_flow_table *br_ftable)
+{
+    hmapx_clear(&br_ftable->modified_lflows);
+    hmapx_destroy(&br_ftable->modified_lflows);
+    ovn_flow_table_destroy(&br_ftable->lflow_table);
+
+    hmapx_clear(&br_ftable->modified_pflows);
+    hmapx_destroy(&br_ftable->modified_pflows);
+    ovn_flow_table_destroy(&br_ftable->pflow_table);
+
+    free(br_ftable->bridge);
+    free(br_ftable);
+}
+
+/* This function
+ *   - Looks up in the bridge flow table if the flow F
+ *     with the provided (match, table_id, priority) is already
+ *     present or not.
+ *
+ *   - If not present it creates an ovn_flow 'F' with the provided
+ *     (match, table_id, priority) and inserts into the flow table.
+ *
+ *   - Allocates ovn_flow_action 'A' with the given -
+ *     (actions, cookie, flow_uuid).
+ *
+ *   - If 'F' already has 'A' it does nothing and returns. Otherwise
+ *     it associates 'A' to the 'F' actions.
+ *
+ *   - Adds 'F' to the active flow list.
+ */
+static void
+br_flow_add_openflow__(struct br_flow_table *br_ftable, bool lflow_table,
+                       uint8_t table_id, uint16_t priority,
+                       uint64_t cookie, const struct match *match,
+                       const struct ofpbuf *actions,
+                       const struct uuid *flow_uuid)
+{
+    struct ovn_flow_table *ftable;
+    struct hmapx *modified_flows;
+    struct ovs_list *active_flows;
+
+    if (lflow_table) {
+        ftable = &br_ftable->lflow_table;
+        modified_flows = &br_ftable->modified_lflows;
+        active_flows = br_ftable->active_lflows;
+    } else {
+        ftable = &br_ftable->pflow_table;
+        modified_flows = &br_ftable->modified_pflows;
+        active_flows = br_ftable->active_pflows;
+    }
+
+    struct minimatch minimatch;
+    minimatch_init(&minimatch, match);
+
+    struct ovn_flow *f = ovn_flow_lookup(ftable, table_id, priority,
+                                         &minimatch);
+
+    minimatch_destroy(&minimatch);
+
+    bool active_flow_list_changed = false;
+    bool flow_exists = true;
+
+    if (!f) {
+        f = ovn_flow_alloc(table_id, priority, match);
+        f->match_flow_table = &ftable->match_flow_table;
+        f->uuid_action_flow_table = &ftable->uuid_action_flow_table;
+        ovs_list_init(&f->flow_list_node);
+        flow_exists = false;
+        hmap_insert(&ftable->match_flow_table, &f->hmap_node,
+                    f->hash);
+    } else {
+        ovs_list_remove(&f->flow_list_node);
+        active_flow_list_changed = (active_flows != f->flow_list);
+    }
+
+    f->flow_list = active_flows;
+    ovs_list_push_back(active_flows, &f->flow_list_node);
+
+    struct ovn_flow_action *a = ovn_flow_action_alloc(actions, cookie,
+                                                      flow_uuid);
+
+    if (active_flow_list_changed) {
+        ovn_flow_invalidate_all_actions(f);
+    }
+
+    bool push_flow_to_switch = true;
+    if (flow_exists && ovn_flow_has_action(f, a)) {
+        struct ovn_flow_action *existing_a =
+            ovn_flow_get_matching_action(f, a);
+        if (uuid_equals(&existing_a->flow_uuid, flow_uuid)) {
+            existing_a->stale = false;
+        }
+        if (!active_flow_list_changed) {
+            /* The flow-action pair already exists. Nothing to be done. */
+            push_flow_to_switch = false;
+        }
+        ovn_flow_action_destroy(a);
+    } else {
+        ovn_flow_update_actions(ftable, f, a);
+    }
+
+    ovn_flow_log(f);
+    if (push_flow_to_switch) {
+        hmapx_add(modified_flows, (void *) f);
+    }
+}
+
+static void
+br_flows_remove(struct br_flow_table *br_ftable, bool lflow_table,
+                const struct uuid *flow_uuid)
+{
+    struct ovn_flow_table *flow_table;
+    struct hmapx *modified_flows;
+
+    if (lflow_table) {
+        flow_table = &br_ftable->lflow_table;
+        modified_flows = &br_ftable->modified_lflows;
+    } else {
+        flow_table = &br_ftable->pflow_table;
+        modified_flows = &br_ftable->modified_pflows;
+    }
+
+    struct flow_uuid_to_acts *f_uuid_to_acts =
+        flow_uuid_to_acts_lookup(&flow_table->uuid_action_flow_table,
+                                 flow_uuid);
+
+    if (!f_uuid_to_acts) {
+        return;
+    }
+
+    struct ovn_flow_action *a;
+    LIST_FOR_EACH_POP (a, flow_uuid_action_node, &f_uuid_to_acts->acts) {
+        struct ovn_flow *f = a->flow;
+        ovn_flow_unref_action__(a->flow, a);
+        ovn_flow_action_destroy(a);
+        hmapx_add(modified_flows, (void *) f);
+    }
+
+    hmap_remove(&flow_table->uuid_action_flow_table,
+                &f_uuid_to_acts->hmap_node);
+    free(f_uuid_to_acts);
+}
+
+/* ovn flow table functions. */
+static void
+ovn_flow_table_init(struct ovn_flow_table *flow_table)
+{
+    hmap_init(&flow_table->match_flow_table);
+    hmap_init(&flow_table->uuid_action_flow_table);
+}
+
+static void
+ovn_flow_table_clear(struct ovn_flow_table *flow_table)
+{
+    struct flow_uuid_to_acts *f_uuid_to_acts;
+    HMAP_FOR_EACH_POP (f_uuid_to_acts, hmap_node,
+                       &flow_table->uuid_action_flow_table) {
+        struct ovn_flow_action *a;
+        LIST_FOR_EACH_POP (a, flow_uuid_action_node,
+                           &f_uuid_to_acts->acts) {
+            ovn_flow_unref_action__(a->flow, a);
+            ovn_flow_action_destroy(a);
+        }
+
+        free(f_uuid_to_acts);
+    }
+
+    struct ovn_flow *f;
+    HMAP_FOR_EACH_POP (f, hmap_node, &flow_table->match_flow_table) {
+        ovn_flow_destroy(f);
+    }
+}
+
+static void
+ovn_flow_table_destroy(struct ovn_flow_table *flow_table)
+{
+    ovn_flow_table_clear(flow_table);
+    hmap_destroy(&flow_table->match_flow_table);
+    hmap_destroy(&flow_table->uuid_action_flow_table);
+}
+
+static uint32_t
+ovn_flow_match_hash__(uint8_t table_id, uint16_t priority,
+                       const struct minimatch *match)
+{
+    return hash_2words((table_id << 16) | priority,
+                       minimatch_hash(match, 0));
+}
+
+/* Returns a hash of the match key in 'f'. */
+static uint32_t
+ovn_flow_match_hash(const struct ovn_flow *f)
+{
+    return ovn_flow_match_hash__(f->table_id, f->priority, &f->match);
+}
+
+static void
+ovn_flow_action_init(struct ovn_flow_action *a, const struct ofpbuf *actions,
+                     uint64_t cookie, const struct uuid *flow_uuid)
+{
+    a->ofpacts = xmemdup(actions->data, actions->size);
+    a->ofpacts_len = actions->size;
+    a->cookie = cookie;
+    a->flow_uuid = *flow_uuid;
+    a->stale = false;
+    ovs_list_init(&a->list_node);
+}
+
+static void
+ovn_flow_action_destroy(struct ovn_flow_action *a)
+{
+    free(a->ofpacts);
+    free(a);
+}
+
+static void
+ovn_flow_init(struct ovn_flow *f, uint8_t table_id, uint16_t priority,
+              const struct match *match)
+{
+    f->table_id = table_id;
+    f->priority = priority;
+    minimatch_init(&f->match, match);
+
+    f->hash = ovn_flow_match_hash(f);
+
+    ovs_list_init(&f->allow_act_list);
+    ovs_list_init(&f->drop_act_list);
+    ovs_list_init(&f->conj_act_list);
+
+    f->installed_ofpacts = NULL;
+    f->installed_ofpacts_len = 0;
+
+    f->active_cookie = 0;
+}
+
+static struct ovn_flow *
+ovn_flow_alloc(uint8_t table_id, uint16_t priority,
+               const struct match *match)
+{
+    struct ovn_flow *f = xzalloc(sizeof *f);
+    ovn_flow_init(f, table_id, priority, match);
+
+    return f;
+}
+
+static struct ovn_flow_action *
+ovn_flow_action_alloc(const struct ofpbuf *actions,
+                      uint64_t cookie, const struct uuid *flow_uuid)
+{
+    struct ovn_flow_action *a = xzalloc(sizeof *a);
+    ovn_flow_action_init(a, actions, cookie, flow_uuid);
+
+    return a;
+}
+
+/* Finds and returns a ovn_flow in 'flow_table' whose key is identical to
+ * 'target''s key, or NULL if there is none.
+ */
+static struct ovn_flow *
+ovn_flow_lookup(struct ovn_flow_table *flow_table,
+                uint8_t table_id, uint16_t priority,
+                const struct minimatch *match)
+{
+    size_t hash = ovn_flow_match_hash__(table_id, priority, match);
+
+    struct ovn_flow *f;
+    HMAP_FOR_EACH_WITH_HASH (f, hmap_node, hash,
+                             &flow_table->match_flow_table) {
+        if (f->priority == priority && minimatch_equal(&f->match, match)) {
+            return f;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovn_flow_clear_actions_from_list(struct ovs_list *act_list)
+{
+    struct ovn_flow_action *a;
+    LIST_FOR_EACH_POP (a, list_node, act_list) {
+        ovs_list_remove(&a->flow_uuid_action_node);
+        ovn_flow_action_destroy(a);
+    }
+}
+
+static void
+ovn_flow_invalidate_actions_from_list(struct ovs_list *act_list)
+{
+    struct ovn_flow_action *a;
+    LIST_FOR_EACH (a, list_node, act_list) {
+        a->stale = true;
+    }
+}
+
+static void
+ovn_flow_clear_actions(struct ovn_flow *f)
+{
+    if (f->normal_act) {
+        ovs_list_remove(&f->normal_act->flow_uuid_action_node);
+        ovn_flow_action_destroy(f->normal_act);
+        f->normal_act = NULL;
+    }
+
+    if (!ovs_list_is_empty(&f->allow_act_list)) {
+        ovn_flow_clear_actions_from_list(&f->allow_act_list);
+    }
+
+    if (!ovs_list_is_empty(&f->drop_act_list)) {
+        ovn_flow_clear_actions_from_list(&f->drop_act_list);
+    }
+
+    if (!ovs_list_is_empty(&f->conj_act_list)) {
+        ovn_flow_clear_actions_from_list(&f->conj_act_list);
+    }
+}
+
+static void
+ovn_flow_invalidate_all_actions(struct ovn_flow *f)
+{
+    if (f->normal_act) {
+        f->normal_act->stale = true;
+    }
+
+    if (!ovs_list_is_empty(&f->allow_act_list)) {
+        ovn_flow_invalidate_actions_from_list(&f->allow_act_list);
+    }
+
+    if (!ovs_list_is_empty(&f->drop_act_list)) {
+        ovn_flow_invalidate_actions_from_list(&f->drop_act_list);
+    }
+
+    if (!ovs_list_is_empty(&f->conj_act_list)) {
+        ovn_flow_invalidate_actions_from_list(&f->conj_act_list);
+    }
+}
+
+static void
+ovn_flow_delete_stale_actions_from_list(struct ovs_list *act_list)
+{
+    struct ovn_flow_action *a, *next;
+    LIST_FOR_EACH_SAFE (a, next, list_node, act_list) {
+        if (a->stale) {
+            ovn_flow_unref_action(a->flow, a);
+        }
+    }
+}
+
+static void
+ovn_flow_delete_stale_actions(struct ovn_flow *f)
+{
+    if (f->normal_act && f->normal_act->stale) {
+        ovn_flow_unref_action(f, f->normal_act);
+        f->normal_act = NULL;
+    }
+
+    if (!ovs_list_is_empty(&f->allow_act_list)) {
+        ovn_flow_delete_stale_actions_from_list(&f->allow_act_list);
+    }
+
+    if (!ovs_list_is_empty(&f->drop_act_list)) {
+        ovn_flow_delete_stale_actions_from_list(&f->drop_act_list);
+    }
+
+    if (!ovs_list_is_empty(&f->conj_act_list)) {
+        ovn_flow_delete_stale_actions_from_list(&f->conj_act_list);
+    }
+}
+
+static void
+ovn_flow_uninit(struct ovn_flow *f)
+{
+    minimatch_destroy(&f->match);
+    ovn_flow_clear_actions(f);
+}
+
+static void
+ovn_flow_destroy(struct ovn_flow *f)
+{
+    if (f) {
+        ovn_flow_uninit(f);
+        free(f->installed_ofpacts);
+        free(f);
+    }
+}
+
+static bool
+ovn_flow_action_is_drop(const struct ovn_flow_action *f)
+{
+    return f->ofpacts_len == 0;
+}
+
+static bool
+ovn_flow_action_is_conj(const struct ovn_flow_action *f)
+{
+    const struct ofpact *a = NULL;
+
+    OFPACT_FOR_EACH (a, f->ofpacts, f->ofpacts_len) {
+        if (a->type == OFPACT_CONJUNCTION) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+ovn_flow_action_is_allow(const struct ovn_flow_action *f)
+{
+    if (ovn_flow_action_is_drop(f)) {
+        return false;
+    }
+
+    const struct ofpact *a = f->ofpacts;
+    return (a->type == OFPACT_RESUBMIT &&
+            ofpact_last(a, f->ofpacts, f->ofpacts_len));
+}
+
+static bool
+ovn_flow_action_is_normal(const struct ovn_flow_action *f)
+{
+    return (!ovn_flow_action_is_allow(f) && !ovn_flow_action_is_drop(f) &&
+            !ovn_flow_action_is_conj(f));
+}
+
+static struct ovn_flow_action *
+ovn_flow_action_get_matching_in_list(struct ovs_list *act_list,
+                                     struct ovn_flow_action *a)
+{
+    struct ovn_flow_action *act_in_list;
+    LIST_FOR_EACH (act_in_list, list_node, act_list) {
+        if (ofpacts_equal(act_in_list->ofpacts,
+                          act_in_list->ofpacts_len,
+                          a->ofpacts,
+                          a->ofpacts_len)) {
+            return act_in_list;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovn_flow_action *
+ovn_flow_get_matching_action(struct ovn_flow *f, struct ovn_flow_action *a)
+{
+    if (f->normal_act && ovn_flow_action_is_normal(a)) {
+        if (ofpacts_equal(f->normal_act->ofpacts,
+                         f->normal_act->ofpacts_len,
+                         a->ofpacts,
+                         a->ofpacts_len)) {
+            return f->normal_act;
+        }
+    }
+
+    if (ovn_flow_action_is_allow(a)) {
+        return ovn_flow_action_get_matching_in_list(&f->allow_act_list, a);
+    }
+
+    if (ovn_flow_action_is_drop(a)) {
+        return ovn_flow_action_get_matching_in_list(&f->drop_act_list, a);
+    }
+
+    if (ovn_flow_action_is_conj(a)) {
+        return ovn_flow_action_get_matching_in_list(&f->conj_act_list, a);
+    }
+
+    return NULL;
+}
+
+static bool
+ovn_flow_has_action(struct ovn_flow *f, struct ovn_flow_action *a)
+{
+    struct ovn_flow_action *ma = ovn_flow_get_matching_action(f, a);
+
+    if (ovn_flow_action_is_normal(a)) {
+        return ma ? true: false;
+    }
+
+    return ma ? uuid_equals(&ma->flow_uuid, &a->flow_uuid) : false;
+}
+
+static bool
+ovn_flow_has_active_actions(struct ovn_flow *f)
+{
+    if (f->normal_act) {
+        return true;
+    }
+
+    if (!ovs_list_is_empty(&f->allow_act_list)) {
+        return true;
+    }
+
+    if (!ovs_list_is_empty(&f->drop_act_list)) {
+        return true;
+    }
+
+    return !ovs_list_is_empty(&f->conj_act_list);
+}
+
+static struct flow_uuid_to_acts *
+flow_uuid_to_acts_lookup(struct hmap *uuid_action_table,
+                         const struct uuid *flow_uuid)
+{
+    struct flow_uuid_to_acts *f_uuid_to_acts;
+    HMAP_FOR_EACH_WITH_HASH (f_uuid_to_acts, hmap_node, uuid_hash(flow_uuid),
+                             uuid_action_table) {
+        if (uuid_equals(flow_uuid, &f_uuid_to_acts->flow_uuid)) {
+            return f_uuid_to_acts;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovn_flow_action_link_to_flow_uuid(struct hmap *uuid_action_table,
+                                  struct ovn_flow_action *a)
+{
+    struct flow_uuid_to_acts *f_uuid_to_acts;
+    f_uuid_to_acts = flow_uuid_to_acts_lookup(uuid_action_table,
+                                              &a->flow_uuid);
+    if (!f_uuid_to_acts) {
+        f_uuid_to_acts = xzalloc(sizeof *f_uuid_to_acts);
+        f_uuid_to_acts->flow_uuid = a->flow_uuid;
+        ovs_list_init(&f_uuid_to_acts->acts);
+        hmap_insert(uuid_action_table, &f_uuid_to_acts->hmap_node,
+                    uuid_hash(&a->flow_uuid));
+    }
+
+    ovs_list_push_back(&f_uuid_to_acts->acts, &a->flow_uuid_action_node);
+}
+
+static void
+ovn_flow_unref_action__(struct ovn_flow *f, struct ovn_flow_action *a)
+{
+    if (f->normal_act == a) {
+        f->normal_act = NULL;
+    } else if (!ovs_list_is_empty(&a->list_node)) {
+        ovs_list_remove(&a->list_node);
+    }
+}
+
+static void
+ovn_flow_unref_action(struct ovn_flow *f, struct ovn_flow_action *a)
+{
+    ovs_list_remove(&a->flow_uuid_action_node);
+    ovn_flow_unref_action__(f, a);
+    ovn_flow_action_destroy(a);
+}
+
+static bool
+ovn_flow_update_actions(struct ovn_flow_table *ftable,
+                        struct ovn_flow *f, struct ovn_flow_action *a)
+{
+    bool flow_updated = true;
+    if (ovn_flow_action_is_normal(a)) {
+        if (f->normal_act) {
+            ovn_flow_unref_action(f, f->normal_act);
+        }
+        f->normal_act = a;
+    } else if (ovn_flow_action_is_allow(a)) {
+        flow_updated = ovs_list_is_empty(&f->allow_act_list);
+        ovs_list_push_back(&f->allow_act_list, &a->list_node);
+    } else if (ovn_flow_action_is_drop(a)) {
+        flow_updated = ovs_list_is_empty(&f->drop_act_list);
+        ovs_list_push_back(&f->drop_act_list, &a->list_node);
+    } else {
+        /* conj action. */
+        ovs_list_push_back(&f->conj_act_list, &a->list_node);
+    }
+
+    a->flow = f;
+    a->stale = false;
+
+    ovn_flow_action_link_to_flow_uuid(&ftable->uuid_action_flow_table, a);
+
+    return flow_updated;
+}
+
+static bool
+ovn_flow_needs_flow_mod(struct ovn_flow *f, struct ovn_flow_action *a)
+{
+    if (f->installed_ofpacts_len != a->ofpacts_len) {
+        return true;
+    }
+
+    if (f->installed_ofpacts_len && a->ofpacts_len) {
+        return !ofpacts_equal(f->installed_ofpacts,
+                              f->installed_ofpacts_len,
+                              a->ofpacts,
+                              a->ofpacts_len);
+    }
+
+    return f->active_cookie ? false : true;
+}
+
+static void
+ovn_flow_prepare_ofmsg(struct ovn_flow *f, struct ovs_list *msgs)
+{
+    struct ovn_flow_action *a = NULL;
+    struct ovn_flow_action *conj_act = NULL;
+
+    if (f->normal_act) {
+        a = f->normal_act;
+    } else if (!ovs_list_is_empty(&f->allow_act_list)) {
+        a = CONTAINER_OF(ovs_list_front(&f->allow_act_list),
+                         struct ovn_flow_action,
+                         list_node);
+    } else if (!ovs_list_is_empty(&f->drop_act_list)) {
+        a = CONTAINER_OF(ovs_list_front(&f->drop_act_list),
+                         struct ovn_flow_action,
+                         list_node);
+    } else if (!ovs_list_is_empty(&f->conj_act_list)) {
+        struct ovn_flow_action *c;
+        uint64_t ofpacts_stub[1024 / 8];
+        struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+
+        LIST_FOR_EACH (c, list_node, &f->conj_act_list) {
+            ofpbuf_put(&ofpacts, c->ofpacts, c->ofpacts_len);
+        }
+        conj_act = xzalloc(sizeof *conj_act);
+        conj_act->cookie = CONJ_ACT_COOKIE;
+        conj_act->ofpacts = xmemdup(ofpacts.data, ofpacts.size);
+        conj_act->ofpacts_len = ofpacts.size;
+        ofpbuf_uninit(&ofpacts);
+        a = conj_act;
+    }
+
+    if (a) {
+        /* check if there is really a need to install or not. */
+        if (ovn_flow_needs_flow_mod(f, a)) {
+            if (f->active_cookie == 0) {
+                ovn_flow_add_oflow(f, a, msgs);
+            } else {
+                ovn_flow_mod_oflow(f, a, msgs);
+            }
+            free(f->installed_ofpacts);
+            if (a->ofpacts_len) {
+                f->installed_ofpacts = xmemdup(a->ofpacts, a->ofpacts_len);
+                f->installed_ofpacts_len = a->ofpacts_len;
+            } else {
+                f->installed_ofpacts = NULL;
+                f->installed_ofpacts_len = 0;
+            }
+        }
+    } else {
+        ovn_flow_del_oflow(f, msgs);
+        free(f->installed_ofpacts);
+        f->installed_ofpacts = NULL;
+        f->installed_ofpacts_len = 0;
+        f->active_cookie = 0;
+    }
+
+    if (conj_act) {
+        free(conj_act->ofpacts);
+        free(conj_act);
+    }
+}
+
+static void
+ovn_flow_handle_modified(struct ovn_flow *f, struct ovs_list *msgs)
+{
+    ovn_flow_delete_stale_actions(f);
+    ovn_flow_prepare_ofmsg(f, msgs);
+    if (!ovn_flow_has_active_actions(f)) {
+        hmap_remove(f->match_flow_table, &f->hmap_node);
+        ovs_list_remove(&f->flow_list_node);
+        ovn_flow_destroy(f);
+    }
+}
+
+static void
+br_flow_populate_oflow_msgs__(struct br_flow_table *br_ftable,
+                              struct ovs_list *msgs)
+{
+    struct ovn_flow *f;
+
+    LIST_FOR_EACH_POP (f, flow_list_node, br_ftable->old_lflows) {
+        ovn_flow_clear_actions(f);
+        ovn_flow_prepare_ofmsg(f, msgs);
+        hmap_remove(f->match_flow_table, &f->hmap_node);
+        hmapx_find_and_delete(&br_ftable->modified_lflows, f);
+        ovn_flow_destroy(f);
+    }
+
+    LIST_FOR_EACH_POP (f, flow_list_node, br_ftable->old_pflows) {
+        ovn_flow_clear_actions(f);
+        ovn_flow_prepare_ofmsg(f, msgs);
+        hmap_remove(f->match_flow_table, &f->hmap_node);
+        hmapx_find_and_delete(&br_ftable->modified_pflows, f);
+        ovn_flow_destroy(f);
+    }
+
+    struct hmapx_node *node;
+    HMAPX_FOR_EACH (node, &br_ftable->modified_lflows) {
+        f = node->data;
+        ovn_flow_handle_modified(f, msgs);
+    }
+
+    hmapx_clear(&br_ftable->modified_lflows);
+
+    HMAPX_FOR_EACH (node, &br_ftable->modified_pflows) {
+        f = node->data;
+        ovn_flow_handle_modified(f, msgs);
+    }
+
+    hmapx_clear(&br_ftable->modified_pflows);
+}
+
+/* Flow table update. */
+
+static struct ofpbuf *
+encode_flow_mod(struct ofputil_flow_mod *fm)
+{
+    fm->buffer_id = UINT32_MAX;
+    fm->out_port = OFPP_ANY;
+    fm->out_group = OFPG_ANY;
+    return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF15_OXM);
+}
+
+static void
+add_flow_mod(struct ofputil_flow_mod *fm, struct ovs_list *msgs)
+{
+    struct ofpbuf *msg = encode_flow_mod(fm);
+    ovs_list_push_back(msgs, &msg->list_node);
+}
+
+static void
+ovn_flow_add_oflow(struct ovn_flow *f, struct ovn_flow_action *a,
+                   struct ovs_list *msgs)
+{
+    /* Send flow_mod to add flow. */
+    struct ofputil_flow_mod fm = {
+        .match = f->match,
+        .priority = f->priority,
+        .table_id = f->table_id,
+        .ofpacts = a->ofpacts,
+        .ofpacts_len = a->ofpacts_len,
+        .new_cookie = htonll(a->cookie),
+        .command = OFPFC_ADD,
+    };
+    add_flow_mod(&fm, msgs);
+    f->active_cookie = a->cookie;
+}
+
+static void
+ovn_flow_mod_oflow(struct ovn_flow *f, struct ovn_flow_action *a,
+                   struct ovs_list *msgs)
+{
+    /* Update actions in installed flow. */
+    struct ofputil_flow_mod fm = {
+        .match = f->match,
+        .priority = f->priority,
+        .table_id = f->table_id,
+        .ofpacts = a->ofpacts,
+        .ofpacts_len = a->ofpacts_len,
+        .command = OFPFC_MODIFY_STRICT,
+    };
+    /* Update cookie if it is changed. */
+    if (f->active_cookie != a->cookie) {
+        fm.modify_cookie = true;
+        fm.new_cookie = htonll(a->cookie);
+        /* Use OFPFC_ADD so that cookie can be updated. */
+        fm.command = OFPFC_ADD;
+    }
+    add_flow_mod(&fm, msgs);
+    f->active_cookie = a->cookie;
+}
+
+static void
+ovn_flow_del_oflow(struct ovn_flow *f, struct ovs_list *msgs)
+{
+    struct ofputil_flow_mod fm = {
+        .match = f->match,
+        .priority = f->priority,
+        .table_id = f->table_id,
+        .command = OFPFC_DELETE_STRICT,
+    };
+    add_flow_mod(&fm, msgs);
+    f->active_cookie = 0;
+}
+
+static char *
+ovn_flow_to_string(const struct ovn_flow *f)
+{
+    struct ds s = DS_EMPTY_INITIALIZER;
+
+    struct ovn_flow_action *a = NULL;
+    struct ovn_flow_action *conj_act = NULL;
+
+    if (f->normal_act) {
+        a = f->normal_act;
+    } else if (!ovs_list_is_empty(&f->allow_act_list)) {
+        a = CONTAINER_OF(ovs_list_front(&f->allow_act_list),
+                         struct ovn_flow_action,
+                         list_node);
+    } else if (!ovs_list_is_empty(&f->drop_act_list)) {
+        a = CONTAINER_OF(ovs_list_front(&f->drop_act_list),
+                         struct ovn_flow_action,
+                         list_node);
+    } else if (!ovs_list_is_empty(&f->conj_act_list)) {
+        struct ovn_flow_action *c;
+        uint64_t ofpacts_stub[1024 / 8];
+        struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+
+        LIST_FOR_EACH (c, list_node, &f->conj_act_list) {
+            ofpbuf_put(&ofpacts, c->ofpacts, c->ofpacts_len);
+        }
+        conj_act = xzalloc(sizeof *conj_act);
+        conj_act->cookie = CONJ_ACT_COOKIE;
+        conj_act->ofpacts = xmemdup(ofpacts.data, ofpacts.size);
+        conj_act->ofpacts_len = ofpacts.size;
+        ofpbuf_uninit(&ofpacts);
+        a = conj_act;
+    }
+
+    if (a) {
+        ds_put_format(&s, "cookie=%"PRIx64", ", a->cookie);
+    }
+    ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
+    ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
+    minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
+    ds_put_cstr(&s, ", actions=");
+    struct ofpact_format_params fp = { .s = &s };
+    if (a) {
+        ofpacts_format(a->ofpacts, a->ofpacts_len, &fp);
+    } else {
+        ds_put_cstr(&s, "<EMPTY>");
+    }
+
+    if (conj_act) {
+        free(conj_act->ofpacts);
+        free(conj_act);
+    }
+    return ds_steal_cstr(&s);
+}
+
+static void
+ovn_flow_log(const struct ovn_flow *f)
+{
+    if (VLOG_IS_DBG_ENABLED()) {
+        char *s = ovn_flow_to_string(f);
+        VLOG_DBG("flow: %s", s);
+        free(s);
+    }
+}
diff --git a/br-controller/br-flow-mgr.h b/br-controller/br-flow-mgr.h
new file mode 100644
index 0000000000..68545941a9
--- /dev/null
+++ b/br-controller/br-flow-mgr.h
@@ -0,0 +1,65 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BR_FLOW_MGR_H
+#define BR_FLOW_MGR_H 1
+
+#include <stdio.h>
+
+/* OVS includes. */
+#include "openvswitch/hmap.h"
+#include "openvswitch/list.h"
+
+/* OVN includes. */
+
+struct match;
+struct ofpbuf;
+struct uuid;
+
+#define DP_FLOW_TABLE_GLOBAL_KEY 0
+
+void br_flow_tables_init(void);
+void br_flow_tables_destroy(void);
+
+struct br_flow_table *br_flow_table_alloc(const char *bridge);
+struct br_flow_table *br_flow_table_get(const char *bridge);
+void br_flow_table_destroy(const char *bridge);
+
+void br_flow_switch_logical_oflow_tables(void);
+void br_flow_switch_logical_oflow_table(const char *bridge);
+void br_flow_switch_physical_oflow_tables(void);
+void br_flow_switch_physical_oflow_table(const char *bridge);
+
+void br_flow_add_logical_oflow(const char *bridge, uint8_t table_id,
+                               uint16_t priority, uint64_t cookie,
+                               const struct match *match,
+                               const struct ofpbuf *actions,
+                               const struct uuid *flow_uuid);
+void br_flow_add_physical_oflow(const char *bridge, uint8_t table_id,
+                                uint16_t priority, uint64_t cookie,
+                                const struct match *match,
+                                const struct ofpbuf *actions,
+                                const struct uuid *flow_uuid);
+
+void br_flow_remove_logical_oflows_all(const struct uuid *flow_uuid);
+void br_flow_remove_logical_oflows(const char *bridge,
+                                   const struct uuid *flow_uuid);
+void br_flow_remove_physical_oflows(const char *bridge,
+                                    const struct uuid *flow_uuid);
+void br_flow_flush_oflows(const char *bridge);
+void br_flow_flush_all_oflows(void);
+
+void br_flow_populate_oflow_msgs(const char *bridge, struct ovs_list *msgs);
+
+#endif /* BR_FLOW_MGR_H */
-- 
2.50.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to