hlist_del_init_rcu(&f->fdb_node);
        rhashtable_remove_fast(&br->fdb_hash_tbl, &f->rhnode,
                               br_fdb_rht_params);
+       if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &f->flags))
+               atomic_dec(&br->fdb_n_learned);
        fdb_notify(br, f, RTM_DELNEIGH, swdev_notify);
        call_rcu(&f->rcu, fdb_rcu_free);
 }
 
-/* Delete a local entry if no other port had the same address. */
+/* Delete a local entry if no other port had the same address.
+ *
+ * This function should only be called on entries with BR_FDB_LOCAL set,
+ * so even with BR_FDB_ADDED_BY_USER cleared we never need to increase
+ * the accounting for dynamically learned entries again.
+ */
 static void fdb_delete_local(struct net_bridge *br,
                             const struct net_bridge_port *p,
                             struct net_bridge_fdb_entry *f)
                                               __u16 vid,
                                               unsigned long flags)
 {
+       bool learned = !test_bit(BR_FDB_ADDED_BY_USER, &flags) &&
+                      !test_bit(BR_FDB_LOCAL, &flags);
+       u32 max_learned = READ_ONCE(br->fdb_max_learned);
        struct net_bridge_fdb_entry *fdb;
        int err;
 
+       if (likely(learned)) {
+               int n_learned = atomic_read(&br->fdb_n_learned);
+
+               if (unlikely(max_learned && n_learned >= max_learned))
+                       return NULL;
+               __set_bit(BR_FDB_DYNAMIC_LEARNED, &flags);
+       }
+
        fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
        if (!fdb)
                return NULL;
                return NULL;
        }
 
+       if (likely(learned))
+               atomic_inc(&br->fdb_n_learned);
+
        hlist_add_head_rcu(&fdb->fdb_node, &br->fdb_list);
 
        return fdb;
                                        clear_bit(BR_FDB_LOCKED, &fdb->flags);
                        }
 
-                       if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags)))
+                       if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) {
                                set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);
+                               if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED,
+                                                      &fdb->flags))
+                                       atomic_dec(&br->fdb_n_learned);
+                       }
                        if (unlikely(fdb_modified)) {
                                trace_br_fdb_update(br, source, addr, vid, flags);
                                fdb_notify(br, fdb, RTM_NEWNEIGH, true);
                }
 
                set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);
+               if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags))
+                       atomic_dec(&br->fdb_n_learned);
        }
 
        if (fdb_to_nud(br, fdb) != state) {
                if (!p)
                        set_bit(BR_FDB_LOCAL, &fdb->flags);
 
+               if ((swdev_notify || !p) &&
+                   test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags))
+                       atomic_dec(&br->fdb_n_learned);
+
                if (modified)
                        fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify);
        }