summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlavio Leitner <fbl@redhat.com>2014-06-18 22:14:29 -0300
committerBen Pfaff <blp@nicira.com>2014-06-24 11:17:07 -0700
commit4a95091d1f66a86c97954a25bf9f84a40988b83d (patch)
treea439f1d0be69ef286ebce2e4842d125678101906
parent90d7383999889025fb9ddac8100694980f6c44c0 (diff)
downloadopenvswitch-4a95091d1f66a86c97954a25bf9f84a40988b83d.tar.gz
lib: Add IGMP snooping library bits
This patch adds generic IGMP snooping library code that is used in follow-up patches. Signed-off-by: Cong Wang <amwang@redhat.com> Signed-off-by: Daniel Borkmann <dborkman@redhat.com> Acked-by: Thomas Graf <tgraf@redhat.com> Signed-off-by: Flavio Leitner <fbl@redhat.com> Signed-off-by: Ben Pfaff <blp@nicira.com>
-rw-r--r--lib/automake.mk2
-rw-r--r--lib/mcast-snooping.c758
-rw-r--r--lib/mcast-snooping.h194
3 files changed, 954 insertions, 0 deletions
diff --git a/lib/automake.mk b/lib/automake.mk
index 3f984d9bd..70f38a9c9 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -103,6 +103,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 000000000..96639fe26
--- /dev/null
+++ b/lib/mcast-snooping.c
@@ -0,0 +1,758 @@
+/*
+ * 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)
+ OVS_REQ_RDLOCK(ms->rwlock);
+
+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;
+}
+
+bool
+mcast_snooping_is_query(ovs_be16 igmp_type)
+{
+ return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY);
+}
+
+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;
+ }
+ return false;
+}
+
+/* Returns the number of seconds since multicast group 'b' was learned in a
+ * port on 'ms'. */
+int
+mcast_bundle_age(const struct mcast_snooping *ms,
+ const struct mcast_group_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, ovs_be32 grp_ip4,
+ uint16_t vlan)
+{
+ return hash_3words((OVS_FORCE uint32_t) grp_ip4, vlan, ms->secret);
+}
+
+static struct mcast_group_bundle *
+mcast_group_bundle_from_lru_node(struct list *list)
+{
+ return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node);
+}
+
+static struct mcast_group *
+mcast_group_from_lru_node(struct list *list)
+{
+ return CONTAINER_OF(list, struct mcast_group, group_node);
+}
+
+/* Searches 'ms' for and returns an mcast group for destination address
+ * 'dip' in 'vlan'. */
+struct mcast_group *
+mcast_snooping_lookup(const struct mcast_snooping *ms, ovs_be32 dip,
+ uint16_t vlan)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ struct mcast_group *grp;
+ uint32_t hash;
+
+ hash = mcast_table_hash(ms, dip, vlan);
+ HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) {
+ if (grp->vlan == vlan && grp->ip4 == dip) {
+ return grp;
+ }
+ }
+ 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
+group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ if (!list_is_empty(&ms->group_lru)) {
+ *grp = mcast_group_from_lru_node(ms->group_lru.next);
+ return true;
+ } else {
+ *grp = 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 MCAST_ENTRY_DEFAULT_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);
+ hmap_init(&ms->table);
+ list_init(&ms->group_lru);
+ list_init(&ms->mrouter_lru);
+ list_init(&ms->fport_list);
+ ms->secret = random_uint32();
+ ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME;
+ ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES;
+ ms->need_revalidate = false;
+ ms->flood_unreg = true;
+ ovs_refcount_init(&ms->ref_cnt);
+ ovs_rwlock_init(&ms->rwlock);
+ 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) {
+ ovs_refcount_ref(&ms->ref_cnt);
+ }
+ return ms;
+}
+
+/* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
+void
+mcast_snooping_unref(struct mcast_snooping *ms)
+{
+ if (!mcast_snooping_enabled(ms)) {
+ return;
+ }
+
+ if (ovs_refcount_unref(&ms->ref_cnt) == 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)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group *grp;
+ struct mcast_group_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 (grp, group_node, &ms->group_lru) {
+ LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
+ 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)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ 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
+ *
+ * Returns true if previous state differs from current state,
+ * false otherwise. */
+bool
+mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ bool prev = ms->flood_unreg;
+ ms->flood_unreg = enable;
+ return prev != enable;
+}
+
+static struct mcast_group_bundle *
+mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED,
+ struct mcast_group *grp, void *port)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ struct mcast_group_bundle *b;
+
+ LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
+ if (b->port == port) {
+ return b;
+ }
+ }
+ return NULL;
+}
+
+/* Insert a new bundle to the mcast group or update its
+ * position and expiration if it is already there. */
+static struct mcast_group_bundle *
+mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED,
+ struct mcast_group *grp, void *port, int idle_time)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group_bundle *b;
+
+ b = mcast_group_bundle_lookup(ms, grp, port);
+ if (b) {
+ list_remove(&b->bundle_node);
+ } else {
+ b = xmalloc(sizeof *b);
+ list_init(&b->bundle_node);
+ b->port = port;
+ }
+
+ b->expires = time_now() + idle_time;
+ list_push_back(&grp->bundle_lru, &b->bundle_node);
+ return b;
+}
+
+/* Return true if multicast still has bundles associated.
+ * Return false if there is no bundles. */
+static bool
+mcast_group_has_bundles(struct mcast_group *grp)
+{
+ return !list_is_empty(&grp->bundle_lru);
+}
+
+/* Delete 'grp' from the 'ms' hash table.
+ * Caller is responsible to clean bundle lru first. */
+static void
+mcast_snooping_flush_group__(struct mcast_snooping *ms,
+ struct mcast_group *grp)
+{
+ ovs_assert(list_is_empty(&grp->bundle_lru));
+ hmap_remove(&ms->table, &grp->hmap_node);
+ list_remove(&grp->group_node);
+ free(grp);
+}
+
+/* Flush out mcast group and its bundles */
+static void
+mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group_bundle *b, *next_b;
+
+ LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
+ list_remove(&b->bundle_node);
+ free(b);
+ }
+ mcast_snooping_flush_group__(ms, grp);
+ ms->need_revalidate = true;
+}
+
+
+/* Delete bundle returning true if it succeeds,
+ * false if it didn't find the group. */
+static bool
+mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED,
+ struct mcast_group *grp, void *port)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group_bundle *b;
+
+ LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
+ if (b->port == port) {
+ list_remove(&b->bundle_node);
+ free(b);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* If any bundle has expired, delete it. Returns the number of deleted
+ * bundles. */
+static int
+mcast_snooping_prune_expired(struct mcast_snooping *ms,
+ struct mcast_group *grp)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ int expired;
+ struct mcast_group_bundle *b, *next_b;
+ time_t timenow = time_now();
+
+ expired = 0;
+ LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
+ /* This list is sorted on expiration time. */
+ if (b->expires > timenow) {
+ break;
+ }
+ list_remove(&b->bundle_node);
+ free(b);
+ expired++;
+ }
+
+ if (!mcast_group_has_bundles(grp)) {
+ mcast_snooping_flush_group__(ms, grp);
+ 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, ovs_be32 ip4,
+ uint16_t vlan, void *port)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ bool learned;
+ struct mcast_group *grp;
+
+ /* Avoid duplicate packets. */
+ if (mcast_snooping_mrouter_lookup(ms, vlan, port)
+ || mcast_snooping_fport_lookup(ms, vlan, port)) {
+ return false;
+ }
+
+ learned = false;
+ grp = mcast_snooping_lookup(ms, ip4, vlan);
+ if (!grp) {
+ uint32_t hash = mcast_table_hash(ms, ip4, vlan);
+
+ if (hmap_count(&ms->table) >= ms->max_entries) {
+ group_get_lru(ms, &grp);
+ mcast_snooping_flush_group(ms, grp);
+ }
+
+ grp = xmalloc(sizeof *grp);
+ hmap_insert(&ms->table, &grp->hmap_node, hash);
+ grp->ip4 = ip4;
+ grp->vlan = vlan;
+ list_init(&grp->bundle_lru);
+ learned = true;
+ ms->need_revalidate = true;
+ COVERAGE_INC(mcast_snooping_learned);
+ } else {
+ list_remove(&grp->group_node);
+ }
+ mcast_group_insert_bundle(ms, grp, port, ms->idle_time);
+
+ /* Mark 'grp' as recently used. */
+ list_push_back(&ms->group_lru, &grp->group_node);
+ return learned;
+}
+
+bool
+mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4,
+ uint16_t vlan, void *port)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group *grp;
+
+ grp = mcast_snooping_lookup(ms, ip4, vlan);
+ if (grp && mcast_group_delete_bundle(ms, grp, 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_IDLE_TIME - remaining;
+}
+
+static struct mcast_mrouter_bundle *
+mcast_mrouter_from_lru_node(struct list *list)
+{
+ return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_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;
+ }
+}
+
+static struct mcast_mrouter_bundle *
+mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
+ void *port)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ struct mcast_mrouter_bundle *mrouter;
+
+ LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) {
+ if (mrouter->vlan == vlan && mrouter->port == port) {
+ return mrouter;
+ }
+ }
+ return NULL;
+}
+
+bool
+mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
+ void *port)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_mrouter_bundle *mrouter;
+
+ /* Avoid duplicate packets. */
+ if (mcast_snooping_fport_lookup(ms, vlan, port)) {
+ return false;
+ }
+
+ mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port);
+ if (mrouter) {
+ list_remove(&mrouter->mrouter_node);
+ } else {
+ mrouter = xmalloc(sizeof *mrouter);
+ mrouter->vlan = vlan;
+ mrouter->port = port;
+ COVERAGE_INC(mcast_snooping_learned);
+ ms->need_revalidate = true;
+ }
+
+ mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME;
+ list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node);
+ return ms->need_revalidate;
+}
+
+static void
+mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
+{
+ list_remove(&mrouter->mrouter_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, fport_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)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ struct mcast_fport_bundle *fport;
+
+ LIST_FOR_EACH (fport, fport_node, &ms->fport_list) {
+ if (fport->vlan == vlan && fport->port == port) {
+ return fport;
+ }
+ }
+ return NULL;
+}
+
+static void
+mcast_snooping_add_fport(struct mcast_snooping *ms, uint16_t vlan, void *port)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_fport_bundle *fport;
+
+ fport = xmalloc(sizeof *fport);
+ fport->vlan = vlan;
+ fport->port = port;
+ list_insert(&ms->fport_list, &fport->fport_node);
+}
+
+static void
+mcast_snooping_flush_fport(struct mcast_fport_bundle *fport)
+{
+ list_remove(&fport->fport_node);
+ free(fport);
+}
+
+void
+mcast_snooping_set_port_flood(struct mcast_snooping *ms, uint16_t vlan,
+ void *port, bool flood)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_fport_bundle *fport;
+
+ fport = mcast_snooping_fport_lookup(ms, vlan, port);
+ if (flood && !fport) {
+ mcast_snooping_add_fport(ms, vlan, port);
+ ms->need_revalidate = true;
+ } else if (!flood && fport) {
+ mcast_snooping_flush_fport(fport);
+ ms->need_revalidate = true;
+ }
+}
+
+/* Run and flush. */
+
+static void
+mcast_snooping_mdb_flush__(struct mcast_snooping *ms)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group *grp;
+ struct mcast_mrouter_bundle *mrouter;
+
+ while (group_get_lru(ms, &grp)) {
+ mcast_snooping_flush_group(ms, grp);
+ }
+
+ 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)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ struct mcast_group *grp;
+ struct mcast_mrouter_bundle *mrouter;
+ struct mcast_fport_bundle *fport;
+
+ while (group_get_lru(ms, &grp)) {
+ mcast_snooping_flush_group(ms, grp);
+ }
+
+ 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)
+ OVS_REQ_WRLOCK(ms->rwlock)
+{
+ bool need_revalidate;
+ struct mcast_group *grp;
+ struct mcast_mrouter_bundle *mrouter;
+ int mrouter_expired;
+
+ while (group_get_lru(ms, &grp)) {
+ if (hmap_count(&ms->table) > ms->max_entries) {
+ mcast_snooping_flush_group(ms, grp);
+ } else {
+ if (!mcast_snooping_prune_expired(ms, grp)) {
+ 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)
+ OVS_REQ_RDLOCK(ms->rwlock)
+{
+ if (hmap_count(&ms->table) > ms->max_entries
+ || ms->need_revalidate) {
+ poll_immediate_wake();
+ } else {
+ struct mcast_group *grp;
+ struct mcast_group_bundle *bundle;
+ struct mcast_mrouter_bundle *mrouter;
+ long long int mrouter_msec;
+ long long int msec = 0;
+
+ if (!list_is_empty(&ms->group_lru)) {
+ grp = mcast_group_from_lru_node(ms->group_lru.next);
+ bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.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 000000000..f15e9736e
--- /dev/null
+++ b/lib/mcast-snooping.h
@@ -0,0 +1,194 @@
+/*
+ * 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_group due to inactivity. */
+#define MCAST_ENTRY_DEFAULT_IDLE_TIME 300
+
+/* Time, in seconds, before expiring a mrouter_port due to inactivity. */
+#define MCAST_MROUTER_PORT_IDLE_TIME 180
+
+/* Multicast group entry.
+ * Guarded by owning 'mcast_snooping''s rwlock. */
+struct mcast_group {
+ /* Node in parent struct mcast_snooping hmap. */
+ struct hmap_node hmap_node;
+
+ /* Multicast group IPv4 address. */
+ ovs_be32 ip4;
+
+ /* VLAN tag. */
+ uint16_t vlan;
+
+ /* Node in parent struct mcast_snooping group_lru. */
+ struct list group_node OVS_GUARDED;
+
+ /* Contains struct mcast_group_bundle (ports), least recently used
+ * at the front, most recently used at the back. */
+ struct list bundle_lru OVS_GUARDED;
+};
+
+/* The bundle associated to the multicast group.
+ * Guarded by owning 'mcast_snooping''s rwlock. */
+struct mcast_group_bundle {
+ /* Node in parent struct mcast_group bundle_lru list. */
+ struct list bundle_node OVS_GUARDED;
+
+ /* When this node expires. */
+ time_t expires;
+
+ /* Learned port. */
+ void *port OVS_GUARDED;
+};
+
+/* The bundle connected to a multicast router.
+ * Guarded by owning 'mcast_snooping''s rwlock. */
+struct mcast_mrouter_bundle {
+ /* Node in parent struct mcast_group mrouter_lru list. */
+ struct list mrouter_node OVS_GUARDED;
+
+ /* When this node expires. */
+ time_t expires;
+
+ /* VLAN tag. */
+ uint16_t vlan;
+
+ /* Learned port. */
+ void *port OVS_GUARDED;
+};
+
+/* The bundle to be flooded with multicast traffic.
+ * Guarded by owning 'mcast_snooping''s rwlock */
+struct mcast_fport_bundle {
+ /* Node in parent struct mcast_snooping fport_list. */
+ struct list fport_node;
+
+ /* VLAN tag. */
+ uint16_t vlan;
+
+ /* Learned port. */
+ void *port OVS_GUARDED;
+};
+
+/* Multicast snooping table. */
+struct mcast_snooping {
+ /* Snooping/learning table. */
+ struct hmap table;
+
+ /* Contains struct mcast_group, least recently used at the front,
+ * most recently used at the back. */
+ struct list group_lru OVS_GUARDED;
+
+ /* Contains struct mcast_mrouter_bundle, least recently used at the
+ * front, most recently used at the back. */
+ struct list mrouter_lru OVS_GUARDED;
+
+ /* Contains struct mcast_fport_bundle to be flooded with multicast
+ * packets in no special order. */
+ struct list fport_list OVS_GUARDED;
+
+ /* Secret for randomizing hash table. */
+ uint32_t secret;
+
+ /* Maximum age before deleting an entry. */
+ unsigned int idle_time;
+
+ /* Maximum number of multicast groups learned. */
+ size_t max_entries;
+
+ /* True if flow revalidation is needed. */
+ bool need_revalidate;
+
+ /* True if unregistered multicast packets should be flooded to all
+ * ports, otherwise send them to ports connected to multicast routers. */
+ bool flood_unreg;
+
+ struct ovs_refcount ref_cnt;
+ struct ovs_rwlock rwlock;
+};
+
+/* 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_group_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);
+bool
+mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
+ OVS_REQ_WRLOCK(ms->rwlock);
+void mcast_snooping_set_port_flood(struct mcast_snooping *ms, uint16_t vlan,
+ void *port, bool flood)
+ OVS_REQ_WRLOCK(ms->rwlock);
+
+/* Lookup. */
+struct mcast_group *
+mcast_snooping_lookup(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, ovs_be32 ip4,
+ uint16_t vlan, void *port)
+ OVS_REQ_WRLOCK(ms->rwlock);
+bool mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4,
+ 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_query(ovs_be16 igmp_type);
+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 */