freebsd-src/usr.sbin/pim6sd/mld6_proto.c

620 lines
16 KiB
C
Raw Normal View History

/*
* 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.
*/
/*
* 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.
*
* $FreeBSD$
*/
#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"
#include "mld6_proto.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 listener 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;
}
v = &uvifs[mifi];
v->uv_in_mld_query++;
IF_DEBUG(DEBUG_MLD)
log(LOG_DEBUG, 0,
"accepting multicast listener query on %s: "
"src %s, dst %s, grp %s",
v->uv_name,
inet6_fmt(&src->sin6_addr), inet6_fmt(dst),
inet6_fmt(group));
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.
*/
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) { /* this should be impossible... */
v->uv_querier = (struct listaddr *)malloc(sizeof(struct listaddr));
memset(v->uv_querier, 0, sizeof(struct listaddr));
}
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 = MLD6_OTHER_QUERIER_PRESENT_INTERVAL;
/*
* 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;
}