[ovs-dev] [PATCH 04/18] lib: Add igmp generic snooping library bits

Flavio Leitner fbl at redhat.com
Fri Apr 11 21:34:09 UTC 2014


This patch adds generic IPv4 library code to deal with
igmp snooping that is implemented in follow-up patches.

Signed-off-by: Cong Wang <amwang at redhat.com>
Signed-off-by: Daniel Borkmann <dborkman at redhat.com>
Acked-by: Thomas Graf <tgraf at redhat.com>
Signed-off-by: Flavio Leitner <fbl at redhat.com>
---
 lib/automake.mk      |   2 +
 lib/mcast-snooping.c | 794 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/mcast-snooping.h | 175 ++++++++++++
 3 files changed, 971 insertions(+)
 create mode 100644 lib/mcast-snooping.c
 create mode 100644 lib/mcast-snooping.h

diff --git a/lib/automake.mk b/lib/automake.mk
index fcd2c5b..11c117e 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -99,6 +99,8 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/mac-learning.h \
 	lib/match.c \
 	lib/match.h \
+	lib/mcast-snooping.c \
+	lib/mcast-snooping.h \
 	lib/memory.c \
 	lib/memory.h \
 	lib/meta-flow.c \
diff --git a/lib/mcast-snooping.c b/lib/mcast-snooping.c
new file mode 100644
index 0000000..9caee39
--- /dev/null
+++ b/lib/mcast-snooping.c
@@ -0,0 +1,794 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * Based on mac-learning implementation.
+ *
+ * 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 "mcast-snooping.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include "bitmap.h"
+#include "byte-order.h"
+#include "coverage.h"
+#include "hash.h"
+#include "list.h"
+#include "poll-loop.h"
+#include "timeval.h"
+#include "entropy.h"
+#include "unaligned.h"
+#include "util.h"
+#include "vlan-bitmap.h"
+#include "vlog.h"
+
+COVERAGE_DEFINE(mcast_snooping_learned);
+COVERAGE_DEFINE(mcast_snooping_expired);
+
+static struct mcast_mrouter_bundle *
+mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
+                              void *port);
+
+bool
+mcast_snooping_enabled(const struct mcast_snooping *ms)
+{
+    return !!ms;
+}
+
+bool
+mcast_snooping_flood_unreg(const struct mcast_snooping *ms)
+{
+    return ms->flood_unreg;
+}
+
+/* Decides whether it should be forwarded only to routers
+ * or flooded to all ports
+ * Returns true when it is intended for routers
+ * Returns false when it should be flooded to all ports
+ */
+bool
+mcast_snooping_is_membership(ovs_be16 igmp_type)
+{
+    switch (ntohs(igmp_type)) {
+        case IGMP_HOST_MEMBERSHIP_REPORT:
+        case IGMPV2_HOST_MEMBERSHIP_REPORT:
+        case IGMP_HOST_LEAVE_MESSAGE:
+            return true;
+            break;
+    };
+    return false;
+}
+
+/* Returns the number of seconds since the multicast group
+ * was learned in a port  */
+int
+mcast_bundle_age(const struct mcast_snooping *ms,
+                 const struct mcast_entry_bundle *b)
+{
+    time_t remaining = b->expires - time_now();
+    return ms->idle_time - remaining;
+}
+
+static uint32_t
+mcast_table_hash(const struct mcast_snooping *ms, const struct mcast_ip *grp,
+                 uint16_t vlan)
+{
+    if (grp->proto == htons(ETH_TYPE_IP)) {
+        return hash_3words(grp->u.ip4, vlan, ms->secret);
+    }
+
+    return 0;
+}
+
+static struct mcast_entry_bundle *
+mcast_entry_bundle_from_bundle_node(struct list *list)
+{
+    return CONTAINER_OF(list, struct mcast_entry_bundle, bundle_node);
+}
+
+static struct mcast_entry *
+mcast_entry_from_lru_node(struct list *list)
+{
+    return CONTAINER_OF(list, struct mcast_entry, lru_node);
+}
+
+/* helper to match a group address */
+static bool
+mcast_entry_match_group(struct mcast_entry *e, const struct mcast_ip *grp)
+{
+    if (grp->proto == htons(ETH_TYPE_IP)) {
+        return e->group.u.ip4 == grp->u.ip4;
+    }
+
+    return false;
+}
+
+static bool
+mcast_entry_match(struct mcast_entry *e, const struct mcast_ip *grp,
+                  uint16_t vlan)
+{
+    return e->vlan == vlan && mcast_entry_match_group(e, grp);
+}
+
+/* Searches 'ms' for and returns an mcast entry for destination address
+ * 'dip' in 'vlan' */
+struct mcast_entry *
+mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 dip,
+                       uint16_t vlan)
+{
+    struct mcast_entry *e;
+    uint32_t hash;
+    struct mcast_ip grp;
+
+    grp.u.ip4 = dip;
+    grp.proto = htons(ETH_TYPE_IP);
+    hash = mcast_table_hash(ms, &grp, vlan);
+    HMAP_FOR_EACH_WITH_HASH (e, hmap_node, hash, &ms->table) {
+        if (mcast_entry_match(e, &grp, vlan)) {
+            return e;
+        }
+    }
+    return NULL;
+}
+
+/* If the LRU list is not empty, stores the least-recently-used entry
+ * in '*e' and returns true.  Otherwise, if the LRU list is empty,
+ * stores NULL in '*e' and return false. */
+static bool
+entry_get_lru(const struct mcast_snooping *ms, struct mcast_entry **e)
+    OVS_REQ_RDLOCK(ms->rwlock)
+{
+    if (!list_is_empty(&ms->lrus)) {
+        *e = mcast_entry_from_lru_node(ms->lrus.next);
+        return true;
+    } else {
+        *e = NULL;
+        return false;
+    }
+}
+
+static unsigned int
+normalize_idle_time(unsigned int idle_time)
+{
+    return (idle_time < 15 ? 15
+            : idle_time > 3600 ? 3600
+            : idle_time);
+}
+
+/* Creates and returns a new mcast table with an initial mcast aging
+ * timeout of 'idle_time' seconds and an initial maximum of
+ * MCAST_DEFAULT_MAX entries. */
+struct mcast_snooping *
+mcast_snooping_create(void)
+{
+    struct mcast_snooping *ms;
+
+    ms = xmalloc(sizeof *ms);
+    list_init(&ms->lrus);
+    list_init(&ms->mrouter_lru);
+    list_init(&ms->fport_list);
+    hmap_init(&ms->table);
+    ms->secret = random_uint32();
+    ms->need_revalidate = false;
+    atomic_init(&ms->ref_cnt, 1);
+    ovs_rwlock_init(&ms->rwlock);
+    mcast_snooping_set_idle_time(ms, MCAST_ENTRY_DEFAULT_IDLE_TIME);
+    mcast_snooping_set_max_entries(ms, MCAST_DEFAULT_MAX_ENTRIES);
+    return ms;
+}
+
+struct mcast_snooping *
+mcast_snooping_ref(const struct mcast_snooping *ms_)
+{
+    struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_);
+    if (ms) {
+        int orig;
+        atomic_add(&ms->ref_cnt, 1, &orig);
+        ovs_assert(orig > 0);
+    }
+    return ms;
+}
+
+/* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
+void
+mcast_snooping_unref(struct mcast_snooping *ms)
+{
+    int orig;
+
+    if (!mcast_snooping_enabled(ms)) {
+        return;
+    }
+
+    atomic_sub(&ms->ref_cnt, 1, &orig);
+    ovs_assert(orig > 0);
+    if (orig == 1) {
+        mcast_snooping_flush(ms);
+        hmap_destroy(&ms->table);
+        ovs_rwlock_destroy(&ms->rwlock);
+        free(ms);
+    }
+}
+
+/* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */
+void
+mcast_snooping_set_idle_time(struct mcast_snooping *ms,
+                             unsigned int idle_time)
+{
+    struct mcast_entry *e;
+    struct mcast_entry_bundle *b;
+    int delta;
+
+    idle_time = normalize_idle_time(idle_time);
+    if (idle_time != ms->idle_time) {
+        delta = (int) idle_time - (int) ms->idle_time;
+        LIST_FOR_EACH (e, lru_node, &ms->lrus) {
+            LIST_FOR_EACH(b, bundle_node, &e->lru_bundles) {
+                b->expires += delta;
+            }
+        }
+        ms->idle_time = idle_time;
+    }
+}
+
+/* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it
+ * to be within a reasonable range. */
+void
+mcast_snooping_set_max_entries(struct mcast_snooping *ms,
+                               size_t max_entries)
+{
+    ms->max_entries = (max_entries < 10 ? 10
+                       : max_entries > 1000 * 1000 ? 1000 * 1000
+                       : max_entries);
+}
+
+/* Sets if unregistered multicast packets should be flooded to
+ * all ports or only to ports connected to multicast routers */
+void
+mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
+{
+    ms->flood_unreg = enable;
+}
+
+/* Insert a new bundle to the mcast entry or update its
+ * position and expiration if it is already there. */
+static struct mcast_entry_bundle *
+mcast_entry_insert_bundle(struct mcast_entry *e, void *port, int idle_time)
+{
+    struct mcast_entry_bundle *b;
+
+    ovs_assert(e != NULL && port != NULL);
+
+    LIST_FOR_EACH(b, bundle_node, &e->lru_bundles) {
+        if (b->port.p == port) {
+            break;
+        }
+    }
+
+    if (b && b->port.p == port) {
+        list_remove(&b->bundle_node);
+    } else {
+        b = xmalloc(sizeof *b);
+        list_init(&b->bundle_node);
+        b->port.p = port;
+    }
+
+    b->expires = time_now() + idle_time;
+    list_push_back(&e->lru_bundles, &b->bundle_node);
+    return b;
+}
+
+/* Return true if multicast still has bundles associated
+ * Return false if there is no bundles */
+static bool
+mcast_entry_has_bundles(struct mcast_entry *e)
+{
+    ovs_assert(e != NULL);
+    return !list_is_empty(&e->lru_bundles);
+}
+
+/* Delete 'e' from the 'ms' hash table.
+ * Caller is responsible to clean bundle lru first */
+static void
+mcast_snooping_flush_entry__(struct mcast_snooping *ms,
+                             struct mcast_entry *e)
+{
+    ovs_assert(list_is_empty(&e->lru_bundles));
+    hmap_remove(&ms->table, &e->hmap_node);
+    list_remove(&e->lru_node);
+    free(e);
+}
+
+/* Flush out mcast entry and its bundles */
+static void
+mcast_snooping_flush_entry(struct mcast_snooping *ms, struct mcast_entry *e)
+{
+    struct mcast_entry_bundle *b, *next_b;
+
+    ovs_assert(ms != NULL && e != NULL);
+
+    LIST_FOR_EACH_SAFE(b, next_b, bundle_node, &e->lru_bundles) {
+        list_remove(&b->bundle_node);
+        free(b);
+    }
+    mcast_snooping_flush_entry__(ms, e);
+    ms->need_revalidate = true;
+}
+
+
+/* Delete bundle returning true if it succeed
+ * false if it didn't find the entry */
+static bool
+mcast_entry_delete_bundle(struct mcast_snooping *ms, struct mcast_entry *e,
+                          void *port)
+{
+    struct mcast_entry_bundle *b;
+
+    ovs_assert(ms != NULL && e != NULL);
+
+    LIST_FOR_EACH(b, bundle_node, &e->lru_bundles) {
+        if (b->port.p == port) {
+            list_remove(&b->bundle_node);
+            free(b);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/* check if any bundle has expired and delete it */
+static int
+mcast_snooping_prune_expired(struct mcast_snooping *ms, struct mcast_entry *e)
+{
+    int expired;
+    struct mcast_entry_bundle *b, *next_b;
+
+    ovs_assert(ms != NULL && e != NULL);
+
+    expired = 0;
+    LIST_FOR_EACH_SAFE(b, next_b, bundle_node, &e->lru_bundles) {
+        if (time_now() >= b->expires) {
+            list_remove(&b->bundle_node);
+            free(b);
+            expired++;
+        }
+    }
+
+    if (!mcast_entry_has_bundles(e)) {
+        mcast_snooping_flush_entry__(ms, e);
+        expired++;
+    }
+
+    if (expired) {
+        ms->need_revalidate = true;
+        COVERAGE_ADD(mcast_snooping_expired, expired);
+    }
+
+    return expired;
+}
+
+/* Add a multicast group to the mdb. If it exists, then
+ * move to the last position in the LRU list.
+ */
+bool mcast_snooping_add_group(struct mcast_snooping *ms,
+                              const struct mcast_ip *grp, uint16_t vlan,
+                              void *port)
+{
+    bool needs_update;
+    struct mcast_entry *e;
+
+    /* avoid duplicate packets */
+    if (mcast_snooping_mrouter_lookup(ms, vlan, port)
+        || mcast_snooping_fport_lookup(ms, vlan, port)) {
+        return false;
+    }
+
+    needs_update = false;
+    e = mcast_snooping_lookup4(ms, grp->u.ip4, vlan);
+    if (!e) {
+        uint32_t hash = mcast_table_hash(ms, grp, vlan);
+
+        if (hmap_count(&ms->table) >= ms->max_entries) {
+            entry_get_lru(ms, &e);
+            mcast_snooping_flush_entry(ms, e);
+        }
+
+        e = xmalloc(sizeof *e);
+        hmap_insert(&ms->table, &e->hmap_node, hash);
+        e->group = *grp;
+        e->vlan = vlan;
+        list_init(&e->lru_bundles);
+        needs_update = true;
+    } else {
+        list_remove(&e->lru_node);
+    }
+    mcast_entry_insert_bundle(e, port, ms->idle_time);
+    /* Mark 'e' as recently used. */
+    list_push_back(&ms->lrus, &e->lru_node);
+    if (needs_update) {
+        COVERAGE_INC(mcast_snooping_learned);
+        ms->need_revalidate = true;
+    }
+
+    return needs_update;
+}
+
+bool mcast_snooping_leave_group(struct mcast_snooping *ms,
+                                const struct mcast_ip *grp, uint16_t vlan,
+                                void *port)
+{
+    struct mcast_entry *e;
+
+    e = mcast_snooping_lookup4(ms, grp->u.ip4, vlan);
+    if (e && mcast_entry_delete_bundle(ms, e, port)) {
+        ms->need_revalidate = true;
+        return true;
+    }
+
+    return false;
+}
+
+
+/* Router ports */
+
+/* Returns the number of seconds since the multicast router
+ * was learned in a port  */
+int
+mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED,
+                  const struct mcast_mrouter_bundle *mrouter)
+{
+    time_t remaining = mrouter->expires - time_now();
+    return MCAST_MROUTER_PORT_DEFAULT_IDLE_TIME - remaining;
+}
+
+static struct mcast_mrouter_bundle *
+mcast_mrouter_from_lru_node(struct list *list)
+{
+    return CONTAINER_OF(list, struct mcast_mrouter_bundle, lru_node);
+}
+
+/* If the LRU list is not empty, stores the least-recently-used mrouter
+ * in '*m' and returns true.  Otherwise, if the LRU list is empty,
+ * stores NULL in '*m' and return false. */
+static bool
+mrouter_get_lru(const struct mcast_snooping *ms,
+                struct mcast_mrouter_bundle **m)
+    OVS_REQ_RDLOCK(ms->rwlock)
+{
+    if (!list_is_empty(&ms->mrouter_lru)) {
+        *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
+        return true;
+    } else {
+        *m = NULL;
+        return false;
+    }
+}
+
+bool
+mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan, void *port)
+{
+    struct mcast_mrouter_bundle *mrouter;
+
+    ovs_assert(ms != NULL);
+
+    /* avoid duplicate packets */
+    if (mcast_snooping_fport_lookup(ms, vlan, port)) {
+        return false;
+    }
+
+    LIST_FOR_EACH(mrouter, lru_node, &ms->mrouter_lru) {
+        if (mrouter->vlan == vlan && mrouter->port.p == port) {
+            break;
+        }
+    }
+
+    if (mrouter && mrouter->vlan == vlan && mrouter->port.p == port) {
+        list_remove(&mrouter->lru_node);
+    } else {
+        mrouter = xmalloc(sizeof *mrouter);
+        mrouter->vlan = vlan;
+        mrouter->port.p = port;
+        COVERAGE_INC(mcast_snooping_learned);
+        ms->need_revalidate = true;
+    }
+
+    mrouter->expires = time_now() + MCAST_MROUTER_PORT_DEFAULT_IDLE_TIME;
+    list_push_back(&ms->mrouter_lru, &mrouter->lru_node);
+    return ms->need_revalidate;
+}
+
+static struct mcast_mrouter_bundle *
+mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
+                              void *port)
+{
+    struct mcast_mrouter_bundle *mrouter;
+
+    ovs_assert(ms != NULL);
+
+    LIST_FOR_EACH(mrouter, lru_node, &ms->mrouter_lru) {
+        if (mrouter->vlan == vlan && mrouter->port.p == port) {
+            return mrouter;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
+{
+    list_remove(&mrouter->lru_node);
+    free(mrouter);
+}
+
+
+/* Flood ports */
+
+static struct mcast_fport_bundle *
+mcast_fport_from_list_node(struct list *list)
+{
+    return CONTAINER_OF(list, struct mcast_fport_bundle, list_node);
+}
+
+/* If the list is not empty, stores the fport in '*f' and returns true.
+ * Otherwise, if the list is empty, stores NULL in '*f' and return false. */
+static bool
+fport_get(const struct mcast_snooping *ms, struct mcast_fport_bundle **f)
+    OVS_REQ_RDLOCK(ms->rwlock)
+{
+    if (!list_is_empty(&ms->fport_list)) {
+        *f = mcast_fport_from_list_node(ms->fport_list.next);
+        return true;
+    } else {
+        *f = NULL;
+        return false;
+    }
+}
+
+struct mcast_fport_bundle *
+mcast_snooping_fport_lookup(struct mcast_snooping *ms, uint16_t vlan,
+                            void *port)
+{
+    struct mcast_fport_bundle *fport;
+
+    ovs_assert(ms != NULL);
+
+    LIST_FOR_EACH(fport, list_node, &ms->fport_list) {
+        if (fport->vlan == vlan && fport->port.p == port) {
+            return fport;
+        }
+    }
+
+    return NULL;
+}
+
+static bool
+mcast_snooping_add_fport(struct mcast_snooping *ms, uint16_t vlan, void *port)
+{
+    struct mcast_fport_bundle *fport;
+
+    ovs_assert(ms != NULL);
+
+    fport = mcast_snooping_fport_lookup(ms, vlan, port);
+
+    if (!fport) {
+        fport = xmalloc(sizeof *fport);
+        fport->vlan = vlan;
+        fport->port.p = port;
+        COVERAGE_INC(mcast_snooping_learned);
+        ms->need_revalidate = true;
+        list_insert(&ms->fport_list, &fport->list_node);
+    }
+
+    return ms->need_revalidate;
+}
+
+static void
+mcast_snooping_flush_fport(struct mcast_fport_bundle *fport)
+{
+    list_remove(&fport->list_node);
+    free(fport);
+}
+
+void
+mcast_snooping_port_enable_flood(struct mcast_snooping *ms, uint16_t vlan,
+                                 void *port)
+{
+    ovs_assert(ms != NULL);
+    mcast_snooping_add_fport(ms, vlan, port);
+}
+
+void
+mcast_snooping_port_disable_flood(struct mcast_snooping *ms, uint16_t vlan,
+                                  void *port)
+{
+    struct mcast_fport_bundle *fport;
+
+    ovs_assert(ms != NULL);
+    fport = mcast_snooping_fport_lookup(ms, vlan, port);
+    if (fport) {
+        mcast_snooping_flush_fport(fport);
+        ms->need_revalidate = true;
+    }
+
+    return;
+}
+
+
+/* Run and Flush */
+static void
+mcast_snooping_mdb_flush__(struct mcast_snooping *ms)
+{
+    struct mcast_entry *e;
+    struct mcast_mrouter_bundle *mrouter;
+
+    while (entry_get_lru(ms, &e)){
+        mcast_snooping_flush_entry(ms, e);
+    }
+
+    hmap_shrink(&ms->table);
+
+    while (mrouter_get_lru(ms, &mrouter)){
+        mcast_snooping_flush_mrouter(mrouter);
+    }
+}
+
+void
+mcast_snooping_mdb_flush(struct mcast_snooping *ms)
+{
+    if (!mcast_snooping_enabled(ms)) {
+        return;
+    }
+
+    ovs_rwlock_wrlock(&ms->rwlock);
+    mcast_snooping_mdb_flush__(ms);
+    ovs_rwlock_unlock(&ms->rwlock);
+}
+
+/* flushes mdb and flood ports */
+static void
+mcast_snooping_flush__(struct mcast_snooping *ms)
+{
+    struct mcast_entry *e;
+    struct mcast_mrouter_bundle *mrouter;
+    struct mcast_fport_bundle *fport;
+
+    while (entry_get_lru(ms, &e)){
+        mcast_snooping_flush_entry(ms, e);
+    }
+
+    hmap_shrink(&ms->table);
+
+    while (mrouter_get_lru(ms, &mrouter)){
+        mcast_snooping_flush_mrouter(mrouter);
+    }
+
+    while (fport_get(ms, &fport)){
+        mcast_snooping_flush_fport(fport);
+    }
+}
+
+void
+mcast_snooping_flush(struct mcast_snooping *ms)
+{
+    if (!mcast_snooping_enabled(ms)) {
+        return;
+    }
+
+    ovs_rwlock_wrlock(&ms->rwlock);
+    mcast_snooping_flush__(ms);
+    ovs_rwlock_unlock(&ms->rwlock);
+}
+
+static bool
+mcast_snooping_run__(struct mcast_snooping *ms)
+{
+    bool need_revalidate;
+    struct mcast_entry *e;
+    struct mcast_mrouter_bundle *mrouter;
+    int mrouter_expired;
+
+    while (entry_get_lru(ms, &e)) {
+        if (hmap_count(&ms->table) > ms->max_entries) {
+            mcast_snooping_flush_entry(ms, e);
+        } else {
+            if (!mcast_snooping_prune_expired(ms, e)) {
+                break;
+            }
+        }
+    }
+
+    hmap_shrink(&ms->table);
+
+    mrouter_expired = 0;
+    while (mrouter_get_lru(ms, &mrouter)
+           && time_now() >= mrouter->expires) {
+        mcast_snooping_flush_mrouter(mrouter);
+        mrouter_expired++;
+    }
+
+    if (mrouter_expired) {
+        ms->need_revalidate = true;
+        COVERAGE_ADD(mcast_snooping_expired, mrouter_expired);
+    }
+
+    need_revalidate = ms->need_revalidate;
+    ms->need_revalidate = false;
+
+    return need_revalidate;
+}
+
+/* Does periodic work required by 'ms'. Returns true if something changed
+ * that may require flow revalidation. */
+bool
+mcast_snooping_run(struct mcast_snooping *ms)
+{
+    bool need_revalidate;
+
+    if (!mcast_snooping_enabled(ms)) {
+        return false;
+    }
+
+    ovs_rwlock_wrlock(&ms->rwlock);
+    need_revalidate = mcast_snooping_run__(ms);
+    ovs_rwlock_unlock(&ms->rwlock);
+
+    return need_revalidate;
+}
+
+static void
+mcast_snooping_wait__(struct mcast_snooping *ms)
+{
+
+    if (hmap_count(&ms->table) > ms->max_entries
+        || ms->need_revalidate) {
+        poll_immediate_wake();
+    } else {
+        struct mcast_entry *e;
+        struct mcast_entry_bundle *bundle;
+        struct mcast_mrouter_bundle *mrouter;
+        long long int mrouter_msec;
+        long long int msec = 0;
+
+        if (!list_is_empty(&ms->lrus)) {
+            e = mcast_entry_from_lru_node(ms->lrus.next);
+            bundle = mcast_entry_bundle_from_bundle_node(e->lru_bundles.next);
+            msec = bundle->expires * 1000LL;
+        }
+
+        if (!list_is_empty(&ms->mrouter_lru)) {
+            mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
+            mrouter_msec = mrouter->expires * 1000LL;
+            msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec;
+        }
+
+        if (msec) {
+            poll_timer_wait_until(msec);
+        }
+    }
+}
+
+void
+mcast_snooping_wait(struct mcast_snooping *ms)
+{
+    if (!mcast_snooping_enabled(ms)) {
+        return;
+    }
+
+    ovs_rwlock_rdlock(&ms->rwlock);
+    mcast_snooping_wait__(ms);
+    ovs_rwlock_unlock(&ms->rwlock);
+}
diff --git a/lib/mcast-snooping.h b/lib/mcast-snooping.h
new file mode 100644
index 0000000..2ad4c9c
--- /dev/null
+++ b/lib/mcast-snooping.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * Based on mac-learning implementation.
+ *
+ * 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 MCAST_SNOOPING_H
+#define MCAST_SNOOPING_H 1
+
+#include <time.h>
+#include "hmap.h"
+#include "list.h"
+#include "ovs-atomic.h"
+#include "ovs-thread.h"
+#include "packets.h"
+#include "timeval.h"
+
+struct mcast_snooping;
+
+/* Default maximum size of a mcast snooping table, in entries. */
+#define MCAST_DEFAULT_MAX_ENTRIES 2048
+
+/* Time, in seconds, before expiring a mcast_entry due to inactivity. */
+#define MCAST_ENTRY_DEFAULT_IDLE_TIME 300
+
+/* Time, in seconds, before expiring a mrouter_port due to incativity. */
+#define MCAST_MROUTER_PORT_DEFAULT_IDLE_TIME 180
+
+/* Multicast ip table entry.
+ * Guarded by owning 'mcast_snooping''s rwlock */
+struct mcast_ip {
+    union {
+        ovs_be32 ip4;
+    } u;
+    uint8_t proto;
+};
+
+/* Multicast group entry.
+ * Guarded by owning 'mcast_snooping''s rwlock */
+struct mcast_entry {
+    struct hmap_node hmap_node; /* Node in a mcast_learning hmap. */
+    struct mcast_ip group;      /* Known multicast group. */
+    uint16_t vlan;              /* VLAN tag. */
+
+    /* The following are marked guarded to prevent users from iterating
+     * over or accessing a mcast_entry without hodling the parent
+     * mcast_snooping rwlock. */
+    struct list lru_node OVS_GUARDED;     /* Element in 'lrus' list. */
+    struct list lru_bundles OVS_GUARDED;  /* Bundles in least recently used
+                                           * at the front */
+};
+
+struct mcast_entry_bundle {
+    struct list bundle_node;            /* Node in mcast_entry ports list */
+    time_t expires;                     /* when this node expires */
+    /* Learned port. */
+    union {
+        void *p;
+        ofp_port_t ofp_port;
+    } port OVS_GUARDED;
+};
+
+/* port connected to a multicast router */
+struct mcast_mrouter_bundle {
+    struct list lru_node;               /* Node in mrouter_list */
+    time_t expires;                     /* when this node expires */
+    uint16_t vlan;                      /* VLAN tag. */
+    /* Learned port. */
+    union {
+        void *p;
+        ofp_port_t ofp_port;
+    } port OVS_GUARDED;
+};
+
+/* port to flood all multicast traffic */
+struct mcast_fport_bundle {
+    struct list list_node;              /* Node in fport_list */
+    uint16_t vlan;                      /* VLAN tag. */
+    /* Learned port. */
+    union {
+        void *p;
+        ofp_port_t ofp_port;
+    } port OVS_GUARDED;
+};
+
+/* Multicast snooping table. */
+struct mcast_snooping {
+    struct hmap table;          /* Snooping/learning table. */
+    struct list lrus OVS_GUARDED; /* In-use entries, least recently used at the
+                                     front, most recently used at the back. */
+    struct list mrouter_lru OVS_GUARDED; /* Ports connected to mcast routers,
+                                             least recently used at the front,
+                                             most recently used at the back */
+    struct list fport_list OVS_GUARDED;  /* Ports to be flooded with multicast
+                                            packets */
+    uint32_t secret;            /* Secret for randomizing hash table. */
+    unsigned int idle_time;     /* Max age before deleting an entry. */
+    size_t max_entries;         /* Max number of mcast entries. */
+    atomic_int ref_cnt;
+    struct ovs_rwlock rwlock;
+    bool need_revalidate;
+    bool flood_unreg;
+};
+
+/* Basics. */
+bool mcast_snooping_enabled(const struct mcast_snooping *ms);
+bool mcast_snooping_flood_unreg(const struct mcast_snooping *ms);
+int mcast_mrouter_age(const struct mcast_snooping *ms,
+                      const struct mcast_mrouter_bundle *m);
+int mcast_bundle_age(const struct mcast_snooping *ms,
+                     const struct mcast_entry_bundle *b);
+struct mcast_snooping *mcast_snooping_create(void);
+struct mcast_snooping *mcast_snooping_ref(const struct mcast_snooping *);
+void mcast_snooping_unref(struct mcast_snooping *);
+bool mcast_snooping_run(struct mcast_snooping *ms);
+void mcast_snooping_wait(struct mcast_snooping *ms);
+
+/* Configuration. */
+void mcast_snooping_set_idle_time(struct mcast_snooping *ms,
+                                  unsigned int idle_time)
+    OVS_REQ_WRLOCK(ms->rwlock);
+void mcast_snooping_set_max_entries(struct mcast_snooping *ms,
+                                    size_t max_entries)
+    OVS_REQ_WRLOCK(ms->rwlock);
+void
+mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
+    OVS_REQ_WRLOCK(ms->rwlock);
+void mcast_snooping_port_enable_flood(struct mcast_snooping *ms,
+                                      uint16_t vlan, void *port)
+    OVS_REQ_WRLOCK(ml->rwlock);
+void mcast_snooping_port_disable_flood(struct mcast_snooping *ms,
+                                       uint16_t vlan, void *port)
+    OVS_REQ_WRLOCK(ml->rwlock);
+
+/* Lookup. */
+struct mcast_entry *
+mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 dip,
+                       uint16_t vlan)
+    OVS_REQ_RDLOCK(ms->rwlock);
+
+/* Learning. */
+bool mcast_snooping_add_group(struct mcast_snooping *ms,
+                              const struct mcast_ip *grp, uint16_t vlan,
+                              void *port)
+    OVS_REQ_WRLOCK(ms->rwlock);
+bool mcast_snooping_leave_group(struct mcast_snooping *ms,
+                                const struct mcast_ip *grp, uint16_t vlan,
+                                void *port)
+    OVS_REQ_WRLOCK(ms->rwlock);
+bool mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
+                                void *port)
+    OVS_REQ_WRLOCK(ms->rwlock);
+struct mcast_fport_bundle *
+mcast_snooping_fport_lookup(struct mcast_snooping *ms, uint16_t vlan,
+                            void *port)
+    OVS_REQ_RDLOCK(ms->rwlock);
+bool mcast_snooping_is_membership(ovs_be16 igmp_type);
+
+/* Flush. */
+void mcast_snooping_mdb_flush(struct mcast_snooping *ms);
+void mcast_snooping_flush(struct mcast_snooping *ms);
+
+#endif /* mcast-snooping.h */
-- 
1.9.0




More information about the dev mailing list