- bucket = mac_table_bucket(ml, src_mac, vlan);
- e = search_bucket(bucket, src_mac, vlan);
+static bool
+is_learning_vlan(const struct mac_learning *ml, uint16_t vlan)
+{
+ return !ml->flood_vlans || !bitmap_is_set(ml->flood_vlans, vlan);
+}
+
+/* Returns true if 'src_mac' may be learned on 'vlan' for 'ml'.
+ * Returns false if 'ml' is NULL, if src_mac is not valid for learning, or if
+ * 'vlan' is configured on 'ml' to flood all packets. */
+bool
+mac_learning_may_learn(const struct mac_learning *ml,
+ const uint8_t src_mac[ETH_ADDR_LEN], uint16_t vlan)
+{
+ return ml && is_learning_vlan(ml, vlan) && !eth_addr_is_multicast(src_mac);
+}
+
+/* Searches 'ml' for and returns a MAC learning entry for 'src_mac' in 'vlan',
+ * inserting a new entry if necessary. The caller must have already verified,
+ * by calling mac_learning_may_learn(), that 'src_mac' and 'vlan' are
+ * learnable.
+ *
+ * If the returned MAC entry is new (as may be determined by calling
+ * mac_entry_is_new()), then the caller must pass the new entry to
+ * mac_learning_changed(). The caller must also initialize the new entry's
+ * 'port' member. Otherwise calling those functions is at the caller's
+ * discretion. */
+struct mac_entry *
+mac_learning_insert(struct mac_learning *ml,
+ const uint8_t src_mac[ETH_ADDR_LEN], uint16_t vlan)
+{
+ struct mac_entry *e;
+
+ e = mac_entry_lookup(ml, src_mac, vlan);