diff options
Diffstat (limited to 'usr.sbin/pim6sd/mld6_proto.c')
-rw-r--r-- | usr.sbin/pim6sd/mld6_proto.c | 632 |
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; +} |