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 0ee6d8fa8..6a2c3dc68 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();
@@ -4772,18 +4774,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. */
@@ -4793,15 +4795,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,
@@ -4809,13 +4818,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
@@ -4831,11 +4835,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);
@@ -4856,7 +4860,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) {
@@ -4930,24 +4934,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);
 }
@@ -4968,13 +4972,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);
         }
     }
 }
@@ -4987,33 +4991,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");
@@ -5029,19 +5024,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();
     }
 }
@@ -5050,8 +5046,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);
     }
 }
 
@@ -6692,7 +6693,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());
 }
 
@@ -8903,6 +8904,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
@@ -8911,13 +8914,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 *
@@ -8941,21 +8944,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 */
@@ -8964,12 +8967,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
@@ -8990,13 +8993,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);
 }
 
 
@@ -9013,9 +9016,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,
