]> git.proxmox.com Git - mirror_ovs.git/blobdiff - lib/mac-learning.c
cirrus: Use FreeBSD 12.2.
[mirror_ovs.git] / lib / mac-learning.c
index 190920b97873896538dd205297a7622633d53af0..9442858d95c3c5f2983dce1f49aa7ab2d6951e84 100644 (file)
@@ -23,8 +23,8 @@
 #include "bitmap.h"
 #include "coverage.h"
 #include "hash.h"
-#include "list.h"
-#include "poll-loop.h"
+#include "openvswitch/list.h"
+#include "openvswitch/poll-loop.h"
 #include "timeval.h"
 #include "unaligned.h"
 #include "util.h"
@@ -32,6 +32,8 @@
 
 COVERAGE_DEFINE(mac_learning_learned);
 COVERAGE_DEFINE(mac_learning_expired);
+COVERAGE_DEFINE(mac_learning_evicted);
+COVERAGE_DEFINE(mac_learning_moved);
 
 /* Returns the number of seconds since 'e' (within 'ml') was last learned. */
 int
@@ -42,7 +44,7 @@ mac_entry_age(const struct mac_learning *ml, const struct mac_entry *e)
 }
 
 static uint32_t
-mac_table_hash(const struct mac_learning *ml, const uint8_t mac[ETH_ADDR_LEN],
+mac_table_hash(const struct mac_learning *ml, const struct eth_addr mac,
                uint16_t vlan)
 {
     return hash_mac(mac, vlan, ml->secret);
@@ -56,7 +58,7 @@ mac_entry_from_lru_node(struct ovs_list *list)
 
 static struct mac_entry *
 mac_entry_lookup(const struct mac_learning *ml,
-                 const uint8_t mac[ETH_ADDR_LEN], uint16_t vlan)
+                 const struct eth_addr mac, uint16_t vlan)
 {
     struct mac_entry *e;
 
@@ -101,9 +103,9 @@ mac_entry_set_port(struct mac_learning *ml, struct mac_entry *e, void *port)
 
         if (e->mlport) {
             struct mac_learning_port *mlport = e->mlport;
-            list_remove(&e->port_lru_node);
+            ovs_list_remove(&e->port_lru_node);
 
-            if (list_is_empty(&mlport->port_lrus)) {
+            if (ovs_list_is_empty(&mlport->port_lrus)) {
                 ovs_assert(mlport->heap_node.priority == 1);
                 hmap_remove(&ml->ports_by_ptr, &mlport->hmap_node);
                 heap_remove(&ml->ports_by_usage, &mlport->heap_node);
@@ -126,12 +128,12 @@ mac_entry_set_port(struct mac_learning *ml, struct mac_entry *e, void *port)
                             hash_pointer(port, ml->secret));
                 heap_insert(&ml->ports_by_usage, &mlport->heap_node, 1);
                 mlport->port = port;
-                list_init(&mlport->port_lrus);
+                ovs_list_init(&mlport->port_lrus);
             } else {
                 heap_change(&ml->ports_by_usage, &mlport->heap_node,
                             mlport->heap_node.priority + 1);
             }
-            list_push_back(&mlport->port_lrus, &e->port_lru_node);
+            ovs_list_push_back(&mlport->port_lrus, &e->port_lru_node);
             e->mlport = mlport;
         }
     }
@@ -148,8 +150,10 @@ evict_mac_entry_fairly(struct mac_learning *ml)
 
     mlport = CONTAINER_OF(heap_max(&ml->ports_by_usage),
                           struct mac_learning_port, heap_node);
-    e = CONTAINER_OF(list_front(&mlport->port_lrus),
+    e = CONTAINER_OF(ovs_list_front(&mlport->port_lrus),
                      struct mac_entry, port_lru_node);
+    COVERAGE_INC(mac_learning_evicted);
+    ml->total_evicted++;
     mac_learning_expire(ml, e);
 }
 
@@ -160,7 +164,7 @@ static bool
 get_lru(struct mac_learning *ml, struct mac_entry **e)
     OVS_REQ_RDLOCK(ml->rwlock)
 {
-    if (!list_is_empty(&ml->lrus)) {
+    if (!ovs_list_is_empty(&ml->lrus)) {
         *e = mac_entry_from_lru_node(ml->lrus.next);
         return true;
     } else {
@@ -177,6 +181,18 @@ normalize_idle_time(unsigned int idle_time)
             : idle_time);
 }
 
+/* Clear all the mac_learning statistics */
+void
+mac_learning_clear_statistics(struct mac_learning *ml)
+{
+    if (ml != NULL) {
+        ml->total_learned = 0;
+        ml->total_expired = 0;
+        ml->total_evicted = 0;
+        ml->total_moved = 0;
+    }
+}
+
 /* Creates and returns a new MAC learning table with an initial MAC aging
  * timeout of 'idle_time' seconds and an initial maximum of MAC_DEFAULT_MAX
  * entries. */
@@ -186,7 +202,7 @@ mac_learning_create(unsigned int idle_time)
     struct mac_learning *ml;
 
     ml = xmalloc(sizeof *ml);
-    list_init(&ml->lrus);
+    ovs_list_init(&ml->lrus);
     hmap_init(&ml->table);
     ml->secret = random_uint32();
     ml->flood_vlans = NULL;
@@ -197,6 +213,7 @@ mac_learning_create(unsigned int idle_time)
     heap_init(&ml->ports_by_usage);
     ovs_refcount_init(&ml->ref_cnt);
     ovs_rwlock_init(&ml->rwlock);
+    mac_learning_clear_statistics(ml);
     return ml;
 }
 
@@ -286,7 +303,7 @@ is_learning_vlan(const struct mac_learning *ml, uint16_t vlan)
  * '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)
+                       const struct eth_addr src_mac, uint16_t vlan)
 {
     return ml && is_learning_vlan(ml, vlan) && !eth_addr_is_multicast(src_mac);
 }
@@ -301,7 +318,7 @@ mac_learning_may_learn(const struct mac_learning *ml,
  * the new entry's port to a nonnull value with mac_entry_set_port(). */
 struct mac_entry *
 mac_learning_insert(struct mac_learning *ml,
-                    const uint8_t src_mac[ETH_ADDR_LEN], uint16_t vlan)
+                    const struct eth_addr src_mac, uint16_t vlan)
 {
     struct mac_entry *e;
 
@@ -315,31 +332,163 @@ mac_learning_insert(struct mac_learning *ml,
 
         e = xmalloc(sizeof *e);
         hmap_insert(&ml->table, &e->hmap_node, hash);
-        memcpy(e->mac, src_mac, ETH_ADDR_LEN);
+        e->mac = src_mac;
         e->vlan = vlan;
         e->grat_arp_lock = TIME_MIN;
         e->mlport = NULL;
         COVERAGE_INC(mac_learning_learned);
+        ml->total_learned++;
     } else {
-        list_remove(&e->lru_node);
+        ovs_list_remove(&e->lru_node);
     }
 
     /* Mark 'e' as recently used. */
-    list_push_back(&ml->lrus, &e->lru_node);
+    ovs_list_push_back(&ml->lrus, &e->lru_node);
     if (e->mlport) {
-        list_remove(&e->port_lru_node);
-        list_push_back(&e->mlport->port_lrus, &e->port_lru_node);
+        ovs_list_remove(&e->port_lru_node);
+        ovs_list_push_back(&e->mlport->port_lrus, &e->port_lru_node);
     }
     e->expires = time_now() + ml->idle_time;
 
     return e;
 }
 
+/* Checks whether a MAC learning update is necessary for MAC learning table
+ * 'ml' given that a packet matching 'src' was received on 'in_port' in 'vlan',
+ * and given that the packet was gratuitous ARP if 'is_gratuitous_arp' is
+ * 'true' and 'in_port' is a bond port if 'is_bond' is 'true'.
+ *
+ * Most packets processed through the MAC learning table do not actually
+ * change it in any way.  This function requires only a read lock on the MAC
+ * learning table, so it is much cheaper in this common case.
+ *
+ * Keep the code here synchronized with that in update_learning_table__()
+ * below. */
+static bool
+is_mac_learning_update_needed(const struct mac_learning *ml,
+                              struct eth_addr src, int vlan,
+                              bool is_gratuitous_arp, bool is_bond,
+                              void *in_port)
+    OVS_REQ_RDLOCK(ml->rwlock)
+{
+    struct mac_entry *mac;
+
+    if (!mac_learning_may_learn(ml, src, vlan)) {
+        return false;
+    }
+
+    mac = mac_learning_lookup(ml, src, vlan);
+    if (!mac || mac_entry_age(ml, mac)) {
+        return true;
+    }
+
+    if (is_gratuitous_arp) {
+        /* We don't want to learn from gratuitous ARP packets that are
+         * reflected back over bond members so we lock the learning table.  For
+         * more detail, see the bigger comment in update_learning_table__(). */
+        if (!is_bond) {
+            return true;   /* Need to set the gratuitous ARP lock. */
+        } else if (mac_entry_is_grat_arp_locked(mac)) {
+            return false;
+        }
+    }
+
+    return mac_entry_get_port(ml, mac) != in_port /* ofbundle */;
+}
+
+/* Updates MAC learning table 'ml' given that a packet matching 'src' was
+ * received on 'in_port' in 'vlan', and given that the packet was gratuitous
+ * ARP if 'is_gratuitous_arp' is 'true' and 'in_port' is a bond port if
+ * 'is_bond' is 'true'.
+ *
+ * This code repeats all the checks in is_mac_learning_update_needed() because
+ * the lock was released between there and here and thus the MAC learning state
+ * could have changed.
+ *
+ * Returns 'true' if 'ml' was updated, 'false' otherwise.
+ *
+ * Keep the code here synchronized with that in is_mac_learning_update_needed()
+ * above. */
+static bool
+update_learning_table__(struct mac_learning *ml, struct eth_addr src,
+                        int vlan, bool is_gratuitous_arp, bool is_bond,
+                        void *in_port)
+    OVS_REQ_WRLOCK(ml->rwlock)
+{
+    struct mac_entry *mac;
+
+    if (!mac_learning_may_learn(ml, src, vlan)) {
+        return false;
+    }
+
+    mac = mac_learning_insert(ml, src, vlan);
+    if (is_gratuitous_arp) {
+        /* Gratuitous ARP packets received over non-bond interfaces could be
+         * reflected back over bond members.  We don't want to learn from these
+         * reflected packets, so we lock each entry for which a gratuitous ARP
+         * packet was received over a non-bond interface and refrain from
+         * learning from gratuitous ARP packets that arrive over bond
+         * interfaces for this entry while the lock is in effect. Refer to the
+         * 'ovs-vswitch Internals' document for more in-depth discussion on
+         * this topic. */
+        if (!is_bond) {
+            mac_entry_set_grat_arp_lock(mac);
+        } else if (mac_entry_is_grat_arp_locked(mac)) {
+            return false;
+        }
+    }
+
+    if (mac_entry_get_port(ml, mac) != in_port) {
+        if (mac_entry_get_port(ml, mac) != NULL) {
+            COVERAGE_INC(mac_learning_moved);
+            ml->total_moved++;
+        }
+        mac_entry_set_port(ml, mac, in_port);
+        return true;
+    }
+    return false;
+}
+
+/* Updates MAC learning table 'ml' given that a packet matching 'src' was
+ * received on 'in_port' in 'vlan', and given that the packet was gratuitous
+ * ARP if 'is_gratuitous_arp' is 'true' and 'in_port' is a bond port if
+ * 'is_bond' is 'true'.
+ *
+ * Returns 'true' if 'ml' was updated, 'false' otherwise. */
+bool
+mac_learning_update(struct mac_learning *ml, struct eth_addr src,
+                    int vlan, bool is_gratuitous_arp, bool is_bond,
+                    void *in_port)
+    OVS_EXCLUDED(ml->rwlock)
+{
+    bool need_update;
+    bool updated = false;
+
+    /* Don't learn the OFPP_NONE port. */
+    if (in_port != NULL) {
+        /* First try the common case: no change to MAC learning table. */
+        ovs_rwlock_rdlock(&ml->rwlock);
+        need_update = is_mac_learning_update_needed(ml, src, vlan,
+                                                    is_gratuitous_arp, is_bond,
+                                                    in_port);
+        ovs_rwlock_unlock(&ml->rwlock);
+
+        if (need_update) {
+            /* Slow path: MAC learning table might need an update. */
+            ovs_rwlock_wrlock(&ml->rwlock);
+            updated = update_learning_table__(ml, src, vlan, is_gratuitous_arp,
+                                              is_bond, in_port);
+            ovs_rwlock_unlock(&ml->rwlock);
+        }
+    }
+    return updated;
+}
+
 /* Looks up MAC 'dst' for VLAN 'vlan' in 'ml' and returns the associated MAC
  * learning entry, if any. */
 struct mac_entry *
 mac_learning_lookup(const struct mac_learning *ml,
-                    const uint8_t dst[ETH_ADDR_LEN], uint16_t vlan)
+                    const struct eth_addr dst, uint16_t vlan)
 {
     if (eth_addr_is_multicast(dst)) {
         /* No tag because the treatment of multicast destinations never
@@ -364,7 +513,7 @@ mac_learning_expire(struct mac_learning *ml, struct mac_entry *e)
     ml->need_revalidate = true;
     mac_entry_set_port(ml, e, NULL);
     hmap_remove(&ml->table, &e->hmap_node);
-    list_remove(&e->lru_node);
+    ovs_list_remove(&e->lru_node);
     free(e);
 }
 
@@ -391,6 +540,7 @@ mac_learning_run(struct mac_learning *ml)
            && (hmap_count(&ml->table) > ml->max_entries
                || time_now() >= e->expires)) {
         COVERAGE_INC(mac_learning_expired);
+        ml->total_expired++;
         mac_learning_expire(ml, e);
     }
 
@@ -405,7 +555,7 @@ mac_learning_wait(struct mac_learning *ml)
     if (hmap_count(&ml->table) > ml->max_entries
         || ml->need_revalidate) {
         poll_immediate_wake();
-    } else if (!list_is_empty(&ml->lrus)) {
+    } else if (!ovs_list_is_empty(&ml->lrus)) {
         struct mac_entry *e = mac_entry_from_lru_node(ml->lrus.next);
         poll_timer_wait_until(e->expires * 1000LL);
     }