summaryrefslogtreecommitdiffstats
path: root/usr.sbin/pim6sd/mld6_proto.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/pim6sd/mld6_proto.c')
-rw-r--r--usr.sbin/pim6sd/mld6_proto.c632
1 files changed, 632 insertions, 0 deletions
diff --git a/usr.sbin/pim6sd/mld6_proto.c b/usr.sbin/pim6sd/mld6_proto.c
new file mode 100644
index 0000000..4533aac
--- /dev/null
+++ b/usr.sbin/pim6sd/mld6_proto.c
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 1998 by the University of Southern California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation in source and binary forms for lawful
+ * purposes and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both
+ * the copyright notice and this permission notice appear in supporting
+ * documentation, and that any documentation, advertising materials,
+ * and other materials related to such distribution and use acknowledge
+ * that the software was developed by the University of Southern
+ * California and/or Information Sciences Institute.
+ * The name of the University of Southern California may not
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THE UNIVERSITY OF SOUTHERN CALIFORNIA DOES NOT MAKE ANY REPRESENTATIONS
+ * ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. THIS SOFTWARE IS
+ * PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND
+ * NON-INFRINGEMENT.
+ *
+ * IN NO EVENT SHALL USC, OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, WHETHER IN CONTRACT,
+ * TORT, OR OTHER FORM OF ACTION, ARISING OUT OF OR IN CONNECTION WITH,
+ * THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Other copyrights might apply to parts of this software and are so
+ * noted when applicable.
+ *
+ * $FreeBSD$
+ */
+/*
+ * Questions concerning this software should be directed to
+ * Mickael Hoerdt (hoerdt@clarinet.u-strasbg.fr) LSIIT Strasbourg.
+ *
+ */
+/*
+ * This program has been derived from pim6dd.
+ * The pim6dd program is covered by the license in the accompanying file
+ * named "LICENSE.pim6dd".
+ */
+/*
+ * This program has been derived from pimd.
+ * The pimd program is covered by the license in the accompanying file
+ * named "LICENSE.pimd".
+ *
+ */
+/*
+ * Part of this program has been derived from mrouted.
+ * The mrouted program is covered by the license in the accompanying file
+ * named "LICENSE.mrouted".
+ *
+ * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
+ * Leland Stanford Junior University.
+ *
+ */
+/*
+ * Part of this program has been derived from mrouted.
+ * The mrouted program is covered by the license in the accompanying file
+ * named "LICENSE.mrouted".
+ *
+ * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
+ * Leland Stanford Junior University.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet6/ip6_mroute.h>
+#include <netinet/icmp6.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include "mld6.h"
+#include "vif.h"
+#include "debug.h"
+#include "inet6.h"
+#include "route.h"
+#include "callout.h"
+#include "timer.h"
+
+extern struct in6_addr in6addr_any;
+
+typedef struct
+{
+ mifi_t mifi;
+ struct listaddr *g;
+ int q_time;
+} cbk_t;
+
+
+/*
+ * Forward declarations.
+ */
+static void DelVif __P((void *arg));
+static int SetTimer __P((int mifi, struct listaddr * g));
+static int DeleteTimer __P((int id));
+static void SendQuery __P((void *arg));
+static int SetQueryTimer
+__P((struct listaddr * g, int mifi, int to_expire,
+ int q_time));
+
+/*
+ * Send group membership queries on that interface if I am querier.
+ */
+void
+query_groups(v)
+ register struct uvif *v;
+{
+ register struct listaddr *g;
+
+ v->uv_gq_timer = MLD6_QUERY_INTERVAL;
+ if (v->uv_flags & VIFF_QUERIER && (v->uv_flags & VIFF_NOLISTENER) == 0) {
+ send_mld6(MLD6_LISTENER_QUERY, 0, &v->uv_linklocal->pa_addr,
+ NULL, (struct in6_addr *)&in6addr_any, v->uv_ifindex,
+ MLD6_QUERY_RESPONSE_INTERVAL, 0, 1);
+ v->uv_out_mld_query++;
+ }
+
+ /*
+ * Decrement the old-hosts-present timer for each active group on that
+ * vif.
+ */
+ for (g = v->uv_groups; g != NULL; g = g->al_next)
+ if (g->al_old > timer_interval)
+ g->al_old -= timer_interval;
+ else
+ g->al_old = 0;
+}
+
+
+/*
+ * Process an incoming host membership query
+ */
+void
+accept_listener_query(src, dst, group, tmo)
+ struct sockaddr_in6 *src;
+ struct in6_addr *dst,
+ *group;
+ int tmo;
+{
+ register int mifi;
+ register struct uvif *v;
+ struct sockaddr_in6 group_sa = {sizeof(group_sa), AF_INET6};
+
+ /* Ignore my own membership query */
+ if (local_address(src) != NO_VIF)
+ return;
+
+ if ((mifi = find_vif_direct(src)) == NO_VIF)
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_INFO, 0,
+ "accept_listener_query: can't find a mif");
+ return;
+ }
+
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "accepting multicast listener query: "
+ "src %s, dst %s, grp %s",
+ inet6_fmt(&src->sin6_addr), inet6_fmt(dst),
+ inet6_fmt(group));
+
+ v = &uvifs[mifi];
+ v->uv_in_mld_query++;
+
+ if (v->uv_querier == NULL || inet6_equal(&v->uv_querier->al_addr, src))
+ {
+ /*
+ * This might be: - A query from a new querier, with a lower source
+ * address than the current querier (who might be me) - A query from
+ * a new router that just started up and doesn't know who the querier
+ * is. - A query from the current querier
+ */
+
+ if (inet6_lessthan(src, (v->uv_querier ? &v->uv_querier->al_addr
+ : &v->uv_linklocal->pa_addr)))
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0, "new querier %s (was %s) "
+ "on mif %d",
+ inet6_fmt(&src->sin6_addr),
+ v->uv_querier ?
+ inet6_fmt(&v->uv_querier->al_addr.sin6_addr) :
+ "me", mifi);
+ if (!v->uv_querier)
+ {
+ v->uv_querier = (struct listaddr *)malloc(sizeof(struct listaddr));
+ v->uv_querier->al_next = (struct listaddr *) NULL;
+ v->uv_querier->al_timer = 0;
+ v->uv_querier->al_genid = 0;
+ v->uv_querier->al_pv = 0;
+ v->uv_querier->al_mv = 0;
+ v->uv_querier->al_old = 0;
+ v->uv_querier->al_index = 0;
+ v->uv_querier->al_timerid = 0;
+ v->uv_querier->al_query = 0;
+ v->uv_querier->al_flags = 0;
+
+ v->uv_flags &= ~VIFF_QUERIER;
+ }
+ v->uv_querier->al_addr = *src;
+ time(&v->uv_querier->al_ctime);
+ }
+ }
+
+ /*
+ * Reset the timer since we've received a query.
+ */
+ if (v->uv_querier && inet6_equal(src, &v->uv_querier->al_addr))
+ v->uv_querier->al_timer = 0;
+
+ /*
+ * If this is a Group-Specific query which we did not source, we must set
+ * our membership timer to [Last Member Query Count] * the [Max Response
+ * Time] in the packet.
+ */
+ if (!IN6_IS_ADDR_UNSPECIFIED(group) &&
+ inet6_equal(src, &v->uv_linklocal->pa_addr))
+ {
+ register struct listaddr *g;
+
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "%s for %s from %s on mif %d, timer %d",
+ "Group-specific membership query",
+ inet6_fmt(group),
+ inet6_fmt(&src->sin6_addr), mifi, tmo);
+
+ group_sa.sin6_addr = *group;
+ group_sa.sin6_scope_id = inet6_uvif2scopeid(&group_sa, v);
+ for (g = v->uv_groups; g != NULL; g = g->al_next)
+ {
+ if (inet6_equal(&group_sa, &g->al_addr)
+ && g->al_query == 0)
+ {
+ /* setup a timeout to remove the group membership */
+ if (g->al_timerid)
+ g->al_timerid = DeleteTimer(g->al_timerid);
+ g->al_timer = MLD6_LAST_LISTENER_QUERY_COUNT *
+ tmo / MLD6_TIMER_SCALE;
+ /*
+ * use al_query to record our presence in last-member state
+ */
+ g->al_query = -1;
+ g->al_timerid = SetTimer(mifi, g);
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "timer for grp %s on mif %d "
+ "set to %d",
+ inet6_fmt(group),
+ mifi, g->al_timer);
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+ * Process an incoming group membership report.
+ */
+void
+accept_listener_report(src, dst, group)
+ struct sockaddr_in6 *src;
+ struct in6_addr *dst,
+ *group;
+{
+ register mifi_t mifi;
+ register struct uvif *v;
+ register struct listaddr *g;
+ struct sockaddr_in6 group_sa = {sizeof(group_sa), AF_INET6};
+
+ if (IN6_IS_ADDR_MC_LINKLOCAL(group)) {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "accept_listener_report: group(%s) has the "
+ "link-local scope. discard", inet6_fmt(group));
+ return;
+ }
+
+ if ((mifi = find_vif_direct_local(src)) == NO_VIF)
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_INFO, 0,
+ "accept_listener_report: can't find a mif");
+ return;
+ }
+
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "accepting multicast listener report: "
+ "src %s,dst %s, grp %s",
+ inet6_fmt(&src->sin6_addr),inet6_fmt(dst),
+ inet6_fmt(group));
+
+ v = &uvifs[mifi];
+ v->uv_in_mld_report++;
+
+ /*
+ * Look for the group in our group list; if found, reset its timer.
+ */
+
+ group_sa.sin6_addr = *group;
+ group_sa.sin6_scope_id = inet6_uvif2scopeid(&group_sa, v);
+
+ for (g = v->uv_groups; g != NULL; g = g->al_next)
+ {
+ if (inet6_equal(&group_sa, &g->al_addr))
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG,0,
+ "The group already exist");
+
+ g->al_reporter = *src;
+
+ /* delete old timers, set a timer for expiration */
+
+ g->al_timer = MLD6_LISTENER_INTERVAL;
+ if (g->al_query)
+ g->al_query = DeleteTimer(g->al_query);
+ if (g->al_timerid)
+ g->al_timerid = DeleteTimer(g->al_timerid);
+ g->al_timerid = SetTimer(mifi, g);
+ add_leaf(mifi, NULL, &group_sa);
+ break;
+ }
+ }
+
+ /*
+ * If not found, add it to the list and update kernel cache.
+ */
+ if (g == NULL)
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG,0,
+ "The group don't exist , trying to add it");
+
+ g = (struct listaddr *) malloc(sizeof(struct listaddr));
+ if (g == NULL)
+ log(LOG_ERR, 0, "ran out of memory"); /* fatal */
+
+ g->al_addr = group_sa;
+ g->al_old = 0;
+
+ /** set a timer for expiration **/
+ g->al_query = 0;
+ g->al_timer = MLD6_LISTENER_INTERVAL;
+ g->al_reporter = *src;
+ g->al_timerid = SetTimer(mifi, g);
+ g->al_next = v->uv_groups;
+ v->uv_groups = g;
+ time(&g->al_ctime);
+
+ add_leaf(mifi, NULL, &group_sa);
+ }
+}
+
+
+/* TODO: send PIM prune message if the last member? */
+void
+accept_listener_done(src, dst, group)
+ struct sockaddr_in6 *src;
+ struct in6_addr *dst,
+ *group;
+{
+ register mifi_t mifi;
+ register struct uvif *v;
+ register struct listaddr *g;
+ struct sockaddr_in6 group_sa = {sizeof(group_sa), AF_INET6};
+
+ /* Don't create routing entries for the LAN scoped addresses */
+
+ if (IN6_IS_ADDR_MC_NODELOCAL(group)) /* sanity? */
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "accept_listener_done: address multicast node local(%s),"
+ " ignore it...", inet6_fmt(group));
+ return;
+ }
+
+ if (IN6_IS_ADDR_MC_LINKLOCAL(group))
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "accept_listener_done: address multicast link local(%s), "
+ "ignore it ...", inet6_fmt(group));
+ return;
+ }
+
+ if ((mifi = find_vif_direct_local(src)) == NO_VIF)
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_INFO, 0,
+ "accept_listener_done: can't find a mif");
+ return;
+ }
+
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_INFO, 0,
+ "accepting listener done message: src %s, dst% s, grp %s",
+ inet6_fmt(&src->sin6_addr),
+ inet6_fmt(dst), inet6_fmt(group));
+
+ v = &uvifs[mifi];
+ v->uv_in_mld_done++;
+
+ if (!(v->uv_flags & (VIFF_QUERIER | VIFF_DR)))
+ return;
+
+ /*
+ * Look for the group in our group list in order to set up a
+ * short-timeout query.
+ */
+ group_sa.sin6_addr = *group;
+ group_sa.sin6_scope_id = inet6_uvif2scopeid(&group_sa, v);
+ for (g = v->uv_groups; g != NULL; g = g->al_next)
+ {
+ if (inet6_equal(&group_sa, &g->al_addr))
+ {
+ IF_DEBUG(DEBUG_MLD)
+ log(LOG_DEBUG, 0,
+ "[accept_done_message] %d %d \n",
+ g->al_old, g->al_query);
+
+ /*
+ * Ignore the done message if there are old hosts present
+ */
+ if (g->al_old)
+ return;
+
+ /*
+ * still waiting for a reply to a query, ignore the done
+ */
+ if (g->al_query)
+ return;
+
+ /** delete old timer set a timer for expiration **/
+ if (g->al_timerid)
+ g->al_timerid = DeleteTimer(g->al_timerid);
+
+ /** send a group specific querry **/
+ g->al_timer = (MLD6_LAST_LISTENER_QUERY_INTERVAL / MLD6_TIMER_SCALE) *
+ (MLD6_LAST_LISTENER_QUERY_COUNT + 1);
+ if (v->uv_flags & VIFF_QUERIER &&
+ (v->uv_flags & VIFF_NOLISTENER) == 0) {
+ send_mld6(MLD6_LISTENER_QUERY, 0,
+ &v->uv_linklocal->pa_addr, NULL,
+ &g->al_addr.sin6_addr,
+ v->uv_ifindex,
+ MLD6_LAST_LISTENER_QUERY_INTERVAL, 0, 1);
+ v->uv_out_mld_query++;
+ }
+ g->al_query = SetQueryTimer(g, mifi,
+ MLD6_LAST_LISTENER_QUERY_INTERVAL / MLD6_TIMER_SCALE,
+ MLD6_LAST_LISTENER_QUERY_INTERVAL);
+ g->al_timerid = SetTimer(mifi, g);
+ break;
+ }
+ }
+}
+
+
+/*
+ * Time out record of a group membership on a vif
+ */
+static void
+DelVif(arg)
+ void *arg;
+{
+ cbk_t *cbk = (cbk_t *) arg;
+ mifi_t mifi = cbk->mifi;
+ struct uvif *v = &uvifs[mifi];
+ struct listaddr *a,
+ **anp,
+ *g = cbk->g;
+
+ /*
+ * Group has expired delete all kernel cache entries with this group
+ */
+ if (g->al_query)
+ DeleteTimer(g->al_query);
+
+ delete_leaf(mifi, NULL, &g->al_addr);
+
+ /* increment statistics */
+ v->uv_listener_timo++;
+
+ anp = &(v->uv_groups);
+ while ((a = *anp) != NULL)
+ {
+ if (a == g)
+ {
+ *anp = a->al_next;
+ free((char *) a);
+ }
+ else
+ {
+ anp = &a->al_next;
+ }
+ }
+
+ free(cbk);
+}
+
+
+/*
+ * Set a timer to delete the record of a group membership on a vif.
+ */
+static int
+SetTimer(mifi, g)
+ mifi_t mifi;
+ struct listaddr *g;
+{
+ cbk_t *cbk;
+
+ cbk = (cbk_t *) malloc(sizeof(cbk_t));
+ cbk->mifi = mifi;
+ cbk->g = g;
+ return timer_setTimer(g->al_timer, DelVif, cbk);
+}
+
+
+/*
+ * Delete a timer that was set above.
+ */
+static int
+DeleteTimer(id)
+ int id;
+{
+ timer_clearTimer(id);
+ return 0;
+}
+
+
+/*
+ * Send a group-specific query.
+ */
+static void
+SendQuery(arg)
+ void *arg;
+{
+ cbk_t *cbk = (cbk_t *) arg;
+ register struct uvif *v = &uvifs[cbk->mifi];
+
+ if (v->uv_flags & VIFF_QUERIER && (v->uv_flags & VIFF_NOLISTENER) == 0) {
+ send_mld6(MLD6_LISTENER_QUERY, 0, &v->uv_linklocal->pa_addr,
+ NULL, &cbk->g->al_addr.sin6_addr, v->uv_ifindex,
+ cbk->q_time, 0, 1);
+ v->uv_out_mld_query++;
+ }
+ cbk->g->al_query = 0;
+ free(cbk);
+}
+
+
+/*
+ * Set a timer to send a group-specific query.
+ */
+static int
+SetQueryTimer(g, mifi, to_expire, q_time)
+ struct listaddr *g;
+ mifi_t mifi;
+ int to_expire;
+ int q_time;
+{
+ cbk_t *cbk;
+
+ cbk = (cbk_t *) malloc(sizeof(cbk_t));
+ cbk->g = g;
+ cbk->q_time = q_time;
+ cbk->mifi = mifi;
+ return timer_setTimer(to_expire, SendQuery, cbk);
+}
+
+/*
+ * Checks for MLD listener: returns TRUE if there is a receiver for the group
+ * on the given uvif, or returns FALSE otherwise.
+ */
+int
+check_multicast_listener(v, group)
+ struct uvif *v;
+ struct sockaddr_in6 *group;
+{
+ register struct listaddr *g;
+
+ /*
+ * Look for the group in our listener list;
+ */
+ for (g = v->uv_groups; g != NULL; g = g->al_next)
+ {
+ if (inet6_equal(group, &g->al_addr))
+ return TRUE;
+ }
+ return FALSE;
+}
OpenPOWER on IntegriCloud