/*
* Copyright (c) 1998-2001
* University of Southern California/Information Sciences Institute.
* 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.
*/
/*
* $Id: igmp_proto.c,v 1.1 2017/06/12 07:59:37 misho Exp $
*/
/*
* 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 "defs.h"
typedef struct {
vifi_t vifi;
struct listaddr *g;
uint32_t source; /* Source for SSM */
int q_time; /* IGMP Code */
int q_len; /* Data length */
} cbk_t;
/*
* Forward declarations.
*/
static void DelVif (void *arg);
static int SetTimer (vifi_t vifi, struct listaddr *g, uint32_t source);
static int SetVersionTimer (vifi_t vifi, struct listaddr *g);
static int DeleteTimer (int id);
static void send_query (struct uvif *v, uint32_t group, int interval);
static void SendQuery (void *arg);
static int SetQueryTimer (struct listaddr *g, vifi_t vifi, int to_expire, int q_time, int q_len);
static uint32_t igmp_group_membership_timeout(void);
/* The querier timeout depends on the configured query interval */
uint32_t igmp_query_interval = IGMP_QUERY_INTERVAL;
uint32_t igmp_querier_timeout = IGMP_OTHER_QUERIER_PRESENT_INTERVAL;
/*
* Send group membership queries on that interface if I am querier.
*/
void query_groups(struct uvif *v)
{
int datalen = 4;
int code = IGMP_MAX_HOST_REPORT_DELAY * IGMP_TIMER_SCALE;
struct listaddr *g;
v->uv_gq_timer = igmp_query_interval;
if (v->uv_flags & VIFF_QUERIER) {
/* IGMP version to use depends on the compatibility mode of the interface */
if (v->uv_flags & VIFF_IGMPV2) {
/* RFC 3376: When in IGMPv2 mode, routers MUST send Periodic
Queries truncated at the Group Address field (i.e., 8 bytes long) */
datalen = 0;
} else if (v->uv_flags & VIFF_IGMPV1) {
/* RFC 3376: When in IGMPv1 mode, routers MUST send Periodic Queries with a Max Response Time of 0 */
datalen = 0;
code = 0;
}
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): Sending IGMP v%s query on %s",
__func__, datalen == 4 ? "3" : "2", v->uv_name);
send_igmp(igmp_send_buf, v->uv_lcl_addr, allhosts_group,
IGMP_MEMBERSHIP_QUERY,
code, 0, datalen);
}
/*
* 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_membership_query(uint32_t src, uint32_t dst __attribute__((unused)), uint32_t group, int tmo, int igmp_version)
{
vifi_t vifi;
struct uvif *v;
/* Ignore my own membership query */
if (local_address(src) != NO_VIF)
return;
/* Only v3 is allowed for SSM
* TODO: Rate-limit messages?
*/
if (igmp_version != 3 && IN_PIM_SSM_RANGE(group)) {
logit(LOG_WARNING, 0, "SSM addresses are not allowed in v%d query.", igmp_version);
return;
}
/* TODO: modify for DVMRP?? */
if ((vifi = find_vif_direct(src)) == NO_VIF) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_INFO, 0, "Ignoring group membership query from non-adjacent host %s",
inet_fmt(src, s1, sizeof(s1)));
return;
}
v = &uvifs[vifi];
/* Do not accept messages of higher version than current
* compatibility mode as specified in RFC 3376 - 7.3.1
*/
if (v->uv_querier) {
if ((igmp_version == 3 && (v->uv_flags & VIFF_IGMPV2)) ||
(igmp_version == 2 && (v->uv_flags & VIFF_IGMPV1))) {
int i;
/*
* Exponentially back-off warning rate
*/
i = ++v->uv_igmpv1_warn;
while (i && !(i & 1)) {
i >>= 1;
if (i == 1) {
logit(LOG_WARNING, 0, "Received IGMP v%d query from %s on vif %d,"
" but I am configured for IGMP v%d network compatibility mode",
igmp_version,
inet_fmt(src, s1, sizeof(s1)),
vifi,
v->uv_flags & VIFF_IGMPV1 ? 1 : 2);
}
return;
}
}
}
if (!v->uv_querier || 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 (ntohl(src) < (v->uv_querier
? ntohl(v->uv_querier->al_addr)
: ntohl(v->uv_lcl_addr))) {
IF_DEBUG(DEBUG_IGMP) {
logit(LOG_DEBUG, 0, "new querier %s (was %s) on vif %d",
inet_fmt(src, s1, sizeof(s1)),
v->uv_querier
? inet_fmt(v->uv_querier->al_addr, s2, sizeof(s2))
: "me", vifi);
}
if (!v->uv_querier) {
v->uv_querier = (struct listaddr *) calloc(1, sizeof(struct listaddr));
if (!v->uv_querier) {
logit(LOG_ERR, 0, "Failed calloc() in accept_membership_query()");
return;
}
v->uv_querier->al_next = (struct listaddr *)NULL;
v->uv_querier->al_timer = 0;
v->uv_querier->al_genid = 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 && 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 (!(v->uv_flags & VIFF_IGMPV1) && group != 0 && src != v->uv_lcl_addr) {
struct listaddr *g;
IF_DEBUG(DEBUG_IGMP) {
logit(LOG_DEBUG, 0, "Group-specific membership query for %s from %s on vif %d, timer %d",
inet_fmt(group, s2, sizeof(s2)), inet_fmt(src, s1, sizeof(s1)), vifi, tmo);
}
for (g = v->uv_groups; g != NULL; g = g->al_next) {
if (group == 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 = IGMP_LAST_MEMBER_QUERY_COUNT * tmo / IGMP_TIMER_SCALE;
/* use al_query to record our presence in last-member state */
g->al_query = -1;
g->al_timerid = SetTimer(vifi, g, 0);
IF_DEBUG(DEBUG_IGMP) {
logit(LOG_DEBUG, 0, "Timer for grp %s on vif %d set to %ld",
inet_fmt(group, s2, sizeof(s2)), vifi, g->al_timer);
}
break;
}
}
}
}
/*
* Process an incoming group membership report.
*/
void accept_group_report(uint32_t igmp_src, uint32_t ssm_src, uint32_t group, int igmp_report_type)
{
vifi_t vifi;
struct uvif *v;
struct listaddr *g;
struct listaddr *s = NULL;
if ((vifi = find_vif_direct_local(igmp_src)) == NO_VIF) {
IF_DEBUG(DEBUG_IGMP) {
logit(LOG_INFO, 0, "Ignoring group membership report from non-adjacent host %s",
inet_fmt(igmp_src, s1, sizeof(s1)));
}
return;
}
inet_fmt(igmp_src, s1, sizeof(s1));
inet_fmt(ssm_src, s2, sizeof(s2));
inet_fmt(group, s3, sizeof(s3));
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): igmp_src %s ssm_src %s group %s report_type %i",
__func__, s1, s2, s3, igmp_report_type);
v = &uvifs[vifi];
/*
* Look for the group in our group list; if found, reset its timer.
*/
for (g = v->uv_groups; g != NULL; g = g->al_next) {
if (group == g->al_addr) {
if (igmp_report_type == IGMP_V1_MEMBERSHIP_REPORT) {
g->al_old = DVMRP_OLD_AGE_THRESHOLD;
if (!IN_PIM_SSM_RANGE(group) && g->al_pv>1) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v1 for group %s", s3);
g->al_pv = 1;
}
} else if (!IN_PIM_SSM_RANGE(group) && igmp_report_type == IGMP_V2_MEMBERSHIP_REPORT) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG,0, "%s(): al_pv=%d", __func__, g->al_pv);
if (g->al_pv > 2) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v2 for group %s", s3);
g->al_pv = 2;
}
}
g->al_reporter = igmp_src;
/** delete old timers, set a timer for expiration **/
g->al_timer = igmp_group_membership_timeout();
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(vifi, g, ssm_src);
/* Reset timer for switching version back every time an older version report is received */
if (!IN_PIM_SSM_RANGE(group) && g->al_pv<3 && (igmp_report_type == IGMP_V1_MEMBERSHIP_REPORT ||
igmp_report_type == IGMP_V2_MEMBERSHIP_REPORT)) {
if (g->al_versiontimer)
g->al_versiontimer = DeleteTimer(g->al_versiontimer);
g->al_versiontimer = SetVersionTimer(vifi, g);
}
/* Find source */
if (IN_PIM_SSM_RANGE(group)) {
for (s = g->al_sources; s; s = s->al_next) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): Seek source %s, curr=%s", __func__,
inet_fmt(ssm_src, s1, sizeof(s1)),
inet_fmt(s->al_addr, s2, sizeof(s2)));
if (ssm_src == s->al_addr) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): Source found", __func__);
break;
}
}
if (!s) {
/* Add new source */
s = (struct listaddr *)calloc(1, sizeof(struct listaddr));
if (!s) {
logit(LOG_ERR, errno, "%s(): Ran out of memory", __func__);
return;
}
s->al_addr = ssm_src;
s->al_next = g->al_sources;
g->al_sources = s;
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): Source %s added to g:%p", __func__, s2, g);
}
}
/* TODO: might need to add a check if I am the forwarder??? */
/* if (v->uv_flags & VIFF_DR) */
if (IN_PIM_SSM_RANGE(group)) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_INFO, 0, "Add leaf (%s,%s)", s1, s3);
add_leaf(vifi, ssm_src, group);
} else {
add_leaf(vifi, INADDR_ANY_N, group);
}
break;
}
}
/*
* If not found, add it to the list and update kernel cache.
*/
if (!g) {
g = (struct listaddr *)calloc(1, sizeof(struct listaddr));
if (!g) {
logit(LOG_ERR, errno, "%s(): Ran out of memory", __func__);
return;
}
g->al_addr = group;
if (!IN_PIM_SSM_RANGE(group) && igmp_report_type == IGMP_V1_MEMBERSHIP_REPORT) {
g->al_old = DVMRP_OLD_AGE_THRESHOLD;
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v1 for group %s", s3);
g->al_pv = 1;
} else if (!IN_PIM_SSM_RANGE(group) && igmp_report_type == IGMP_V2_MEMBERSHIP_REPORT) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v2 for group %s", s3);
g->al_pv = 2;
} else {
g->al_pv = 3;
}
/* Add new source */
if (IN_PIM_SSM_RANGE(group)) {
s = (struct listaddr *)calloc(1, sizeof(struct listaddr));
if (!s) {
logit(LOG_ERR, errno, "%s(): Ran out of memory", __func__);
return;
}
s->al_addr = ssm_src;
s->al_next = g->al_sources;
g->al_sources = s;
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): Source %s added to new g:%p", __func__, s2, g);
}
/** set a timer for expiration **/
g->al_query = 0;
g->al_timer = IGMP_GROUP_MEMBERSHIP_INTERVAL;
g->al_reporter = igmp_src;
g->al_timerid = SetTimer(vifi, g, ssm_src);
/* Set timer for swithing version back if an older version report is received */
if (!IN_PIM_SSM_RANGE(group) && g->al_pv<3) {
g->al_versiontimer = SetVersionTimer(vifi, g);
}
g->al_next = v->uv_groups;
v->uv_groups = g;
time(&g->al_ctime);
/* TODO: might need to add a check if I am the forwarder??? */
/* if (v->uv_flags & VIFF_DR) */
if (IN_PIM_SSM_RANGE(group)) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_INFO, 0, "SSM group order from %s (%s,%s)", s1, s2, s3);
add_leaf(vifi, ssm_src, group);
} else {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_INFO, 0, "SM group order from %s (*,%s)", s1, s3);
add_leaf(vifi, INADDR_ANY_N, group);
}
}
}
/* TODO: send PIM prune message if the last member? */
void accept_leave_message(uint32_t src, uint32_t dst __attribute__((unused)), uint32_t group)
{
vifi_t vifi;
struct uvif *v;
struct listaddr *g;
int datalen = 4;
int code = IGMP_LAST_MEMBER_QUERY_INTERVAL * IGMP_TIMER_SCALE;
/* TODO: modify for DVMRP ??? */
if ((vifi = find_vif_direct_local(src)) == NO_VIF) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_INFO, 0, "ignoring group leave report from non-adjacent host %s",
inet_fmt(src, s1, sizeof(s1)));
return;
}
inet_fmt(src, s1, sizeof(s1));
inet_fmt(dst, s2, sizeof(s2));
inet_fmt(group, s3, sizeof(s3));
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): src %s dst %s group %s", __func__, s1, s2, s3);
v = &uvifs[vifi];
#if 0
/* XXX: a PIM-SM last-hop router needs to know when a local member
* has left.
*/
if (!(v->uv_flags & (VIFF_QUERIER | VIFF_DR))
|| (v->uv_flags & VIFF_IGMPV1))
return;
#endif
/*
* Look for the group in our group list in order to set up a short-timeout
* query.
*/
for (g = v->uv_groups; g; g = g->al_next) {
if (group == g->al_addr) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "accept_leave_message(): old=%d query=%d", g->al_old, g->al_query);
/* Ignore the leave message if there are old hosts present */
if (g->al_old)
return;
/* still waiting for a reply to a query, ignore the leave */
if (g->al_query)
return;
/* TODO: Remove the source. Ignore the leave if there
are still sources left
if (IN_PIM_SSM_RANGE(g->al_addr)) {
for (s = g->al_sources; s != NULL; s = s->al_next) {
if (dst == s->al_addr) {
}
}
}
*/
/** delete old timer set a timer for expiration **/
if (g->al_timerid)
g->al_timerid = DeleteTimer(g->al_timerid);
#if IGMP_LAST_MEMBER_QUERY_COUNT != 2
/*
This code needs to be updated to keep a counter of the number
of queries remaining.
*/
#endif
if (v->uv_flags & VIFF_QUERIER) {
/* Use lowest IGMP version */
if (v->uv_flags & VIFF_IGMPV2 || g->al_pv <= 2) {
datalen = 0;
} else if (v->uv_flags & VIFF_IGMPV1 || g->al_pv == 1) {
datalen = 0;
code = 0;
}
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): Sending IGMP v%s query (al_pv=%d)",
__func__, datalen == 4 ? "3" : "2", g->al_pv);
send_igmp(igmp_send_buf, v->uv_lcl_addr, g->al_addr,
IGMP_MEMBERSHIP_QUERY,
code,
g->al_addr, datalen);
}
g->al_timer = IGMP_LAST_MEMBER_QUERY_INTERVAL * (IGMP_LAST_MEMBER_QUERY_COUNT + 1);
g->al_query = SetQueryTimer(g, vifi,
IGMP_LAST_MEMBER_QUERY_INTERVAL,
code, datalen);
g->al_timerid = SetTimer(vifi, g, dst);
break;
}
}
}
/*
* Time out old version compatibility mode
*/
static void SwitchVersion(void *arg)
{
cbk_t *cbk = (cbk_t *)arg;
if (cbk->g->al_pv < 3)
cbk->g->al_pv += 1;
logit(LOG_INFO, 0, "Switch IGMP compatibility mode back to v%d for group %s",
cbk->g->al_pv, inet_fmt(cbk->g->al_addr, s1, sizeof(s1)));
}
/*
* Loop through and process all sources in a v3 record.
*
* Parameters:
* igmp_report_type Report type of IGMP message
* igmp_src Src address of IGMP message
* group Multicast group
* sources Pointer to the beginning of sources list in the IGMP message
* report_pastend Pointer to the end of IGMP message
*
* Returns:
* 1 if succeeded, 0 if failed
*/
int accept_sources(int igmp_report_type, uint32_t igmp_src, uint32_t group, uint8_t *sources,
uint8_t *report_pastend, int rec_num_sources) {
int j;
uint8_t *src;
char src_str[200];
for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
if ((src + 4) > report_pastend) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "src +4 > report_pastend");
return 0;
}
inet_ntop(AF_INET, src, src_str , sizeof(src_str));
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Add source (%s,%s)", src_str, inet_fmt(group, s1, sizeof(s1)));
accept_group_report(igmp_src, ((struct in_addr*)src)->s_addr, group, igmp_report_type);
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Accepted, switch SPT (%s,%s)", src_str, inet_fmt(group, s1, sizeof(s1)));
switch_shortest_path(((struct in_addr*)src)->s_addr, group);
}
return 1;
}
/*
* Handle IGMP v3 membership reports (join/leave)
*/
void accept_membership_report(uint32_t src, uint32_t dst, struct igmpv3_report *report, ssize_t reportlen)
{
struct igmpv3_grec *record;
int num_groups, i;
uint8_t *report_pastend = (uint8_t *)report + reportlen;
num_groups = ntohs(report->ngrec);
if (num_groups < 0) {
logit(LOG_INFO, 0, "Invalid Membership Report from %s: num_groups = %d",
inet_fmt(src, s1, sizeof(s1)), num_groups);
return;
}
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "%s(): IGMP v3 report, %d bytes, from %s to %s with %d group records.",
__func__, reportlen, inet_fmt(src, s1, sizeof(s1)), inet_fmt(dst, s2, sizeof(s2)), num_groups);
record = &report->grec[0];
for (i = 0; i < num_groups; i++) {
struct in_addr rec_group;
uint8_t *sources;
int rec_type;
int rec_auxdatalen;
int rec_num_sources;
int j;
char src_str[200];
int record_size = 0;
rec_num_sources = ntohs(record->grec_nsrcs);
rec_auxdatalen = record->grec_auxwords;
record_size = sizeof(struct igmpv3_grec) + sizeof(uint32_t) * rec_num_sources + rec_auxdatalen;
if ((uint8_t *)record + record_size > report_pastend) {
logit(LOG_INFO, 0, "Invalid group report %p > %p",
(uint8_t *)record + record_size, report_pastend);
return;
}
rec_type = record->grec_type;
rec_group.s_addr = (in_addr_t)record->grec_mca;
sources = (u_int8_t *)record->grec_src;
switch (rec_type) {
case IGMP_MODE_IS_EXCLUDE:
/* RFC 4604: A router SHOULD ignore a group record of
type MODE_IS_EXCLUDE if it refers to an SSM destination address */
if (!IN_PIM_SSM_RANGE(rec_group.s_addr)) {
if (rec_num_sources==0) {
/* RFC 5790: EXCLUDE (*,G) join can be interpreted by the router
as a request to include all sources. */
accept_group_report(src, 0 /*dst*/, rec_group.s_addr, report->type);
} else {
/* RFC 5790: LW-IGMPv3 does not use EXCLUDE filter-mode with a non-null source address list.*/
logit(LOG_INFO, 0, "Record type MODE_IS_EXCLUDE with non-null source list is currently unsupported.");
}
}
break;
case IGMP_CHANGE_TO_EXCLUDE_MODE:
/* RFC 4604: A router SHOULD ignore a group record of
type CHANGE_TO_EXCLUDE_MODE if it refers to an SSM destination address */
if (!IN_PIM_SSM_RANGE(rec_group.s_addr)) {
if (rec_num_sources==0) {
/* RFC 5790: EXCLUDE (*,G) join can be interpreted by the router
as a request to include all sources. */
accept_group_report(src, 0 /*dst*/, rec_group.s_addr, report->type);
} else {
/* RFC 5790: LW-IGMPv3 does not use EXCLUDE filter-mode with a non-null source address list.*/
logit(LOG_DEBUG, 0, "Record type MODE_TO_EXCLUDE with non-null source list is currently unsupported.");
}
}
break;
case IGMP_MODE_IS_INCLUDE:
if (!accept_sources(report->type, src, rec_group.s_addr, sources, report_pastend, rec_num_sources)) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Accept sources failed.");
return;
}
break;
case IGMP_CHANGE_TO_INCLUDE_MODE:
if (!accept_sources(report->type, src, rec_group.s_addr, sources, report_pastend, rec_num_sources)) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Accept sources failed.");
return;
}
break;
case IGMP_ALLOW_NEW_SOURCES:
if (!accept_sources(report->type, src, rec_group.s_addr, sources, report_pastend, rec_num_sources)) {
logit(LOG_DEBUG, 0, "Accept sources failed.");
return;
}
break;
case IGMP_BLOCK_OLD_SOURCES:
for (j = 0; j < rec_num_sources; j++) {
uint32_t *gsrc = (uint32_t *)&record->grec_src[j];
if ((uint8_t *)gsrc > report_pastend) {
logit(LOG_INFO, 0, "Invalid group record");
return;
}
inet_ntop(AF_INET, gsrc, src_str , sizeof(src_str));
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Remove source[%d] (%s,%s)", j, src_str, inet_ntoa(rec_group));
accept_leave_message(src, *gsrc, rec_group.s_addr);
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Accepted");
}
break;
default:
// RFC3376: Unrecognized Record Type values MUST be silently ignored.
break;
}
record = (struct igmpv3_grec *)((uint8_t *)record + record_size);
}
}
/*
* Calculate group membership timeout
*/
static uint32_t igmp_group_membership_timeout(void)
{
return IGMP_ROBUSTNESS_VARIABLE * igmp_query_interval + IGMP_QUERY_RESPONSE_INTERVAL;
}
/*
* Time out record of a group membership on a vif
*/
static void DelVif(void *arg)
{
cbk_t *cbk = (cbk_t *)arg;
vifi_t vifi = cbk->vifi;
struct uvif *v = &uvifs[vifi];
struct listaddr *a, **anp, *g = cbk->g;
struct listaddr *curr, *prev = NULL;
if (IN_PIM_SSM_RANGE(g->al_addr)) {
for (curr = g->al_sources; curr; prev = curr, curr = curr->al_next) {
inet_fmt(cbk->source, s1, sizeof(s1));
inet_fmt(curr->al_addr, s2, sizeof(s2));
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "DelVif: Seek source %s, curr=%s (%p)", s1, s2, curr);
if (curr->al_addr == cbk->source) {
if (!prev)
g->al_sources = curr->al_next; /* Remove from beginning */
else
prev->al_next = curr->al_next;
free(curr);
break;
}
}
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "DelVif: %s sources left", g->al_sources ? "Still" : "No");
if (g->al_sources) {
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "DelVif: Not last source, g->al_sources --> %s",
inet_fmt(g->al_sources->al_addr, s1, sizeof(s1)));
delete_leaf(vifi, cbk->source, g->al_addr);
free(cbk);
return; /* This was not last source for this interface */
}
}
/*
* Group has expired
* delete all kernel cache entries with this group
*/
if (g->al_query)
DeleteTimer(g->al_query);
if (g->al_versiontimer)
DeleteTimer(g->al_versiontimer);
if (IN_PIM_SSM_RANGE(g->al_addr)) {
inet_fmt(g->al_addr, s1, sizeof(s1));
inet_fmt(cbk->source, s2, sizeof(s2));
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "SSM range, source specific delete");
/* delete (S,G) entry */
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "DelVif: vif:%d(%s), (S=%s,G=%s)", vifi, v->uv_name, s2, s1);
delete_leaf(vifi, cbk->source, g->al_addr);
} else {
delete_leaf(vifi, INADDR_ANY_N, g->al_addr);
}
anp = &(v->uv_groups);
while ((a = *anp)) {
if (a == g) {
*anp = a->al_next;
free(a->al_sources);
free(a);
} else {
anp = &a->al_next;
}
}
free(cbk);
}
/*
* Set a timer to switch version back on a vif.
*/
static int SetVersionTimer(vifi_t vifi, struct listaddr *g)
{
cbk_t *cbk;
cbk = (cbk_t *)calloc(1, sizeof(cbk_t));
if (!cbk) {
logit(LOG_ERR, 0, "Failed calloc() in SetVersionTimer()\n");
return -1;
}
cbk->vifi = vifi;
cbk->g = g;
return timer_setTimer(IGMP_ROBUSTNESS_VARIABLE * igmp_query_interval + IGMP_QUERY_RESPONSE_INTERVAL,
SwitchVersion, cbk);
}
/*
* Set a timer to delete the record of a group membership on a vif.
*/
static int SetTimer(vifi_t vifi, struct listaddr *g, uint32_t source)
{
cbk_t *cbk;
cbk = (cbk_t *) calloc(1, sizeof(cbk_t));
if (!cbk) {
logit(LOG_ERR, 0, "Failed calloc() in SetTimer()");
return -1;
}
cbk->vifi = vifi;
cbk->g = g;
cbk->source = source;
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Set delete timer for group: %s", inet_ntoa(*((struct in_addr *)&g->al_addr)));
return timer_setTimer(g->al_timer, DelVif, cbk);
}
/*
* Delete a timer that was set above.
*/
static int DeleteTimer(int id)
{
timer_clearTimer(id);
return 0;
}
/*
* Send IGMP Query
*/
static void send_query(struct uvif *v, uint32_t group, int interval)
{
if (v->uv_flags & VIFF_QUERIER) {
send_igmp(igmp_send_buf, v->uv_lcl_addr, group,
IGMP_MEMBERSHIP_QUERY, interval, group != allhosts_group ? group : 0, 0);
}
}
/*
* Send a group-specific query.
*/
static void SendQuery(void *arg)
{
cbk_t *cbk = (cbk_t *)arg;
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "SendQuery: Send IGMP v%s query", cbk->q_len == 4 ? "3" : "2");
send_query(&uvifs[cbk->vifi], cbk->g->al_addr, cbk->q_time);
cbk->g->al_query = 0;
free(cbk);
}
/*
* Set a timer to send a group-specific query.
*/
static int SetQueryTimer(struct listaddr *g, vifi_t vifi, int to_expire, int q_time, int q_len)
{
cbk_t *cbk;
cbk = (cbk_t *)calloc(1, sizeof(cbk_t));
if (!cbk) {
logit(LOG_ERR, 0, "Failed calloc() in SetQueryTimer()");
return -1;
}
cbk->g = g;
cbk->q_time = q_time;
cbk->q_len = q_len;
cbk->vifi = vifi;
return timer_setTimer(to_expire, SendQuery, cbk);
}
/**
* Local Variables:
* version-control: t
* indent-tabs-mode: t
* c-file-style: "ellemtel"
* c-basic-offset: 4
* End:
*/
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>