diff mbox series

[ovs-dev,3/4] controller: Merge the mac-cache and mac-learn.

Message ID 20240422073602.258855-4-amusil@redhat.com
State New
Headers show
Series Mac cache handling refactor | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Ales Musil April 22, 2024, 7:36 a.m. UTC
Merge mac-cache and mac-learn into single module. Both of those
modules contained very similar functionality with some small
differences. By merging those we have unified interface to deal
with FDB and MAC binding.

Signed-off-by: Ales Musil <amusil@redhat.com>
---
 controller/automake.mk      |   2 -
 controller/mac-cache.c      | 588 ++++++++++++++++++++++++------------
 controller/mac-cache.h      | 165 +++++++---
 controller/mac-learn.c      | 482 -----------------------------
 controller/mac-learn.h      | 145 ---------
 controller/ovn-controller.c | 104 +++++--
 controller/pinctrl.c        | 165 +++++-----
 controller/statctrl.c       |   5 +-
 8 files changed, 697 insertions(+), 959 deletions(-)
 delete mode 100644 controller/mac-learn.c
 delete mode 100644 controller/mac-learn.h
diff mbox series

Patch

diff --git a/controller/automake.mk b/controller/automake.mk
index 2eeca718a..1b1b3aeb1 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -36,8 +36,6 @@  controller_ovn_controller_SOURCES = \
 	controller/ovn-controller.h \
 	controller/physical.c \
 	controller/physical.h \
-	controller/mac-learn.c \
-	controller/mac-learn.h \
 	controller/local_data.c \
 	controller/local_data.h \
 	controller/ovsport.h \
diff --git a/controller/mac-cache.c b/controller/mac-cache.c
index 1515e0ec2..c52f913ce 100644
--- a/controller/mac-cache.c
+++ b/controller/mac-cache.c
@@ -25,29 +25,20 @@ 
 
 VLOG_DEFINE_THIS_MODULE(mac_cache);
 
+#define MAX_BUFFERED_PACKETS        1000
+#define BUFFER_QUEUE_DEPTH          4
+#define BUFFERED_PACKETS_TIMEOUT_MS 10000
+#define BUFFERED_PACKETS_LOOKUP_MS  100
+
 static uint32_t
-mac_cache_mb_data_hash(const struct mac_cache_mb_data *mb_data);
+mac_binding_data_hash(const struct mac_binding_data *mb_data);
 static inline bool
-mac_cache_mb_data_equals(const struct mac_cache_mb_data *a,
-                          const struct mac_cache_mb_data *b);
-static struct mac_cache_mac_binding *
-mac_cache_mac_binding_find(struct mac_cache_data *data,
-                           const struct mac_cache_mb_data *mb_data);
-static bool
-mac_cache_mb_data_from_sbrec(struct mac_cache_mb_data *data,
-                              const struct sbrec_mac_binding *mb,
-                              struct ovsdb_idl_index *sbrec_pb_by_name);
+mac_binding_data_equals(const struct mac_binding_data *a,
+                        const struct mac_binding_data *b);
 static uint32_t
-mac_cache_fdb_data_hash(const struct mac_cache_fdb_data *fdb_data);
+fdb_data_hash(const struct fdb_data *fdb_data);
 static inline bool
-mac_cache_fdb_data_equals(const struct mac_cache_fdb_data *a,
-                          const struct mac_cache_fdb_data *b);
-static bool
-mac_cache_fdb_data_from_sbrec(struct mac_cache_fdb_data *data,
-                              const struct sbrec_fdb *fdb);
-static struct mac_cache_fdb *
-mac_cache_fdb_find(struct mac_cache_data *data,
-                   const struct mac_cache_fdb_data *fdb_data);
+fdb_data_equals(const struct fdb_data *a, const struct fdb_data *b);
 static struct mac_cache_threshold *
 mac_cache_threshold_find(struct hmap *thresholds, const struct uuid *uuid);
 static uint64_t
@@ -59,6 +50,23 @@  mac_cache_threshold_remove(struct hmap *thresholds,
 static void
 mac_cache_update_req_delay(struct hmap *thresholds, uint64_t *req_delay);
 
+static struct buffered_packets *
+buffered_packets_find(struct buffered_packets_ctx *ctx,
+                      const struct mac_binding_data *mb_data);
+
+static void
+buffered_packets_remove(struct buffered_packets_ctx *ctx,
+                        struct buffered_packets *bp);
+
+static void
+buffered_packets_db_lookup(struct buffered_packets *bp,
+                           struct ds *ip, struct eth_addr *mac,
+                           struct ovsdb_idl_index *sbrec_pb_by_key,
+                           struct ovsdb_idl_index *sbrec_dp_by_key,
+                           struct ovsdb_idl_index *sbrec_pb_by_name,
+                           struct ovsdb_idl_index *sbrec_mb_by_lport_ip);
+
+/* Thresholds. */
 bool
 mac_cache_threshold_add(struct mac_cache_data *data,
                         const struct sbrec_datapath_binding *dp,
@@ -113,50 +121,78 @@  mac_cache_thresholds_clear(struct mac_cache_data *data)
     }
 }
 
-void
-mac_cache_mac_binding_add(struct mac_cache_data *data,
-                           const struct sbrec_mac_binding *mb,
-                           struct ovsdb_idl_index *sbrec_pb_by_name)
-{
-    struct mac_cache_mb_data mb_data;
-    if (!mac_cache_mb_data_from_sbrec(&mb_data, mb, sbrec_pb_by_name)) {
-        return;
-    }
+/* MAC binding. */
+struct mac_binding *
+mac_binding_add(struct hmap *map, struct mac_binding_data mb_data,
+                long long timestamp) {
 
-    struct mac_cache_mac_binding *mc_mb = mac_cache_mac_binding_find(data,
-                                                                     &mb_data);
-    if (!mc_mb) {
-        mc_mb = xmalloc(sizeof *mc_mb);
-        hmap_insert(&data->mac_bindings, &mc_mb->hmap_node,
-                    mac_cache_mb_data_hash(&mb_data));
+    struct mac_binding *mb = mac_binding_find(map, &mb_data);
+    if (!mb) {
+        mb = xmalloc(sizeof *mb);
+        mb->sbrec_mb = NULL;
+        hmap_insert(map, &mb->hmap_node, mac_binding_data_hash(&mb_data));
     }
 
-    mc_mb->sbrec_mb = mb;
-    mc_mb->data = mb_data;
+    mb->data = mb_data;
+    mb->timestamp = timestamp;
+
+    return mb;
 }
 
 void
-mac_cache_mac_binding_remove(struct mac_cache_data *data,
-                             const struct sbrec_mac_binding *mb,
-                             struct ovsdb_idl_index *sbrec_pb_by_name)
-{
-    struct mac_cache_mb_data mb_data;
-    if (!mac_cache_mb_data_from_sbrec(&mb_data, mb, sbrec_pb_by_name)) {
-        return;
+mac_binding_remove(struct hmap *map, struct mac_binding *mb) {
+    hmap_remove(map, &mb->hmap_node);
+    free(mb);
+}
+
+struct mac_binding *
+mac_binding_find(const struct hmap *map,
+                 const struct mac_binding_data *mb_data) {
+    uint32_t hash = mac_binding_data_hash(mb_data);
+
+    struct mac_binding *mb;
+    HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, map) {
+        if (mac_binding_data_equals(&mb->data, mb_data)) {
+            return mb;
+        }
     }
 
-    struct mac_cache_mac_binding *mc_mb = mac_cache_mac_binding_find(data,
-                                                                     &mb_data);
-    if (!mc_mb) {
-        return;
+    return NULL;
+}
+
+bool
+mac_binding_data_from_sbrec(struct mac_binding_data *data,
+                            const struct sbrec_mac_binding *mb,
+                            struct ovsdb_idl_index *sbrec_pb_by_name)
+{
+    const struct sbrec_port_binding *pb =
+            lport_lookup_by_name(sbrec_pb_by_name, mb->logical_port);
+
+    if (!pb || !pb->datapath || !ip46_parse(mb->ip, &data->ip) ||
+        !eth_addr_from_string(mb->mac, &data->mac)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "Couldn't parse MAC binding: ip=%s, mac=%s, "
+                     "logical_port=%s", mb->ip, mb->mac, mb->logical_port);
+        return false;
     }
 
-    hmap_remove(&data->mac_bindings, &mc_mb->hmap_node);
-    free(mc_mb);
+    data->dp_key = mb->datapath->tunnel_key;
+    data->port_key = pb->tunnel_key;
+
+    return true;
+}
+
+void
+mac_bindings_clear(struct hmap *map)
+{
+    struct mac_binding *mb;
+    HMAP_FOR_EACH_POP (mb, hmap_node, map) {
+        free(mb);
+    }
 }
 
 bool
-mac_cache_sb_mac_binding_updated(const struct sbrec_mac_binding *mb)
+sb_mac_binding_updated(const struct sbrec_mac_binding *mb)
 {
     bool updated = false;
     for (size_t i = 0; i < SBREC_MAC_BINDING_N_COLUMNS; i++) {
@@ -171,56 +207,78 @@  mac_cache_sb_mac_binding_updated(const struct sbrec_mac_binding *mb)
     return updated || sbrec_mac_binding_is_deleted(mb);
 }
 
-void
-mac_cache_mac_bindings_clear(struct mac_cache_data *data)
-{
-    struct mac_cache_mac_binding *mc_mb;
-    HMAP_FOR_EACH_POP (mc_mb, hmap_node, &data->mac_bindings) {
-        free(mc_mb);
+const struct sbrec_mac_binding *
+mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                   const char *logical_port, const char *ip) {
+    struct sbrec_mac_binding *mb =
+            sbrec_mac_binding_index_init_row(sbrec_mac_binding_by_lport_ip);
+    sbrec_mac_binding_index_set_logical_port(mb, logical_port);
+    sbrec_mac_binding_index_set_ip(mb, ip);
+
+    const struct sbrec_mac_binding *retval =
+            sbrec_mac_binding_index_find(sbrec_mac_binding_by_lport_ip, mb);
+
+    sbrec_mac_binding_index_destroy_row(mb);
+
+    return retval;
+}
+
+/* FDB. */
+struct fdb *
+fdb_add(struct hmap *map, struct fdb_data fdb_data) {
+    struct fdb *fdb = fdb_find(map, &fdb_data);
+
+    if (!fdb) {
+        fdb = xmalloc(sizeof *fdb);
+        fdb->sbrec_fdb = NULL;
+        fdb->dp_uuid = UUID_ZERO;
+        hmap_insert(map, &fdb->hmap_node, fdb_data_hash(&fdb_data));
     }
+
+    fdb->data = fdb_data;
+
+    return fdb;
 }
 
 void
-mac_cache_fdb_add(struct mac_cache_data *data, const struct sbrec_fdb *fdb,
-                  struct uuid dp_uuid)
+fdb_remove(struct hmap *map, struct fdb *fdb)
 {
-    struct mac_cache_fdb_data fdb_data;
-    if (!mac_cache_fdb_data_from_sbrec(&fdb_data, fdb)) {
-        return;
-    }
-
-    struct mac_cache_fdb *mc_fdb = mac_cache_fdb_find(data, &fdb_data);
+    hmap_remove(map, &fdb->hmap_node);
+    free(fdb);
+}
 
-    if (!mc_fdb) {
-        mc_fdb = xmalloc(sizeof *mc_fdb);
-        hmap_insert(&data->fdbs, &mc_fdb->hmap_node,
-                    mac_cache_fdb_data_hash(&fdb_data));
+bool
+fdb_data_from_sbrec(struct fdb_data *data, const struct sbrec_fdb *fdb)
+{
+    if (!eth_addr_from_string(fdb->mac, &data->mac)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "Couldn't parse FDB: mac=%s", fdb->mac);
+        return false;
     }
 
-    mc_fdb->sbrec_fdb = fdb;
-    mc_fdb->data = fdb_data;
-    mc_fdb->dp_uuid = dp_uuid;
+    data->dp_key = fdb->dp_key;
+    data->port_key = fdb->port_key;
+
+    return true;
 }
 
-void
-mac_cache_fdb_remove(struct mac_cache_data *data, const struct sbrec_fdb *fdb)
+struct fdb *
+fdb_find(const struct hmap *map, const struct fdb_data *fdb_data)
 {
-    struct mac_cache_fdb_data fdb_data;
-    if (!mac_cache_fdb_data_from_sbrec(&fdb_data, fdb)) {
-        return;
-    }
+    uint32_t hash = fdb_data_hash(fdb_data);
 
-    struct mac_cache_fdb *mc_fdb = mac_cache_fdb_find(data, &fdb_data);
-    if (!mc_fdb) {
-        return;
+    struct fdb *fdb;
+    HMAP_FOR_EACH_WITH_HASH (fdb, hmap_node, hash, map) {
+        if (fdb_data_equals(&fdb->data, fdb_data)) {
+            return fdb;
+        }
     }
 
-    hmap_remove(&data->fdbs, &mc_fdb->hmap_node);
-    free(mc_fdb);
+    return NULL;
 }
 
 bool
-mac_cache_sb_fdb_updated(const struct sbrec_fdb *fdb)
+sb_fdb_updated(const struct sbrec_fdb *fdb)
 {
     bool updated = false;
     for (size_t i = 0; i < SBREC_FDB_N_COLUMNS; i++) {
@@ -236,11 +294,11 @@  mac_cache_sb_fdb_updated(const struct sbrec_fdb *fdb)
 }
 
 void
-mac_cache_fdbs_clear(struct mac_cache_data *data)
+fdbs_clear(struct hmap *map)
 {
-    struct mac_cache_fdb *mc_fdb;
-    HMAP_FOR_EACH_POP (mc_fdb, hmap_node, &data->fdbs) {
-        free(mc_fdb);
+    struct fdb *fdb;
+    HMAP_FOR_EACH_POP (fdb, hmap_node, map) {
+        free(fdb);
     }
 }
 
@@ -251,20 +309,21 @@  struct mac_cache_stats {
 
     union {
         /* Common data to identify MAC binding. */
-        struct mac_cache_mb_data mb;
+        struct mac_binding_data mb;
         /* Common data to identify FDB. */
-        struct mac_cache_fdb_data fdb;
+        struct fdb_data fdb;
     } data;
 };
 
+/* MAC binding stat processing. */
 void
-mac_cache_mb_stats_process_flow_stats(struct ovs_list *stats_list,
-                                      struct ofputil_flow_stats *ofp_stats)
+mac_binding_stats_process_flow_stats(struct ovs_list *stats_list,
+                                     struct ofputil_flow_stats *ofp_stats)
 {
     struct mac_cache_stats *stats = xmalloc(sizeof *stats);
 
     stats->idle_age_ms = ofp_stats->idle_age * 1000;
-    stats->data.mb = (struct mac_cache_mb_data) {
+    stats->data.mb = (struct mac_binding_data) {
         .port_key = ofp_stats->match.flow.regs[MFF_LOG_INPORT - MFF_REG0],
         .dp_key = ntohll(ofp_stats->match.flow.metadata),
         .mac = ofp_stats->match.flow.dl_src
@@ -280,8 +339,8 @@  mac_cache_mb_stats_process_flow_stats(struct ovs_list *stats_list,
 }
 
 void
-mac_cache_mb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-                       void *data)
+mac_binding_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
+                      void *data)
 {
     struct mac_cache_data *cache_data = data;
     struct hmap *thresholds = &cache_data->thresholds[MAC_CACHE_MAC_BINDING];
@@ -289,14 +348,14 @@  mac_cache_mb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
 
     struct mac_cache_stats *stats;
     LIST_FOR_EACH_POP (stats, list_node, stats_list) {
-        struct mac_cache_mac_binding *mc_mb =
-                mac_cache_mac_binding_find(cache_data, &stats->data.mb);
-        if (!mc_mb) {
+        struct mac_binding *mb = mac_binding_find(&cache_data->mac_bindings,
+                                                  &stats->data.mb);
+        if (!mb) {
             free(stats);
             continue;
         }
 
-        struct uuid *dp_uuid = &mc_mb->sbrec_mb->datapath->header_.uuid;
+        struct uuid *dp_uuid = &mb->sbrec_mb->datapath->header_.uuid;
         struct mac_cache_threshold *threshold =
                 mac_cache_threshold_find(thresholds, dp_uuid);
 
@@ -304,9 +363,9 @@  mac_cache_mb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
          * used on this chassis. Also make sure that we don't update the
          * timestamp more than once during the dump period. */
         if (stats->idle_age_ms < threshold->value &&
-            (timewall_now - mc_mb->sbrec_mb->timestamp) >=
+                (timewall_now - mb->sbrec_mb->timestamp) >=
             threshold->dump_period) {
-            sbrec_mac_binding_set_timestamp(mc_mb->sbrec_mb, timewall_now);
+            sbrec_mac_binding_set_timestamp(mb->sbrec_mb, timewall_now);
         }
 
         free(stats);
@@ -315,14 +374,15 @@  mac_cache_mb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
     mac_cache_update_req_delay(thresholds, req_delay);
 }
 
+/* FDB stat processing. */
 void
-mac_cache_fdb_stats_process_flow_stats(struct ovs_list *stats_list,
-                                       struct ofputil_flow_stats *ofp_stats)
+fdb_stats_process_flow_stats(struct ovs_list *stats_list,
+                             struct ofputil_flow_stats *ofp_stats)
 {
     struct mac_cache_stats *stats = xmalloc(sizeof *stats);
 
     stats->idle_age_ms = ofp_stats->idle_age * 1000;
-    stats->data.fdb = (struct mac_cache_fdb_data) {
+    stats->data.fdb = (struct fdb_data) {
             .port_key = ofp_stats->match.flow.regs[MFF_LOG_INPORT - MFF_REG0],
             .dp_key = ntohll(ofp_stats->match.flow.metadata),
             .mac = ofp_stats->match.flow.dl_src
@@ -332,8 +392,8 @@  mac_cache_fdb_stats_process_flow_stats(struct ovs_list *stats_list,
 }
 
 void
-mac_cache_fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-                        void *data)
+fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
+              void *data)
 {
     struct mac_cache_data *cache_data = data;
     struct hmap *thresholds = &cache_data->thresholds[MAC_CACHE_FDB];
@@ -341,22 +401,22 @@  mac_cache_fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
 
     struct mac_cache_stats *stats;
     LIST_FOR_EACH_POP (stats, list_node, stats_list) {
-        struct mac_cache_fdb *mc_fdb = mac_cache_fdb_find(cache_data,
-                                                          &stats->data.fdb);
-        if (!mc_fdb) {
+        struct fdb *fdb = fdb_find(&cache_data->fdbs, &stats->data.fdb);
+
+        if (!fdb) {
             free(stats);
             continue;
         }
 
         struct mac_cache_threshold *threshold =
-                mac_cache_threshold_find(thresholds, &mc_fdb->dp_uuid);
+                mac_cache_threshold_find(thresholds, &fdb->dp_uuid);
         /* If "idle_age" is under threshold it means that the mac binding is
          * used on this chassis. Also make sure that we don't update the
          * timestamp more than once during the dump period. */
         if (stats->idle_age_ms < threshold->value &&
-            (timewall_now - mc_fdb->sbrec_fdb->timestamp) >=
+                (timewall_now - fdb->sbrec_fdb->timestamp) >=
             threshold->dump_period) {
-            sbrec_fdb_set_timestamp(mc_fdb->sbrec_fdb, timewall_now);
+            sbrec_fdb_set_timestamp(fdb->sbrec_fdb, timewall_now);
         }
 
         free(stats);
@@ -365,130 +425,207 @@  mac_cache_fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
     mac_cache_update_req_delay(thresholds, req_delay);
 }
 
+/* Packet buffering. */
+struct bp_packet_data *
+bp_packet_data_create(const struct ofputil_packet_in *pin,
+                      const struct ofpbuf *continuation) {
+    struct bp_packet_data *pd = xmalloc(sizeof *pd);
+
+    pd->pin = (struct ofputil_packet_in) {
+            .packet = xmemdup(pin->packet, pin->packet_len),
+            .packet_len = pin->packet_len,
+            .flow_metadata = pin->flow_metadata,
+            .reason = pin->reason,
+            .table_id = pin->table_id,
+            .cookie = pin->cookie,
+            /* Userdata are empty on purpose,
+             * it is not needed for the continuation. */
+            .userdata = NULL,
+            .userdata_len = 0,
+    };
+    pd->continuation = ofpbuf_clone(continuation);
+
+    return pd;
+}
+
+
 void
-mac_cache_stats_destroy(struct ovs_list *stats_list)
-{
-    struct mac_cache_stats *stats;
-    LIST_FOR_EACH_POP (stats, list_node, stats_list) {
-        free(stats);
-    }
+bp_packet_data_destroy(struct bp_packet_data *pd) {
+    free(pd->pin.packet);
+    ofpbuf_delete(pd->continuation);
+    free(pd);
 }
 
-static uint32_t
-mac_cache_mb_data_hash(const struct mac_cache_mb_data *mb_data)
-{
-    uint32_t hash = 0;
+struct buffered_packets *
+buffered_packets_add(struct buffered_packets_ctx *ctx,
+                     struct mac_binding_data mb_data) {
+    uint32_t hash = mac_binding_data_hash(&mb_data);
 
-    hash = hash_add(hash, mb_data->port_key);
-    hash = hash_add(hash, mb_data->dp_key);
-    hash = hash_add_in6_addr(hash, &mb_data->ip);
-    hash = hash_add64(hash, eth_addr_to_uint64(mb_data->mac));
+    struct buffered_packets *bp = buffered_packets_find(ctx, &mb_data);
+    if (!bp) {
+        if (hmap_count(&ctx->buffered_packets) >= MAX_BUFFERED_PACKETS) {
+            return NULL;
+        }
+
+        bp = xmalloc(sizeof *bp);
+        hmap_insert(&ctx->buffered_packets, &bp->hmap_node, hash);
+        bp->mb_data = mb_data;
+        /* Schedule the freshly added buffered packet to do lookup
+         * immediately. */
+        bp->lookup_at_ms = 0;
+        ovs_list_init(&bp->queue);
+    }
 
-    return hash_finish(hash, 32);
+    bp->expire_at_ms = time_msec() + BUFFERED_PACKETS_TIMEOUT_MS;
+
+    return bp;
 }
 
-static inline bool
-mac_cache_mb_data_equals(const struct mac_cache_mb_data *a,
-                          const struct mac_cache_mb_data *b)
-{
-    return a->port_key == b->port_key &&
-           a->dp_key == b->dp_key &&
-           ipv6_addr_equals(&a->ip, &b->ip) &&
-           eth_addr_equals(a->mac, b->mac);
+void
+buffered_packets_packet_data_enqueue(struct buffered_packets *bp,
+                                     struct bp_packet_data *pd) {
+    if (ovs_list_size(&bp->queue) == BUFFER_QUEUE_DEPTH) {
+        struct bp_packet_data *p = CONTAINER_OF(ovs_list_pop_front(&bp->queue),
+                                                struct bp_packet_data, node);
+
+        bp_packet_data_destroy(p);
+    }
+    ovs_list_push_back(&bp->queue, &pd->node);
 }
 
-static bool
-mac_cache_mb_data_from_sbrec(struct mac_cache_mb_data *data,
-                              const struct sbrec_mac_binding *mb,
-                              struct ovsdb_idl_index *sbrec_pb_by_name)
-{
-    const struct sbrec_port_binding *pb =
-            lport_lookup_by_name(sbrec_pb_by_name, mb->logical_port);
+void
+buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
+                         const struct hmap *recent_mbs,
+                         struct ovsdb_idl_index *sbrec_pb_by_key,
+                         struct ovsdb_idl_index *sbrec_dp_by_key,
+                         struct ovsdb_idl_index *sbrec_pb_by_name,
+                         struct ovsdb_idl_index *sbrec_mb_by_lport_ip) {
+    struct ds ip = DS_EMPTY_INITIALIZER;
+    long long now = time_msec();
+
+    struct buffered_packets *bp;
+    HMAP_FOR_EACH_SAFE (bp, hmap_node, &ctx->buffered_packets) {
+        struct eth_addr mac = eth_addr_zero;
+        /* Remove expired buffered packets. */
+        if (now > bp->expire_at_ms) {
+            buffered_packets_remove(ctx, bp);
+            continue;
+        }
 
-    if (!pb || !pb->datapath || !ip46_parse(mb->ip, &data->ip) ||
-        !eth_addr_from_string(mb->mac, &data->mac)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "Couldn't parse MAC binding: ip=%s, mac=%s, "
-                     "logical_port=%s", mb->ip, mb->mac, mb->logical_port);
-        return false;
+        struct mac_binding *mb = mac_binding_find(recent_mbs, &bp->mb_data);
+        if (mb) {
+            mac = mb->data.mac;
+        } else if (now >= bp->lookup_at_ms) {
+            /* Check if we can do a full lookup. */
+            buffered_packets_db_lookup(bp, &ip, &mac, sbrec_pb_by_key,
+                                       sbrec_dp_by_key, sbrec_pb_by_name,
+                                       sbrec_mb_by_lport_ip);
+            /* Schedule next lookup even if we found the MAC address,
+             * if the address was found this struct will be deleted anyway. */
+            bp->lookup_at_ms = now + BUFFERED_PACKETS_LOOKUP_MS;
+        }
+
+        if (eth_addr_is_zero(mac)) {
+            continue;
+        }
+
+        struct bp_packet_data *pd;
+        LIST_FOR_EACH_POP (pd, node, &bp->queue) {
+            struct dp_packet packet;
+            dp_packet_use_const(&packet, pd->pin.packet, pd->pin.packet_len);
+
+            struct eth_header *eth = dp_packet_data(&packet);
+            eth->eth_dst = mac;
+
+            ovs_list_push_back(&ctx->ready_packets_data, &pd->node);
+        }
+
+        buffered_packets_remove(ctx, bp);
     }
 
-    data->dp_key = mb->datapath->tunnel_key;
-    data->port_key = pb->tunnel_key;
+    ds_destroy(&ip);
+}
 
-    return true;
+bool
+buffered_packets_ctx_is_ready_to_send(struct buffered_packets_ctx *ctx) {
+    return !ovs_list_is_empty(&ctx->ready_packets_data);
 }
 
-static struct mac_cache_mac_binding *
-mac_cache_mac_binding_find(struct mac_cache_data *data,
-                           const struct mac_cache_mb_data *mb_data)
-{
-    uint32_t hash = mac_cache_mb_data_hash(mb_data);
+bool
+buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx) {
+    return !hmap_is_empty(&ctx->buffered_packets);
+}
 
-    struct mac_cache_mac_binding *mb;
-    HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, &data->mac_bindings) {
-        if (mac_cache_mb_data_equals(&mb->data, mb_data)) {
-            return mb;
-        }
+void
+buffered_packets_ctx_init(struct buffered_packets_ctx *ctx) {
+    hmap_init(&ctx->buffered_packets);
+    ovs_list_init(&ctx->ready_packets_data);
+}
+
+void
+buffered_packets_ctx_destroy(struct buffered_packets_ctx *ctx) {
+    struct bp_packet_data *pd;
+    LIST_FOR_EACH_POP (pd, node, &ctx->ready_packets_data) {
+        bp_packet_data_destroy(pd);
     }
 
-    return NULL;
+    struct buffered_packets *bp;
+    HMAP_FOR_EACH_SAFE (bp, hmap_node, &ctx->buffered_packets) {
+        buffered_packets_remove(ctx, bp);
+    }
+    hmap_destroy(&ctx->buffered_packets);
+}
+
+
+void
+mac_cache_stats_destroy(struct ovs_list *stats_list)
+{
+    struct mac_cache_stats *stats;
+    LIST_FOR_EACH_POP (stats, list_node, stats_list) {
+        free(stats);
+    }
 }
 
 static uint32_t
-mac_cache_fdb_data_hash(const struct mac_cache_fdb_data *fdb_data)
+mac_binding_data_hash(const struct mac_binding_data *mb_data)
 {
     uint32_t hash = 0;
 
-    hash = hash_add(hash, fdb_data->port_key);
-    hash = hash_add(hash, fdb_data->dp_key);
-    hash = hash_add64(hash, eth_addr_to_uint64(fdb_data->mac));
+    hash = hash_add(hash, mb_data->port_key);
+    hash = hash_add(hash, mb_data->dp_key);
+    hash = hash_add_in6_addr(hash, &mb_data->ip);
 
-    return hash_finish(hash, 16);
+    return hash_finish(hash, 24);
 }
 
 static inline bool
-mac_cache_fdb_data_equals(const struct mac_cache_fdb_data *a,
-                          const struct mac_cache_fdb_data *b)
+mac_binding_data_equals(const struct mac_binding_data *a,
+                        const struct mac_binding_data *b)
 {
     return a->port_key == b->port_key &&
            a->dp_key == b->dp_key &&
-           eth_addr_equals(a->mac, b->mac);
+            ipv6_addr_equals(&a->ip, &b->ip);
 }
 
-static bool
-mac_cache_fdb_data_from_sbrec(struct mac_cache_fdb_data *data,
-                              const struct sbrec_fdb *fdb)
+static uint32_t
+fdb_data_hash(const struct fdb_data *fdb_data)
 {
+    uint32_t hash = 0;
 
-    if (!eth_addr_from_string(fdb->mac, &data->mac)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "Couldn't parse FDB: mac=%s", fdb->mac);
-        return false;
-    }
-
-    data->dp_key = fdb->dp_key;
-    data->port_key = fdb->port_key;
+    hash = hash_add(hash, fdb_data->dp_key);
+    hash = hash_add64(hash, eth_addr_to_uint64(fdb_data->mac));
 
-    return true;
+    return hash_finish(hash, 12);
 }
 
-static struct mac_cache_fdb *
-mac_cache_fdb_find(struct mac_cache_data *data,
-                   const struct mac_cache_fdb_data *fdb_data)
+static inline bool
+fdb_data_equals(const struct fdb_data *a, const struct fdb_data *b)
 {
-    uint32_t hash = mac_cache_fdb_data_hash(fdb_data);
-
-    struct mac_cache_fdb *fdb;
-    HMAP_FOR_EACH_WITH_HASH (fdb, hmap_node, hash, &data->fdbs) {
-        if (mac_cache_fdb_data_equals(&fdb->data, fdb_data)) {
-            return fdb;
-        }
-    }
-
-    return NULL;
+    return a->dp_key == b->dp_key &&
+           eth_addr_equals(a->mac, b->mac);
 }
 
+
 static struct mac_cache_threshold *
 mac_cache_threshold_find(struct hmap *thresholds, const struct uuid *uuid)
 {
@@ -545,3 +682,66 @@  mac_cache_update_req_delay(struct hmap *thresholds, uint64_t *req_delay)
 
     *req_delay = dump_period < UINT64_MAX ? dump_period : 0;
 }
+
+static struct buffered_packets *
+buffered_packets_find(struct buffered_packets_ctx *ctx,
+                      const struct mac_binding_data *mb_data) {
+    uint32_t hash = mac_binding_data_hash(mb_data);
+
+    struct buffered_packets *bp;
+    HMAP_FOR_EACH_WITH_HASH (bp, hmap_node, hash, &ctx->buffered_packets) {
+        if (mac_binding_data_equals(&bp->mb_data, mb_data)) {
+            return bp;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+buffered_packets_remove(struct buffered_packets_ctx *ctx,
+                        struct buffered_packets *bp) {
+    struct bp_packet_data *pd;
+    LIST_FOR_EACH_POP (pd, node, &bp->queue) {
+        bp_packet_data_destroy(pd);
+    }
+
+    hmap_remove(&ctx->buffered_packets, &bp->hmap_node);
+    free(bp);
+}
+
+static void
+buffered_packets_db_lookup(struct buffered_packets *bp, struct ds *ip,
+                           struct eth_addr *mac,
+                           struct ovsdb_idl_index *sbrec_pb_by_key,
+                           struct ovsdb_idl_index *sbrec_dp_by_key,
+                           struct ovsdb_idl_index *sbrec_pb_by_name,
+                           struct ovsdb_idl_index *sbrec_mb_by_lport_ip) {
+    const struct sbrec_port_binding *pb =
+            lport_lookup_by_key(sbrec_dp_by_key, sbrec_pb_by_key,
+                                bp->mb_data.dp_key, bp->mb_data.port_key);
+    if (!pb) {
+        return;
+    }
+
+    if (!strcmp(pb->type, "chassisredirect")) {
+        const char *dgp_name =
+                smap_get_def(&pb->options, "distributed-port", "");
+        pb = lport_lookup_by_name(sbrec_pb_by_name, dgp_name);
+        if (!pb) {
+            return;
+        }
+    }
+
+    ipv6_format_mapped(&bp->mb_data.ip, ip);
+    const struct sbrec_mac_binding *smb =
+            mac_binding_lookup(sbrec_mb_by_lport_ip, pb->logical_port,
+                               ds_cstr_ro(ip));
+    ds_clear(ip);
+
+    if (!smb) {
+        return;
+    }
+
+    eth_addr_from_string(smb->mac, mac);
+}
diff --git a/controller/mac-cache.h b/controller/mac-cache.h
index 644ac8be2..932f6cfd4 100644
--- a/controller/mac-cache.h
+++ b/controller/mac-cache.h
@@ -18,9 +18,16 @@ 
 
 #include <stdint.h>
 
+#include "dp-packet.h"
 #include "openvswitch/hmap.h"
-#include "ovn-sb-idl.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/list.h"
+#include "openvswitch/ofpbuf.h"
 #include "openvswitch/ofp-flow.h"
+#include "openvswitch/ofp-packet.h"
+#include "ovn-sb-idl.h"
+
+struct ovsdb_idl_index;
 
 enum mac_cache_type {
     MAC_CACHE_MAC_BINDING,
@@ -49,37 +56,73 @@  struct mac_cache_threshold {
     uint64_t dump_period;
 };
 
-struct mac_cache_mb_data {
+struct mac_binding_data {
+    /* Keys. */
     uint32_t port_key;
     uint32_t dp_key;
     struct in6_addr ip;
+    /* Value. */
     struct eth_addr mac;
 };
 
-struct mac_cache_mac_binding {
+struct mac_binding {
     struct hmap_node hmap_node;
     /* Common data to identify MAC binding. */
-    struct mac_cache_mb_data data;
-    /* Reference to the SB MAC binding record. */
+    struct mac_binding_data data;
+    /* Reference to the SB MAC binding record (Might be NULL). */
     const struct sbrec_mac_binding *sbrec_mb;
+    /* User specified timestamp (in ms) */
+    long long timestamp;
 };
 
-struct mac_cache_fdb_data {
-    uint32_t port_key;
+struct fdb_data {
+    /* Keys. */
     uint32_t dp_key;
     struct eth_addr mac;
+    /* Value. */
+    uint32_t port_key;
 };
 
-struct mac_cache_fdb {
+struct fdb {
     struct hmap_node hmap_node;
     /* Common data to identify FDB. */
-    struct mac_cache_fdb_data data;
+    struct fdb_data data;
     /* Reference to the SB FDB record. */
     const struct sbrec_fdb *sbrec_fdb;
     /* UUID of datapath for this FDB record. */
     struct uuid dp_uuid;
 };
 
+struct bp_packet_data {
+    struct ovs_list node;
+
+    struct ofpbuf *continuation;
+    struct ofputil_packet_in pin;
+};
+
+struct buffered_packets {
+    struct hmap_node hmap_node;
+
+    struct mac_binding_data mb_data;
+
+    /* Queue of packet_data associated with this struct. */
+    struct ovs_list queue;
+
+    /* Timestamp in ms when the buffered packet should expire. */
+    long long int expire_at_ms;
+
+    /* Timestamp in ms when the buffered packet should do full SB lookup.*/
+    long long int lookup_at_ms;
+};
+
+struct buffered_packets_ctx {
+    /* Map of all buffered packets waiting for the MAC address. */
+    struct hmap buffered_packets;
+    /* List of packet data that are ready to be sent. */
+    struct ovs_list ready_packets_data;
+};
+
+/* Thresholds. */
 bool mac_cache_threshold_add(struct mac_cache_data *data,
                              const struct sbrec_datapath_binding *dp,
                              enum mac_cache_type type);
@@ -87,38 +130,86 @@  bool mac_cache_threshold_replace(struct mac_cache_data *data,
                                  const struct sbrec_datapath_binding *dp,
                                  enum mac_cache_type type);
 void mac_cache_thresholds_clear(struct mac_cache_data *data);
-void mac_cache_mac_binding_add(struct mac_cache_data *data,
-                                const struct sbrec_mac_binding *mb,
-                                struct ovsdb_idl_index *sbrec_pb_by_name);
-struct mac_cache_mac_binding *
-mac_cachce_mac_binding_find(struct mac_cache_data *data,
-                            const struct sbrec_mac_binding *mb,
-                            struct ovsdb_idl_index *sbrec_pb_by_name);
-void mac_cache_mac_binding_remove(struct mac_cache_data *data,
-                                  const struct sbrec_mac_binding *mb,
-                                  struct ovsdb_idl_index *sbrec_pb_by_name);
-void mac_cache_mac_bindings_clear(struct mac_cache_data *data);
-bool mac_cache_sb_mac_binding_updated(const struct sbrec_mac_binding *mb);
-
-void mac_cache_fdb_add(struct mac_cache_data *data,
-                       const struct sbrec_fdb *fdb, struct uuid dp_uuid);
-void mac_cache_fdb_remove(struct mac_cache_data *data,
-                          const struct sbrec_fdb *fdb);
-bool mac_cache_sb_fdb_updated(const struct sbrec_fdb *fdb);
-void mac_cache_fdbs_clear(struct mac_cache_data *data);
 
-void
-mac_cache_mb_stats_process_flow_stats(struct ovs_list *stats_list,
-                                      struct ofputil_flow_stats *ofp_stats);
-void mac_cache_mb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-                            void *data);
+/* MAC binding. */
+struct mac_binding *mac_binding_add(struct hmap *map,
+                                    struct mac_binding_data mb_data,
+                                    long long timestamp);
+
+void mac_binding_remove(struct hmap *map, struct mac_binding *mb);
+
+struct mac_binding *mac_binding_find(const struct hmap *map,
+                                     const struct mac_binding_data *mb_data);
+
+bool mac_binding_data_from_sbrec(struct mac_binding_data *data,
+                                 const struct sbrec_mac_binding *mb,
+                                 struct ovsdb_idl_index *sbrec_pb_by_name);
 
+void mac_bindings_clear(struct hmap *map);
+
+bool sb_mac_binding_updated(const struct sbrec_mac_binding *mb);
+
+const struct sbrec_mac_binding *
+mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                   const char *logical_port, const char *ip);
+
+/* FDB. */
+struct fdb *fdb_add(struct hmap *map, struct fdb_data fdb_data);
+
+void fdb_remove(struct hmap *map, struct fdb *fdb);
+
+bool fdb_data_from_sbrec(struct fdb_data *data, const struct sbrec_fdb *fdb);
+
+struct fdb *fdb_find(const struct hmap *map, const struct fdb_data *fdb_data);
+
+bool sb_fdb_updated(const struct sbrec_fdb *fdb);
+
+void fdbs_clear(struct hmap *map);
+
+/* MAC binding stat processing. */
 void
-mac_cache_fdb_stats_process_flow_stats(struct ovs_list *stats_list,
-                                       struct ofputil_flow_stats *ofp_stats);
-void mac_cache_fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-                             void *data);
+mac_binding_stats_process_flow_stats(struct ovs_list *stats_list,
+                                     struct ofputil_flow_stats *ofp_stats);
+
+void mac_binding_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
+                           void *data);
+
+/* FDB stat processing. */
+void fdb_stats_process_flow_stats(struct ovs_list *stats_list,
+                                  struct ofputil_flow_stats *ofp_stats);
+
+void fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
+                   void *data);
 
 void mac_cache_stats_destroy(struct ovs_list *stats_list);
 
+/* Packet buffering. */
+struct bp_packet_data *
+bp_packet_data_create(const struct ofputil_packet_in *pin,
+                      const struct ofpbuf *continuation);
+
+void bp_packet_data_destroy(struct bp_packet_data *pd);
+
+struct buffered_packets *
+buffered_packets_add(struct buffered_packets_ctx *ctx,
+                     struct mac_binding_data mb_data);
+
+void buffered_packets_packet_data_enqueue(struct buffered_packets *bp,
+                                          struct bp_packet_data *pd);
+
+void buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
+                              const struct hmap *recent_mbs,
+                              struct ovsdb_idl_index *sbrec_pb_by_key,
+                              struct ovsdb_idl_index *sbrec_dp_by_key,
+                              struct ovsdb_idl_index *sbrec_pb_by_name,
+                              struct ovsdb_idl_index *sbrec_mb_by_lport_ip);
+
+void buffered_packets_ctx_init(struct buffered_packets_ctx *ctx);
+
+void buffered_packets_ctx_destroy(struct buffered_packets_ctx *ctx);
+
+bool buffered_packets_ctx_is_ready_to_send(struct buffered_packets_ctx *ctx);
+
+bool buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx);
+
 #endif /* controller/mac-cache.h */
diff --git a/controller/mac-learn.c b/controller/mac-learn.c
deleted file mode 100644
index 0c3b60c23..000000000
--- a/controller/mac-learn.c
+++ /dev/null
@@ -1,482 +0,0 @@ 
-/* Copyright (c) 2020, Red Hat, Inc.
- *
- * 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>
-
-#include "mac-learn.h"
-
-/* OpenvSwitch lib includes. */
-#include "openvswitch/poll-loop.h"
-#include "openvswitch/vlog.h"
-#include "lib/packets.h"
-#include "lib/smap.h"
-#include "lib/timeval.h"
-#include "lport.h"
-#include "ovn-sb-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(mac_learn);
-
-#define MAX_FDB_ENTRIES             1000
-#define MAX_BUFFERED_PACKETS        1000
-#define BUFFER_QUEUE_DEPTH          4
-#define BUFFERED_PACKETS_TIMEOUT_MS 10000
-#define BUFFERED_PACKETS_LOOKUP_MS  100
-
-static size_t keys_ip_hash(uint32_t dp_key, uint32_t port_key,
-                           struct in6_addr *ip);
-static struct mac_binding *mac_binding_find(const struct mac_bindings_map
-                                            *mac_bindings, uint32_t dp_key,
-                                            uint32_t port_key,
-                                            struct in6_addr *ip, size_t hash);
-static size_t fdb_entry_hash(uint32_t dp_key, struct eth_addr *);
-
-static struct fdb_entry *fdb_entry_find(struct hmap *fdbs, uint32_t dp_key,
-                                        struct eth_addr *mac, size_t hash);
-static struct buffered_packets *
-buffered_packets_find(struct buffered_packets_ctx *ctx, uint64_t dp_key,
-                      uint64_t port_key, struct in6_addr *ip, uint32_t hash);
-static void ovn_buffered_packets_remove(struct buffered_packets_ctx *ctx,
-                                        struct buffered_packets *bp);
-static void
-buffered_packets_db_lookup(struct buffered_packets *bp,
-                           struct ds *ip, struct eth_addr *mac,
-                           struct ovsdb_idl_index *sbrec_pb_by_key,
-                           struct ovsdb_idl_index *sbrec_dp_by_key,
-                           struct ovsdb_idl_index *sbrec_pb_by_name,
-                           struct ovsdb_idl_index *sbrec_mb_by_lport_ip);
-
-/* mac_binding functions. */
-void
-ovn_mac_bindings_map_init(struct mac_bindings_map *mac_bindings,
-                          size_t max_size)
-{
-    mac_bindings->max_size = max_size;
-    hmap_init(&mac_bindings->map);
-}
-
-void
-ovn_mac_bindings_map_destroy(struct mac_bindings_map *mac_bindings)
-{
-    struct mac_binding *mb;
-
-    HMAP_FOR_EACH_POP (mb, hmap_node, &mac_bindings->map) {
-        free(mb);
-    }
-    hmap_destroy(&mac_bindings->map);
-}
-
-struct mac_binding *
-ovn_mac_binding_add(struct mac_bindings_map *mac_bindings, uint32_t dp_key,
-                    uint32_t port_key, struct in6_addr *ip,
-                    struct eth_addr mac, uint32_t timeout_ms)
-{
-    uint32_t hash = keys_ip_hash(dp_key, port_key, ip);
-
-    struct mac_binding *mb =
-        mac_binding_find(mac_bindings, dp_key, port_key, ip, hash);
-    size_t max_size = mac_bindings->max_size;
-    if (!mb) {
-        if (max_size && hmap_count(&mac_bindings->map) >= max_size) {
-            return NULL;
-        }
-        mb = xmalloc(sizeof *mb);
-        mb->dp_key = dp_key;
-        mb->port_key = port_key;
-        mb->ip = *ip;
-        mb->timeout_at_ms = time_msec() + timeout_ms;
-        hmap_insert(&mac_bindings->map, &mb->hmap_node, hash);
-    }
-    mb->mac = mac;
-
-    return mb;
-}
-
-/* This is called from ovn-controller main context */
-void
-ovn_mac_bindings_map_wait(struct mac_bindings_map *mac_bindings)
-{
-    if (hmap_is_empty(&mac_bindings->map)) {
-        return;
-    }
-
-    struct mac_binding *mb;
-
-    HMAP_FOR_EACH (mb, hmap_node, &mac_bindings->map) {
-        poll_timer_wait_until(mb->timeout_at_ms);
-    }
-}
-
-void
-ovn_mac_binding_remove(struct mac_binding *mb,
-                       struct mac_bindings_map *mac_bindings)
-{
-    hmap_remove(&mac_bindings->map, &mb->hmap_node);
-    free(mb);
-}
-
-bool
-ovn_mac_binding_timed_out(const struct mac_binding *mb, long long now)
-{
-    return now >= mb->timeout_at_ms;
-}
-
-const struct sbrec_mac_binding *
-ovn_mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                       const char *logical_port, const char *ip)
-{
-    struct sbrec_mac_binding *mb =
-        sbrec_mac_binding_index_init_row(sbrec_mac_binding_by_lport_ip);
-    sbrec_mac_binding_index_set_logical_port(mb, logical_port);
-    sbrec_mac_binding_index_set_ip(mb, ip);
-
-    const struct sbrec_mac_binding *retval =
-        sbrec_mac_binding_index_find(sbrec_mac_binding_by_lport_ip, mb);
-
-    sbrec_mac_binding_index_destroy_row(mb);
-
-    return retval;
-}
-
-/* fdb functions. */
-void
-ovn_fdb_init(struct hmap *fdbs)
-{
-    hmap_init(fdbs);
-}
-
-void
-ovn_fdbs_flush(struct hmap *fdbs)
-{
-    struct fdb_entry *fdb_e;
-    HMAP_FOR_EACH_POP (fdb_e, hmap_node, fdbs) {
-        free(fdb_e);
-    }
-}
-
-void
-ovn_fdbs_destroy(struct hmap *fdbs)
-{
-   ovn_fdbs_flush(fdbs);
-   hmap_destroy(fdbs);
-}
-
-struct fdb_entry *
-ovn_fdb_add(struct hmap *fdbs, uint32_t dp_key, struct eth_addr mac,
-            uint32_t port_key)
-{
-    uint32_t hash = fdb_entry_hash(dp_key, &mac);
-
-    struct fdb_entry *fdb_e =
-        fdb_entry_find(fdbs, dp_key, &mac, hash);
-    if (!fdb_e) {
-        if (hmap_count(fdbs) >= MAX_FDB_ENTRIES) {
-            return NULL;
-        }
-
-        fdb_e = xzalloc(sizeof *fdb_e);
-        fdb_e->dp_key = dp_key;
-        fdb_e->mac = mac;
-        hmap_insert(fdbs, &fdb_e->hmap_node, hash);
-    }
-    fdb_e->port_key = port_key;
-
-    return fdb_e;
-
-}
-
-/* packet buffering functions */
-
-struct packet_data *
-ovn_packet_data_create(const struct ofputil_packet_in *pin,
-                       const struct ofpbuf *continuation)
-{
-    struct packet_data *pd = xmalloc(sizeof *pd);
-
-    pd->pin = (struct ofputil_packet_in) {
-        .packet = xmemdup(pin->packet, pin->packet_len),
-        .packet_len = pin->packet_len,
-        .flow_metadata = pin->flow_metadata,
-        .reason = pin->reason,
-        .table_id = pin->table_id,
-        .cookie = pin->cookie,
-        /* Userdata are empty on purpose,
-         * it is not needed for the continuation. */
-        .userdata = NULL,
-        .userdata_len = 0,
-    };
-    pd->continuation = ofpbuf_clone(continuation);
-
-    return pd;
-}
-
-
-void
-ovn_packet_data_destroy(struct packet_data *pd)
-{
-    free(pd->pin.packet);
-    ofpbuf_delete(pd->continuation);
-    free(pd);
-}
-
-struct buffered_packets *
-ovn_buffered_packets_add(struct buffered_packets_ctx *ctx, uint64_t dp_key,
-                         uint64_t port_key, struct in6_addr ip)
-{
-    struct buffered_packets *bp;
-
-    uint32_t hash = keys_ip_hash(dp_key, port_key, &ip);
-
-    bp = buffered_packets_find(ctx, dp_key, port_key, &ip, hash);
-    if (!bp) {
-        if (hmap_count(&ctx->buffered_packets) >= MAX_BUFFERED_PACKETS) {
-            return NULL;
-        }
-
-        bp = xmalloc(sizeof *bp);
-        hmap_insert(&ctx->buffered_packets, &bp->hmap_node, hash);
-        bp->ip = ip;
-        bp->dp_key = dp_key;
-        bp->port_key = port_key;
-        /* Schedule the freshly added buffered packet to do lookup
-         * immediately. */
-        bp->lookup_at_ms = 0;
-        ovs_list_init(&bp->queue);
-    }
-
-    bp->expire_at_ms = time_msec() + BUFFERED_PACKETS_TIMEOUT_MS;
-
-    return bp;
-}
-
-void
-ovn_buffered_packets_packet_data_enqueue(struct buffered_packets *bp,
-                                         struct packet_data *pd)
-{
-    if (ovs_list_size(&bp->queue) == BUFFER_QUEUE_DEPTH) {
-        struct packet_data *p = CONTAINER_OF(ovs_list_pop_front(&bp->queue),
-                                             struct packet_data, node);
-
-        ovn_packet_data_destroy(p);
-    }
-    ovs_list_push_back(&bp->queue, &pd->node);
-}
-
-void
-ovn_buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
-                             const struct mac_bindings_map *recent_mbs,
-                             struct ovsdb_idl_index *sbrec_pb_by_key,
-                             struct ovsdb_idl_index *sbrec_dp_by_key,
-                             struct ovsdb_idl_index *sbrec_pb_by_name,
-                             struct ovsdb_idl_index *sbrec_mb_by_lport_ip)
-{
-    struct ds ip = DS_EMPTY_INITIALIZER;
-    long long now = time_msec();
-
-    struct buffered_packets *bp;
-
-    HMAP_FOR_EACH_SAFE (bp, hmap_node, &ctx->buffered_packets) {
-        struct eth_addr mac = eth_addr_zero;
-        /* Remove expired buffered packets. */
-        if (now > bp->expire_at_ms) {
-            ovn_buffered_packets_remove(ctx, bp);
-            continue;
-        }
-
-        uint32_t hash = keys_ip_hash(bp->dp_key, bp->port_key, &bp->ip);
-        struct mac_binding *mb = mac_binding_find(recent_mbs, bp->dp_key,
-                                                  bp->port_key, &bp->ip, hash);
-
-        if (mb) {
-            mac = mb->mac;
-        } else if (now >= bp->lookup_at_ms) {
-            /* Check if we can do a full lookup. */
-            buffered_packets_db_lookup(bp, &ip, &mac, sbrec_pb_by_key,
-                                       sbrec_dp_by_key, sbrec_pb_by_name,
-                                       sbrec_mb_by_lport_ip);
-            /* Schedule next lookup even if we found the MAC address,
-             * if the address was found this struct will be deleted anyway. */
-            bp->lookup_at_ms = now + BUFFERED_PACKETS_LOOKUP_MS;
-        }
-
-        if (eth_addr_is_zero(mac)) {
-            continue;
-        }
-
-        struct packet_data *pd;
-        LIST_FOR_EACH_POP (pd, node, &bp->queue) {
-            struct dp_packet packet;
-            dp_packet_use_const(&packet, pd->pin.packet, pd->pin.packet_len);
-
-            struct eth_header *eth = dp_packet_data(&packet);
-            eth->eth_dst = mac;
-
-            ovs_list_push_back(&ctx->ready_packets_data, &pd->node);
-        }
-
-        ovn_buffered_packets_remove(ctx, bp);
-    }
-
-    ds_destroy(&ip);
-}
-
-bool
-ovn_buffered_packets_ctx_is_ready_to_send(struct buffered_packets_ctx *ctx)
-{
-    return !ovs_list_is_empty(&ctx->ready_packets_data);
-}
-
-bool
-ovn_buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx)
-{
-    return !hmap_is_empty(&ctx->buffered_packets);
-}
-
-void
-ovn_buffered_packets_ctx_init(struct buffered_packets_ctx *ctx)
-{
-    hmap_init(&ctx->buffered_packets);
-    ovs_list_init(&ctx->ready_packets_data);
-}
-
-void
-ovn_buffered_packets_ctx_destroy(struct buffered_packets_ctx *ctx)
-{
-    struct packet_data *pd;
-    LIST_FOR_EACH_POP (pd, node, &ctx->ready_packets_data) {
-        ovn_packet_data_destroy(pd);
-    }
-
-    struct buffered_packets *bp;
-    HMAP_FOR_EACH_SAFE (bp, hmap_node, &ctx->buffered_packets) {
-        ovn_buffered_packets_remove(ctx, bp);
-    }
-    hmap_destroy(&ctx->buffered_packets);
-}
-
-/* mac_binding related static functions. */
-static size_t
-keys_ip_hash(uint32_t dp_key, uint32_t port_key, struct in6_addr *ip)
-{
-    return hash_bytes(ip, sizeof *ip, hash_2words(dp_key, port_key));
-}
-
-static struct mac_binding *
-mac_binding_find(const struct mac_bindings_map *mac_bindings,
-                 uint32_t dp_key, uint32_t port_key, struct in6_addr *ip,
-                 size_t hash)
-{
-    struct mac_binding *mb;
-
-    HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, &mac_bindings->map) {
-        if (mb->dp_key == dp_key && mb->port_key == port_key &&
-            IN6_ARE_ADDR_EQUAL(&mb->ip, ip)) {
-            return mb;
-        }
-    }
-
-    return NULL;
-}
-
-/* fdb related static functions. */
-
-static size_t
-fdb_entry_hash(uint32_t dp_key, struct eth_addr *mac)
-{
-    uint64_t mac64 = eth_addr_to_uint64(*mac);
-    return hash_2words(dp_key, hash_uint64(mac64));
-}
-
-static struct fdb_entry *
-fdb_entry_find(struct hmap *fdbs, uint32_t dp_key,
-               struct eth_addr *mac, size_t hash)
-{
-    struct fdb_entry *fdb_e;
-    HMAP_FOR_EACH_WITH_HASH (fdb_e, hmap_node, hash, fdbs) {
-        if (fdb_e->dp_key == dp_key && eth_addr_equals(fdb_e->mac, *mac)) {
-            return fdb_e;
-        }
-    }
-
-    return NULL;
-}
-
-/* packet buffering static functions. */
-static struct buffered_packets *
-buffered_packets_find(struct buffered_packets_ctx *ctx, uint64_t dp_key,
-                      uint64_t port_key, struct in6_addr *ip, uint32_t hash)
-{
-    struct buffered_packets *mb;
-
-    HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, &ctx->buffered_packets) {
-        if (mb->dp_key == dp_key && mb->port_key == port_key &&
-            IN6_ARE_ADDR_EQUAL(&mb->ip, ip)) {
-            return mb;
-        }
-    }
-
-    return NULL;
-}
-
-static void
-ovn_buffered_packets_remove(struct buffered_packets_ctx *ctx,
-                            struct buffered_packets *bp)
-{
-    struct packet_data *pd;
-
-    LIST_FOR_EACH_POP (pd, node, &bp->queue) {
-        ovn_packet_data_destroy(pd);
-    }
-
-    hmap_remove(&ctx->buffered_packets, &bp->hmap_node);
-    free(bp);
-}
-
-static void
-buffered_packets_db_lookup(struct buffered_packets *bp, struct ds *ip,
-                           struct eth_addr *mac,
-                           struct ovsdb_idl_index *sbrec_pb_by_key,
-                           struct ovsdb_idl_index *sbrec_dp_by_key,
-                           struct ovsdb_idl_index *sbrec_pb_by_name,
-                           struct ovsdb_idl_index *sbrec_mb_by_lport_ip)
-{
-    const struct sbrec_port_binding *pb = lport_lookup_by_key(sbrec_dp_by_key,
-                                                              sbrec_pb_by_key,
-                                                              bp->dp_key,
-                                                              bp->port_key);
-    if (!pb) {
-        return;
-    }
-
-    if (!strcmp(pb->type, "chassisredirect")) {
-        const char *dgp_name =
-            smap_get_def(&pb->options, "distributed-port", "");
-        pb = lport_lookup_by_name(sbrec_pb_by_name, dgp_name);
-        if (!pb) {
-            return;
-        }
-    }
-
-    ipv6_format_mapped(&bp->ip, ip);
-    const struct sbrec_mac_binding *smb =
-        ovn_mac_binding_lookup(sbrec_mb_by_lport_ip, pb->logical_port,
-                               ds_cstr_ro(ip));
-    ds_clear(ip);
-
-    if (!smb) {
-        return;
-    }
-
-    eth_addr_from_string(smb->mac, mac);
-}
diff --git a/controller/mac-learn.h b/controller/mac-learn.h
deleted file mode 100644
index 20a015e1a..000000000
--- a/controller/mac-learn.h
+++ /dev/null
@@ -1,145 +0,0 @@ 
-/*
- * Copyright (c) 2020 Red Hat, Inc.
- *
- * 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 OVN_MAC_LEARN_H
-#define OVN_MAC_LEARN_H 1
-
-#include <sys/types.h>
-#include <netinet/in.h>
-
-#include "dp-packet.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/list.h"
-#include "openvswitch/ofpbuf.h"
-#include "openvswitch/ofp-packet.h"
-
-struct ovsdb_idl_index;
-
-struct mac_binding {
-    struct hmap_node hmap_node; /* In a hmap. */
-
-    /* Key. */
-    uint32_t dp_key;
-    uint32_t port_key; /* Port from where this mac_binding is learnt. */
-    struct in6_addr ip;
-
-    /* Value. */
-    struct eth_addr mac;
-
-    /* Absolute time (in ms) when a user specific timeout expires for
-     * this entry. */
-    long long timeout_at_ms;
-};
-
-struct mac_bindings_map {
-    struct hmap map;
-    /* Maximum capacity of the associated map. "0" means unlimited. */
-    size_t max_size;
-};
-
-void ovn_mac_bindings_map_init(struct mac_bindings_map *mac_bindings,
-                               size_t max_size);
-void ovn_mac_bindings_map_destroy(struct mac_bindings_map *mac_bindings);
-void ovn_mac_bindings_map_wait(struct mac_bindings_map *mac_bindings);
-void ovn_mac_binding_remove(struct mac_binding *mb,
-                            struct mac_bindings_map *mac_bindings);
-bool ovn_mac_binding_timed_out(const struct mac_binding *mb,
-                               long long now);
-
-struct mac_binding *ovn_mac_binding_add(struct mac_bindings_map *mac_bindings,
-                                        uint32_t dp_key, uint32_t port_key,
-                                        struct in6_addr *ip,
-                                        struct eth_addr mac,
-                                        uint32_t timeout_ms);
-const struct sbrec_mac_binding *
-ovn_mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                       const char *logical_port, const char *ip);
-
-
-struct fdb_entry {
-    struct hmap_node hmap_node; /* In a hmap. */
-
-    /* Key. */
-    uint32_t dp_key;
-    struct eth_addr mac;
-
-    /* value. */
-    uint32_t port_key;
-};
-
-void ovn_fdb_init(struct hmap *fdbs);
-void ovn_fdbs_flush(struct hmap *fdbs);
-void ovn_fdbs_destroy(struct hmap *fdbs);
-
-struct fdb_entry *ovn_fdb_add(struct hmap *fdbs,
-                              uint32_t dp_key, struct eth_addr mac,
-                              uint32_t port_key);
-
-
-struct packet_data {
-    struct ovs_list node;
-
-    struct ofpbuf *continuation;
-    struct ofputil_packet_in pin;
-};
-
-struct buffered_packets {
-    struct hmap_node hmap_node;
-
-    struct in6_addr ip;
-    uint64_t dp_key;
-    uint64_t port_key;
-
-    /* Queue of packet_data associated with this struct. */
-    struct ovs_list queue;
-
-    /* Timestamp in ms when the buffered packet should expire. */
-    long long int expire_at_ms;
-
-    /* Timestamp in ms when the buffered packet should do full SB lookup.*/
-    long long int lookup_at_ms;
-};
-
-struct buffered_packets_ctx {
-    /* Map of all buffered packets waiting for the MAC address. */
-    struct hmap buffered_packets;
-    /* List of packet data that are ready to be sent. */
-    struct ovs_list ready_packets_data;
-};
-
-struct packet_data *
-ovn_packet_data_create(const struct ofputil_packet_in *pin,
-                       const struct ofpbuf *continuation);
-void ovn_packet_data_destroy(struct packet_data *pd);
-struct buffered_packets *
-ovn_buffered_packets_add(struct buffered_packets_ctx *ctx, uint64_t dp_key,
-                         uint64_t port_key, struct in6_addr ip);
-void ovn_buffered_packets_packet_data_enqueue(struct buffered_packets *bp,
-                                              struct packet_data *pd);
-void
-ovn_buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
-                             const struct mac_bindings_map *recent_mbs,
-                             struct ovsdb_idl_index *sbrec_pb_by_key,
-                             struct ovsdb_idl_index *sbrec_dp_by_key,
-                             struct ovsdb_idl_index *sbrec_pb_by_name,
-                             struct ovsdb_idl_index *sbrec_mb_by_lport_ip);
-void ovn_buffered_packets_ctx_init(struct buffered_packets_ctx *ctx);
-void ovn_buffered_packets_ctx_destroy(struct buffered_packets_ctx *ctx);
-bool
-ovn_buffered_packets_ctx_is_ready_to_send(struct buffered_packets_ctx *ctx);
-bool ovn_buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx);
-
-#endif /* OVN_MAC_LEARN_H */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index cde45e35e..cd50db823 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -3275,6 +3275,70 @@  en_lb_data_cleanup(void *data)
     uuidset_destroy(&lb_data->new);
 }
 
+static void
+mac_binding_add_sb(struct mac_cache_data *data,
+                   const struct sbrec_mac_binding *smb,
+                   struct ovsdb_idl_index *sbrec_pb_by_name)
+{
+    struct mac_binding_data mb_data;
+    if (!mac_binding_data_from_sbrec(&mb_data, smb, sbrec_pb_by_name)) {
+        return;
+    }
+
+    struct mac_binding *mb = mac_binding_add(&data->mac_bindings, mb_data, 0);
+
+    mb->sbrec_mb = smb;
+}
+
+static void
+mac_binding_remove_sb(struct mac_cache_data *data,
+                      const struct sbrec_mac_binding *smb,
+                      struct ovsdb_idl_index *sbrec_pb_by_name)
+{
+    struct mac_binding_data mb_data;
+    if (!mac_binding_data_from_sbrec(&mb_data, smb, sbrec_pb_by_name)) {
+        return;
+    }
+
+    struct mac_binding *mb = mac_binding_find(&data->mac_bindings, &mb_data);
+    if (!mb) {
+        return;
+    }
+
+    mac_binding_remove(&data->mac_bindings, mb);
+}
+
+static void
+fdb_add_sb(struct mac_cache_data *data, const struct sbrec_fdb *sfdb,
+           struct uuid dp_uuid)
+{
+    struct fdb_data fdb_data;
+    if (!fdb_data_from_sbrec(&fdb_data, sfdb)) {
+        return;
+    }
+
+    struct fdb *fdb = fdb_add(&data->fdbs, fdb_data);
+
+    fdb->sbrec_fdb = sfdb;
+    fdb->dp_uuid = dp_uuid;
+}
+
+static void
+fdb_remove_sb(struct mac_cache_data *data, const struct sbrec_fdb *sfdb)
+{
+    struct fdb_data fdb_data;
+    if (!fdb_data_from_sbrec(&fdb_data, sfdb)) {
+        return;
+    }
+
+    struct fdb *fdb = fdb_find(&data->fdbs, &fdb_data);
+    if (!fdb) {
+        return;
+    }
+
+    fdb_remove(&data->fdbs, fdb);
+}
+
 static void
 mac_cache_mb_handle_for_datapath(struct mac_cache_data *data,
                                  const struct sbrec_datapath_binding *dp,
@@ -3291,9 +3355,9 @@  mac_cache_mb_handle_for_datapath(struct mac_cache_data *data,
     const struct sbrec_mac_binding *mb;
     SBREC_MAC_BINDING_FOR_EACH_EQUAL (mb, mb_index_row, sbrec_mb_by_dp) {
         if (has_threshold) {
-            mac_cache_mac_binding_add(data, mb, sbrec_pb_by_name);
+            mac_binding_add_sb(data, mb, sbrec_pb_by_name);
         } else {
-            mac_cache_mac_binding_remove(data, mb, sbrec_pb_by_name);
+            mac_binding_remove_sb(data, mb, sbrec_pb_by_name);
         }
     }
 
@@ -3314,9 +3378,9 @@  mac_cache_fdb_handle_for_datapath(struct mac_cache_data *data,
     const struct sbrec_fdb *fdb;
     SBREC_FDB_FOR_EACH_EQUAL (fdb, fdb_index_row, sbrec_fdb_by_dp_key) {
         if (has_threshold) {
-            mac_cache_fdb_add(data, fdb, dp->header_.uuid);
+            fdb_add_sb(data, fdb, dp->header_.uuid);
         } else {
-            mac_cache_fdb_remove(data, fdb);
+            fdb_remove_sb(data, fdb);
         }
     }
 
@@ -3354,8 +3418,8 @@  en_mac_cache_run(struct engine_node *node, void *data)
                     "name");
 
     mac_cache_thresholds_clear(cache_data);
-    mac_cache_mac_bindings_clear(cache_data);
-    mac_cache_fdbs_clear(cache_data);
+    mac_bindings_clear(&cache_data->mac_bindings);
+    fdbs_clear(&cache_data->fdbs);
 
     const struct sbrec_mac_binding *sbrec_mb;
     SBREC_MAC_BINDING_TABLE_FOR_EACH (sbrec_mb, mb_table) {
@@ -3366,7 +3430,8 @@  en_mac_cache_run(struct engine_node *node, void *data)
 
         if (mac_cache_threshold_add(cache_data, sbrec_mb->datapath,
                                     MAC_CACHE_MAC_BINDING)) {
-            mac_cache_mac_binding_add(cache_data, sbrec_mb, sbrec_pb_by_name);
+            mac_binding_add_sb(cache_data, sbrec_mb,
+                               sbrec_pb_by_name);
         }
     }
 
@@ -3381,8 +3446,8 @@  en_mac_cache_run(struct engine_node *node, void *data)
 
         if (mac_cache_threshold_add(cache_data, local_dp->datapath,
                                     MAC_CACHE_FDB)) {
-            mac_cache_fdb_add(cache_data, sbrec_fdb,
-                              local_dp->datapath->header_.uuid);
+            fdb_add_sb(cache_data, sbrec_fdb,
+                       local_dp->datapath->header_.uuid);
         }
     }
 
@@ -3406,13 +3471,13 @@  mac_cache_sb_mac_binding_handler(struct engine_node *node, void *data)
 
     const struct sbrec_mac_binding *sbrec_mb;
     SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (sbrec_mb, mb_table) {
-        if (!mac_cache_sb_mac_binding_updated(sbrec_mb)) {
+        if (!sb_mac_binding_updated(sbrec_mb)) {
             continue;
         }
 
         if (!sbrec_mac_binding_is_new(sbrec_mb)) {
-            mac_cache_mac_binding_remove(cache_data, sbrec_mb,
-                                         sbrec_pb_by_name);
+            mac_binding_remove_sb(cache_data, sbrec_mb,
+                                  sbrec_pb_by_name);
         }
 
         if (sbrec_mac_binding_is_deleted(sbrec_mb) ||
@@ -3423,7 +3488,8 @@  mac_cache_sb_mac_binding_handler(struct engine_node *node, void *data)
 
         if (mac_cache_threshold_add(cache_data, sbrec_mb->datapath,
                                     MAC_CACHE_MAC_BINDING)) {
-            mac_cache_mac_binding_add(cache_data, sbrec_mb, sbrec_pb_by_name);
+            mac_binding_add_sb(cache_data, sbrec_mb,
+                               sbrec_pb_by_name);
         }
     }
 
@@ -3448,12 +3514,12 @@  mac_cache_sb_fdb_handler(struct engine_node *node, void *data)
     struct local_datapath *local_dp;
     const struct sbrec_fdb *sbrec_fdb;
     SBREC_FDB_TABLE_FOR_EACH_TRACKED (sbrec_fdb, fdb_table) {
-        if (!mac_cache_sb_fdb_updated(sbrec_fdb)) {
+        if (!sb_fdb_updated(sbrec_fdb)) {
             continue;
         }
 
         if (!sbrec_fdb_is_new(sbrec_fdb)) {
-            mac_cache_fdb_remove(cache_data, sbrec_fdb);
+            fdb_remove_sb(cache_data, sbrec_fdb);
         }
 
         local_dp = get_local_datapath(&rt_data->local_datapaths,
@@ -3464,8 +3530,8 @@  mac_cache_sb_fdb_handler(struct engine_node *node, void *data)
 
         if (mac_cache_threshold_add(cache_data, local_dp->datapath,
                                     MAC_CACHE_FDB)) {
-            mac_cache_fdb_add(cache_data, sbrec_fdb,
-                              local_dp->datapath->header_.uuid);
+            fdb_add_sb(cache_data, sbrec_fdb,
+                       local_dp->datapath->header_.uuid);
         }
     }
 
@@ -3582,9 +3648,9 @@  en_mac_cache_cleanup(void *data)
     for (size_t i = 0; i < MAC_CACHE_MAX; i++) {
         hmap_destroy(&cache_data->thresholds[i]);
     }
-    mac_cache_mac_bindings_clear(cache_data);
+    mac_bindings_clear(&cache_data->mac_bindings);
     hmap_destroy(&cache_data->mac_bindings);
-    mac_cache_fdbs_clear(cache_data);
+    fdbs_clear(&cache_data->fdbs);
     hmap_destroy(&cache_data->fdbs);
 }
 
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index aa73facbf..e8ab91a21 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -27,7 +27,7 @@ 
 #include "ha-chassis.h"
 #include "local_data.h"
 #include "lport.h"
-#include "mac-learn.h"
+#include "mac-cache.h"
 #include "nx-match.h"
 #include "ofctrl.h"
 #include "latch.h"
@@ -382,7 +382,7 @@  static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
             struct ovsdb_idl_index *sbrec_port_binding_by_key,
             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                        const struct fdb_entry *fdb_e)
+                        const struct fdb *fdb)
                         OVS_REQUIRES(pinctrl_mutex);
 static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_port_binding_by_key,
@@ -1523,13 +1523,13 @@  static struct buffered_packets_ctx buffered_packets_ctx;
 static void
 init_buffered_packets_ctx(void)
 {
-    ovn_buffered_packets_ctx_init(&buffered_packets_ctx);
+    buffered_packets_ctx_init(&buffered_packets_ctx);
 }
 
 static void
 destroy_buffered_packets_ctx(void)
 {
-    ovn_buffered_packets_ctx_destroy(&buffered_packets_ctx);
+    buffered_packets_ctx_destroy(&buffered_packets_ctx);
 }
 
 /* Called with in the pinctrl_handler thread context. */
@@ -1539,27 +1539,29 @@  pinctrl_handle_buffered_packets(const struct ofputil_packet_in *pin,
                                 bool is_arp)
 OVS_REQUIRES(pinctrl_mutex)
 {
-    struct in6_addr ip;
     const struct match *md = &pin->flow_metadata;
-    uint64_t dp_key = ntohll(md->flow.metadata);
-    uint64_t oport_key = md->flow.regs[MFF_LOG_OUTPORT - MFF_REG0];
+    struct mac_binding_data mb_data = (struct mac_binding_data) {
+            .dp_key =  ntohll(md->flow.metadata),
+            .port_key =  md->flow.regs[MFF_LOG_OUTPORT - MFF_REG0],
+            .mac = eth_addr_zero,
+    };
 
     if (is_arp) {
-        ip = in6_addr_mapped_ipv4(htonl(md->flow.regs[0]));
+        mb_data.ip = in6_addr_mapped_ipv4(htonl(md->flow.regs[0]));
     } else {
         ovs_be128 ip6 = hton128(flow_get_xxreg(&md->flow, 0));
-        memcpy(&ip, &ip6, sizeof ip);
+        memcpy(&mb_data.ip, &ip6, sizeof mb_data.ip);
     }
 
-    struct buffered_packets *bp =
-        ovn_buffered_packets_add(&buffered_packets_ctx, dp_key, oport_key, ip);
+    struct buffered_packets *bp = buffered_packets_add(&buffered_packets_ctx,
+                                                       mb_data);
     if (!bp) {
         COVERAGE_INC(pinctrl_drop_buffered_packets_map);
         return;
     }
 
-    struct packet_data *pd = ovn_packet_data_create(pin, continuation);
-    ovn_buffered_packets_packet_data_enqueue(bp, pd);
+    struct bp_packet_data *pd = bp_packet_data_create(pin, continuation);
+    buffered_packets_packet_data_enqueue(bp, pd);
 
     /* There is a chance that the MAC binding was already created. */
     notify_pinctrl_main();
@@ -4302,18 +4304,18 @@  pinctrl_destroy(void)
 #define MAX_MAC_BINDINGS           1000
 
 /* Contains "struct mac_binding"s. */
-static struct mac_bindings_map put_mac_bindings;
+static struct hmap put_mac_bindings;
 
 static void
 init_put_mac_bindings(void)
 {
-    ovn_mac_bindings_map_init(&put_mac_bindings, MAX_MAC_BINDINGS);
+    hmap_init(&put_mac_bindings);
 }
 
 static void
 destroy_put_mac_bindings(void)
 {
-    ovn_mac_bindings_map_destroy(&put_mac_bindings);
+    hmap_destroy(&put_mac_bindings);
 }
 
 /* Called with in the pinctrl_handler thread context. */
@@ -4323,15 +4325,22 @@  pinctrl_handle_put_mac_binding(const struct flow *md,
                                bool is_arp)
     OVS_REQUIRES(pinctrl_mutex)
 {
-    uint32_t dp_key = ntohll(md->metadata);
-    uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0];
-    struct in6_addr ip_key;
+    if (hmap_count(&put_mac_bindings) >= MAX_MAC_BINDINGS) {
+        COVERAGE_INC(pinctrl_drop_put_mac_binding);
+        return;
+    }
+
+    struct mac_binding_data mb_data = (struct mac_binding_data) {
+            .dp_key =  ntohll(md->metadata),
+            .port_key =  md->regs[MFF_LOG_INPORT - MFF_REG0],
+            .mac = headers->dl_src,
+    };
 
     if (is_arp) {
-        ip_key = in6_addr_mapped_ipv4(htonl(md->regs[0]));
+        mb_data.ip = in6_addr_mapped_ipv4(htonl(md->regs[0]));
     } else {
         ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0));
-        memcpy(&ip_key, &ip6, sizeof ip_key);
+        memcpy(&mb_data.ip, &ip6, sizeof mb_data.ip);
     }
 
     /* If the ARP reply was unicast we should not delay it,
@@ -4339,13 +4348,8 @@  pinctrl_handle_put_mac_binding(const struct flow *md,
     uint32_t delay = eth_addr_is_multicast(headers->dl_dst)
                      ? random_range(MAX_MAC_BINDING_DELAY_MSEC) + 1
                      : 0;
-    struct mac_binding *mb = ovn_mac_binding_add(&put_mac_bindings, dp_key,
-                                                 port_key, &ip_key,
-                                                 headers->dl_src, delay);
-    if (!mb) {
-        COVERAGE_INC(pinctrl_drop_put_mac_binding);
-        return;
-    }
+    long long timestamp = time_msec() + delay;
+    mac_binding_add(&put_mac_bindings, mb_data, timestamp);
 
     /* We can send the buffered packet once the main ovn-controller
      * thread calls pinctrl_run() and it writes the mac_bindings stored
@@ -4361,11 +4365,11 @@  send_mac_binding_buffered_pkts(struct rconn *swconn)
     enum ofp_version version = rconn_get_version(swconn);
     enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
 
-    struct packet_data *pd;
+    struct bp_packet_data *pd;
     LIST_FOR_EACH_POP (pd, node, &buffered_packets_ctx.ready_packets_data) {
         queue_msg(swconn, ofputil_encode_resume(&pd->pin, pd->continuation,
                                                 proto));
-        ovn_packet_data_destroy(pd);
+        bp_packet_data_destroy(pd);
     }
 
     ovs_list_init(&buffered_packets_ctx.ready_packets_data);
@@ -4386,7 +4390,7 @@  mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
     snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
 
     const struct sbrec_mac_binding *b =
-        ovn_mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
+            mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
                                logical_port, ip);
     if (!b) {
         if (update_only) {
@@ -4460,24 +4464,24 @@  run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
     /* Convert logical datapath and logical port key into lport. */
     const struct sbrec_port_binding *pb = lport_lookup_by_key(
         sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
-        mb->dp_key, mb->port_key);
+        mb->data.dp_key, mb->data.port_key);
     if (!pb) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
         VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" "
-                     "and port %"PRIu32, mb->dp_key, mb->port_key);
+                     "and port %"PRIu32, mb->data.dp_key, mb->data.port_key);
         return;
     }
 
     /* Convert ethernet argument to string form for database. */
     char mac_string[ETH_ADDR_STRLEN + 1];
     snprintf(mac_string, sizeof mac_string,
-             ETH_ADDR_FMT, ETH_ADDR_ARGS(mb->mac));
+             ETH_ADDR_FMT, ETH_ADDR_ARGS(mb->data.mac));
 
     struct ds ip_s = DS_EMPTY_INITIALIZER;
-    ipv6_format_mapped(&mb->ip, &ip_s);
+    ipv6_format_mapped(&mb->data.ip, &ip_s);
     mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                          pb->logical_port, pb->datapath, mb->mac,
+                          pb->logical_port, pb->datapath, mb->data.mac,
                           ds_cstr(&ip_s), false);
     ds_destroy(&ip_s);
 }
@@ -4498,13 +4502,13 @@  run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
     long long now = time_msec();
 
     struct mac_binding *mb;
-    HMAP_FOR_EACH_SAFE (mb, hmap_node, &put_mac_bindings.map) {
-        if (ovn_mac_binding_timed_out(mb, now)) {
+    HMAP_FOR_EACH_SAFE (mb, hmap_node, &put_mac_bindings) {
+        if (now >= mb->timestamp) {
             run_put_mac_binding(ovnsb_idl_txn,
                                 sbrec_datapath_binding_by_key,
                                 sbrec_port_binding_by_key,
                                 sbrec_mac_binding_by_lport_ip, mb);
-            ovn_mac_binding_remove(mb, &put_mac_bindings);
+            mac_binding_remove(&put_mac_bindings, mb);
         }
     }
 }
@@ -4517,33 +4521,24 @@  run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
                      struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
     OVS_REQUIRES(pinctrl_mutex)
 {
-    if (!ovn_buffered_packets_ctx_has_packets(&buffered_packets_ctx)) {
+    if (!buffered_packets_ctx_has_packets(&buffered_packets_ctx)) {
         return;
     }
 
-    struct mac_bindings_map recent_mbs;
-    ovn_mac_bindings_map_init(&recent_mbs, 0);
+    struct hmap recent_mbs = HMAP_INITIALIZER(&recent_mbs);
 
     const struct sbrec_mac_binding *smb;
     SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (smb, mac_binding_table) {
         const struct sbrec_port_binding *pb = lport_lookup_by_name(
             sbrec_port_binding_by_name, smb->logical_port);
-        if (!pb || !pb->datapath) {
-            continue;
-        }
-
-        struct in6_addr ip;
-        if (!ip46_parse(smb->ip, &ip)) {
-            continue;
-        }
 
-        struct eth_addr mac;
-        if (!eth_addr_from_string(smb->mac, &mac)) {
+        struct mac_binding_data mb_data;
+        if (!mac_binding_data_from_sbrec(&mb_data, smb,
+                                         sbrec_port_binding_by_name)) {
             continue;
         }
 
-        ovn_mac_binding_add(&recent_mbs, smb->datapath->tunnel_key,
-                            pb->tunnel_key, &ip, mac, 0);
+        mac_binding_add(&recent_mbs, mb_data, 0);
 
         const char *redirect_port =
             smap_get(&pb->options, "chassis-redirect-port");
@@ -4559,19 +4554,20 @@  run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
 
         /* Add the same entry also for chassisredirect port as the buffered
          * traffic might be buffered on the cr port. */
-        ovn_mac_binding_add(&recent_mbs, smb->datapath->tunnel_key,
-                            pb->tunnel_key, &ip, mac, 0);
+        mb_data.port_key = pb->tunnel_key;
+        mac_binding_add(&recent_mbs, mb_data, 0);
     }
 
-    ovn_buffered_packets_ctx_run(&buffered_packets_ctx, &recent_mbs,
+    buffered_packets_ctx_run(&buffered_packets_ctx, &recent_mbs,
                                  sbrec_port_binding_by_key,
                                  sbrec_datapath_binding_by_key,
                                  sbrec_port_binding_by_name,
                                  sbrec_mac_binding_by_lport_ip);
 
-    ovn_mac_bindings_map_destroy(&recent_mbs);
+    mac_bindings_clear(&recent_mbs);
+    hmap_destroy(&recent_mbs);
 
-    if (ovn_buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)) {
+    if (buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)) {
         notify_pinctrl_handler();
     }
 }
@@ -4580,8 +4576,13 @@  static void
 wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn)
     OVS_REQUIRES(pinctrl_mutex)
 {
-    if (ovnsb_idl_txn) {
-        ovn_mac_bindings_map_wait(&put_mac_bindings);
+    if (!ovnsb_idl_txn) {
+        return;
+    }
+
+    struct mac_binding *mb;
+    HMAP_FOR_EACH (mb, hmap_node, &put_mac_bindings) {
+        poll_timer_wait_until(mb->timestamp);
     }
 }
 
@@ -6222,7 +6223,7 @@  may_inject_pkts(void)
             !shash_is_empty(&send_garp_rarp_data) ||
             ipv6_prefixd_should_inject() ||
             !ovs_list_is_empty(&mcast_query_list) ||
-            ovn_buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx) ||
+            buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx) ||
             bfd_monitor_should_inject());
 }
 
@@ -8433,6 +8434,8 @@  pinctrl_mg_split_buff_handler(struct rconn *swconn, struct dp_packet *pkt,
     ofpbuf_uninit(&ofpacts);
 }
 
+#define MAX_FDB_ENTRIES             1000
+
 static struct hmap put_fdbs;
 
 /* MAC learning (fdb) related functions.  Runs within the main
@@ -8441,13 +8444,13 @@  static struct hmap put_fdbs;
 static void
 init_fdb_entries(void)
 {
-    ovn_fdb_init(&put_fdbs);
+    hmap_init(&put_fdbs);
 }
 
 static void
 destroy_fdb_entries(void)
 {
-    ovn_fdbs_destroy(&put_fdbs);
+    hmap_destroy(&put_fdbs);
 }
 
 static const struct sbrec_fdb *
@@ -8471,21 +8474,21 @@  run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
             struct ovsdb_idl_index *sbrec_port_binding_by_key,
             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-            const struct fdb_entry *fdb_e)
+            const struct fdb *fdb)
 {
     /* Convert ethernet argument to string form for database. */
     char mac_string[ETH_ADDR_STRLEN + 1];
     snprintf(mac_string, sizeof mac_string,
-             ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac));
+             ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb->data.mac));
 
     /* Update or add an FDB entry. */
     const struct sbrec_port_binding *sb_entry_pb = NULL;
     const struct sbrec_port_binding *new_entry_pb = NULL;
     const struct sbrec_fdb *sb_fdb =
-        fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string);
+            fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb->data.dp_key, mac_string);
     if (!sb_fdb) {
         sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn);
-        sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key);
+        sbrec_fdb_set_dp_key(sb_fdb, fdb->data.dp_key);
         sbrec_fdb_set_mac(sb_fdb, mac_string);
     } else {
         /* check whether sb_fdb->port_key is vif or localnet type */
@@ -8494,12 +8497,12 @@  run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
             sb_fdb->dp_key, sb_fdb->port_key);
         new_entry_pb = lport_lookup_by_key(
             sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
-            fdb_e->dp_key, fdb_e->port_key);
+            fdb->data.dp_key, fdb->data.port_key);
     }
     /* Do not have localnet overwrite a previous vif entry */
     if (!sb_entry_pb || !new_entry_pb || strcmp(sb_entry_pb->type, "") ||
         strcmp(new_entry_pb->type, "localnet")) {
-        sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
+        sbrec_fdb_set_port_key(sb_fdb, fdb->data.port_key);
     }
 
     /* For backward compatibility check if timestamp column is available
@@ -8520,13 +8523,13 @@  run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
         return;
     }
 
-    const struct fdb_entry *fdb_e;
-    HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) {
+    const struct fdb *fdb;
+    HMAP_FOR_EACH (fdb, hmap_node, &put_fdbs) {
         run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac,
                     sbrec_port_binding_by_key,
-                    sbrec_datapath_binding_by_key, fdb_e);
+                    sbrec_datapath_binding_by_key, fdb);
     }
-    ovn_fdbs_flush(&put_fdbs);
+    fdbs_clear(&put_fdbs);
 }
 
 
@@ -8543,9 +8546,17 @@  static void
 pinctrl_handle_put_fdb(const struct flow *md, const struct flow *headers)
                        OVS_REQUIRES(pinctrl_mutex)
 {
-    uint32_t dp_key = ntohll(md->metadata);
-    uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0];
+    if (hmap_count(&put_mac_bindings) >= MAX_FDB_ENTRIES) {
+        COVERAGE_INC(pinctrl_drop_put_mac_binding);
+        return;
+    }
+
+    struct fdb_data fdb_data = (struct fdb_data) {
+            .dp_key =  ntohll(md->metadata),
+            .port_key =  md->regs[MFF_LOG_INPORT - MFF_REG0],
+            .mac = headers->dl_src,
+    };
 
-    ovn_fdb_add(&put_fdbs, dp_key, headers->dl_src, port_key);
+    fdb_add(&put_fdbs, fdb_data);
     notify_pinctrl_main();
 }
diff --git a/controller/statctrl.c b/controller/statctrl.c
index cce31cce6..ca514bbee 100644
--- a/controller/statctrl.c
+++ b/controller/statctrl.c
@@ -136,7 +136,7 @@  statctrl_init(void)
             .table_id = OFTABLE_MAC_CACHE_USE,
     };
     STATS_NODE(MAC_BINDING, mac_binding_request, mac_cache_stats_destroy,
-               mac_cache_mb_stats_process_flow_stats, mac_cache_mb_stats_run);
+               mac_binding_stats_process_flow_stats, mac_binding_stats_run);
 
     struct ofputil_flow_stats_request fdb_request = {
             .cookie = htonll(0),
@@ -146,8 +146,7 @@  statctrl_init(void)
             .table_id = OFTABLE_LOOKUP_FDB,
     };
     STATS_NODE(FDB, fdb_request, mac_cache_stats_destroy,
-               mac_cache_fdb_stats_process_flow_stats,
-               mac_cache_fdb_stats_run);
+               fdb_stats_process_flow_stats, fdb_stats_run);
 
     statctrl_ctx.thread = ovs_thread_create("ovn_statctrl",
                                             statctrl_thread_handler,