File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / pimd / igmp_proto.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Jun 12 07:59:37 2017 UTC (6 years, 11 months ago) by misho
Branches: pimd, MAIN
CVS tags: v2_3_2, HEAD
pimd 2.3.2

    1: /*
    2:  * Copyright (c) 1998-2001
    3:  * University of Southern California/Information Sciences Institute.
    4:  * All rights reserved.
    5:  *
    6:  * Redistribution and use in source and binary forms, with or without
    7:  * modification, are permitted provided that the following conditions
    8:  * are met:
    9:  * 1. Redistributions of source code must retain the above copyright
   10:  *    notice, this list of conditions and the following disclaimer.
   11:  * 2. Redistributions in binary form must reproduce the above copyright
   12:  *    notice, this list of conditions and the following disclaimer in the
   13:  *    documentation and/or other materials provided with the distribution.
   14:  * 3. Neither the name of the project nor the names of its contributors
   15:  *    may be used to endorse or promote products derived from this software
   16:  *    without specific prior written permission.
   17:  *
   18:  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
   19:  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   20:  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   21:  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
   22:  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   23:  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   24:  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   25:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   26:  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   27:  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   28:  * SUCH DAMAGE.
   29:  */
   30: /*
   31:  *  $Id: igmp_proto.c,v 1.1.1.1 2017/06/12 07:59:37 misho Exp $
   32:  */
   33: /*
   34:  * Part of this program has been derived from mrouted.
   35:  * The mrouted program is covered by the license in the accompanying file
   36:  * named "LICENSE.mrouted".
   37:  *
   38:  * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
   39:  * Leland Stanford Junior University.
   40:  *
   41:  */
   42: 
   43: #include "defs.h"
   44: 
   45: typedef struct {
   46:     vifi_t  vifi;
   47:     struct listaddr *g;
   48:     uint32_t source; /* Source for SSM */
   49:     int q_time; /* IGMP Code */
   50:     int q_len; /* Data length */
   51: } cbk_t;
   52: 
   53: 
   54: /*
   55:  * Forward declarations.
   56:  */
   57: static void DelVif       (void *arg);
   58: static int SetTimer      (vifi_t vifi, struct listaddr *g, uint32_t source);
   59: static int SetVersionTimer      (vifi_t vifi, struct listaddr *g);
   60: static int DeleteTimer   (int id);
   61: static void send_query   (struct uvif *v, uint32_t group, int interval);
   62: static void SendQuery    (void *arg);
   63: static int SetQueryTimer (struct listaddr *g, vifi_t vifi, int to_expire, int q_time, int q_len);
   64: static uint32_t igmp_group_membership_timeout(void);
   65: 
   66: /* The querier timeout depends on the configured query interval */
   67: uint32_t igmp_query_interval  = IGMP_QUERY_INTERVAL;
   68: uint32_t igmp_querier_timeout = IGMP_OTHER_QUERIER_PRESENT_INTERVAL;
   69: 
   70: 
   71: /*
   72:  * Send group membership queries on that interface if I am querier.
   73:  */
   74: void query_groups(struct uvif *v)
   75: {
   76:     int datalen = 4;
   77:     int code = IGMP_MAX_HOST_REPORT_DELAY * IGMP_TIMER_SCALE;
   78:     struct listaddr *g;
   79: 
   80:     v->uv_gq_timer = igmp_query_interval;
   81: 
   82:     if (v->uv_flags & VIFF_QUERIER) {
   83: 	/* IGMP version to use depends on the compatibility mode of the interface */
   84: 	if (v->uv_flags & VIFF_IGMPV2) {
   85: 	    /* RFC 3376: When in IGMPv2 mode, routers MUST send Periodic
   86: 	    Queries truncated at the Group Address field (i.e., 8 bytes long) */
   87: 	    datalen = 0;
   88: 	} else if (v->uv_flags & VIFF_IGMPV1) {
   89: 	    /* RFC 3376: When in IGMPv1 mode, routers MUST send Periodic Queries with a Max Response Time of 0 */
   90: 	    datalen = 0;
   91: 	    code = 0;
   92: 	}
   93: 
   94: 	IF_DEBUG(DEBUG_IGMP)
   95: 	    logit(LOG_DEBUG, 0, "%s(): Sending IGMP v%s query on %s",
   96: 		  __func__, datalen == 4 ? "3" : "2", v->uv_name);
   97: 	send_igmp(igmp_send_buf, v->uv_lcl_addr, allhosts_group,
   98: 		  IGMP_MEMBERSHIP_QUERY,
   99: 		  code, 0, datalen);
  100:     }
  101: 
  102:     /*
  103:      * Decrement the old-hosts-present timer for each
  104:      * active group on that vif.
  105:      */
  106:     for (g = v->uv_groups; g != NULL; g = g->al_next) {
  107: 	if (g->al_old > TIMER_INTERVAL)
  108: 	    g->al_old -= TIMER_INTERVAL;
  109: 	else
  110: 	    g->al_old = 0;
  111:     }
  112: }
  113: 
  114: 
  115: /*
  116:  * Process an incoming host membership query
  117:  */
  118: void accept_membership_query(uint32_t src, uint32_t dst __attribute__((unused)), uint32_t group, int tmo, int igmp_version)
  119: {
  120:     vifi_t vifi;
  121:     struct uvif *v;
  122: 
  123:     /* Ignore my own membership query */
  124:     if (local_address(src) != NO_VIF)
  125: 	return;
  126: 
  127:     /* Only v3 is allowed for SSM
  128:      * TODO: Rate-limit messages?
  129:      */
  130:     if (igmp_version != 3 && IN_PIM_SSM_RANGE(group)) {
  131: 	logit(LOG_WARNING, 0, "SSM addresses are not allowed in v%d query.", igmp_version);
  132: 	return;
  133:     }
  134: 
  135:     /* TODO: modify for DVMRP?? */
  136:     if ((vifi = find_vif_direct(src)) == NO_VIF) {
  137: 	IF_DEBUG(DEBUG_IGMP)
  138: 	    logit(LOG_INFO, 0, "Ignoring group membership query from non-adjacent host %s",
  139: 		  inet_fmt(src, s1, sizeof(s1)));
  140: 	return;
  141:     }
  142: 
  143:     v = &uvifs[vifi];
  144: 
  145:     /* Do not accept messages of higher version than current
  146:      * compatibility mode as specified in RFC 3376 - 7.3.1
  147:      */
  148:     if (v->uv_querier) {
  149: 	if ((igmp_version == 3 && (v->uv_flags & VIFF_IGMPV2)) ||
  150: 	    (igmp_version == 2 && (v->uv_flags & VIFF_IGMPV1))) {
  151: 	    int i;
  152: 
  153: 	    /*
  154: 	     * Exponentially back-off warning rate
  155: 	     */
  156: 	    i = ++v->uv_igmpv1_warn;
  157: 	    while (i && !(i & 1)) {
  158: 		i >>= 1;
  159: 		if (i == 1) {
  160: 		    logit(LOG_WARNING, 0, "Received IGMP v%d query from %s on vif %d,"
  161: 			  " but I am configured for IGMP v%d network compatibility mode",
  162: 			  igmp_version,
  163: 			  inet_fmt(src, s1, sizeof(s1)),
  164: 			  vifi,
  165: 			  v->uv_flags & VIFF_IGMPV1 ? 1 : 2);
  166: 		}
  167: 		return;
  168: 	    }
  169: 	}
  170:     }
  171: 
  172:     if (!v->uv_querier || v->uv_querier->al_addr != src) {
  173: 	/*
  174: 	 * This might be:
  175: 	 * - A query from a new querier, with a lower source address
  176: 	 *   than the current querier (who might be me)
  177: 	 * - A query from a new router that just started up and doesn't
  178: 	 *   know who the querier is.
  179: 	 * - A query from the current querier
  180: 	 */
  181: 	if (ntohl(src) < (v->uv_querier
  182: 			  ? ntohl(v->uv_querier->al_addr)
  183: 			  : ntohl(v->uv_lcl_addr))) {
  184: 	    IF_DEBUG(DEBUG_IGMP) {
  185: 		logit(LOG_DEBUG, 0, "new querier %s (was %s) on vif %d",
  186: 		      inet_fmt(src, s1, sizeof(s1)),
  187: 		      v->uv_querier
  188: 		      ? inet_fmt(v->uv_querier->al_addr, s2, sizeof(s2))
  189: 		      : "me", vifi);
  190: 	    }
  191: 
  192: 	    if (!v->uv_querier) {
  193: 		v->uv_querier = (struct listaddr *) calloc(1, sizeof(struct listaddr));
  194: 		if (!v->uv_querier) {
  195: 		    logit(LOG_ERR, 0, "Failed calloc() in accept_membership_query()");
  196: 		    return;
  197: 		}
  198: 
  199: 		v->uv_querier->al_next = (struct listaddr *)NULL;
  200: 		v->uv_querier->al_timer = 0;
  201: 		v->uv_querier->al_genid = 0;
  202: 		v->uv_querier->al_mv = 0;
  203: 		v->uv_querier->al_old = 0;
  204: 		v->uv_querier->al_index = 0;
  205: 		v->uv_querier->al_timerid = 0;
  206: 		v->uv_querier->al_query = 0;
  207: 		v->uv_querier->al_flags = 0;
  208: 
  209: 		v->uv_flags &= ~VIFF_QUERIER;
  210: 	    }
  211: 	    v->uv_querier->al_addr = src;
  212: 	    time(&v->uv_querier->al_ctime);
  213: 	}
  214:     }
  215: 
  216:     /*
  217:      * Reset the timer since we've received a query.
  218:      */
  219:     if (v->uv_querier && src == v->uv_querier->al_addr)
  220: 	v->uv_querier->al_timer = 0;
  221: 
  222:     /*
  223:      * If this is a Group-Specific query which we did not source,
  224:      * we must set our membership timer to [Last Member Query Count] *
  225:      * the [Max Response Time] in the packet.
  226:      */
  227:     if (!(v->uv_flags & VIFF_IGMPV1) && group != 0 && src != v->uv_lcl_addr) {
  228: 	struct listaddr *g;
  229: 
  230: 	IF_DEBUG(DEBUG_IGMP) {
  231: 	    logit(LOG_DEBUG, 0, "Group-specific membership query for %s from %s on vif %d, timer %d",
  232: 		  inet_fmt(group, s2, sizeof(s2)), inet_fmt(src, s1, sizeof(s1)), vifi, tmo);
  233: 	}
  234: 
  235: 	for (g = v->uv_groups; g != NULL; g = g->al_next) {
  236: 	    if (group == g->al_addr && g->al_query == 0) {
  237: 		/* setup a timeout to remove the group membership */
  238: 		if (g->al_timerid)
  239: 		    g->al_timerid = DeleteTimer(g->al_timerid);
  240: 
  241: 		g->al_timer = IGMP_LAST_MEMBER_QUERY_COUNT * tmo / IGMP_TIMER_SCALE;
  242: 		/* use al_query to record our presence in last-member state */
  243: 		g->al_query = -1;
  244: 		g->al_timerid = SetTimer(vifi, g, 0);
  245: 		IF_DEBUG(DEBUG_IGMP) {
  246: 		    logit(LOG_DEBUG, 0, "Timer for grp %s on vif %d set to %ld",
  247: 			  inet_fmt(group, s2, sizeof(s2)), vifi, g->al_timer);
  248: 		}
  249: 		break;
  250: 	    }
  251: 	}
  252:     }
  253: }
  254: 
  255: 
  256: /*
  257:  * Process an incoming group membership report.
  258:  */
  259: void accept_group_report(uint32_t igmp_src, uint32_t ssm_src, uint32_t group, int igmp_report_type)
  260: {
  261:     vifi_t vifi;
  262:     struct uvif *v;
  263:     struct listaddr *g;
  264:     struct listaddr *s = NULL;
  265: 
  266:     if ((vifi = find_vif_direct_local(igmp_src)) == NO_VIF) {
  267: 	IF_DEBUG(DEBUG_IGMP) {
  268: 	    logit(LOG_INFO, 0, "Ignoring group membership report from non-adjacent host %s",
  269: 		  inet_fmt(igmp_src, s1, sizeof(s1)));
  270: 	}
  271: 	return;
  272:     }
  273: 
  274:     inet_fmt(igmp_src, s1, sizeof(s1));
  275:     inet_fmt(ssm_src, s2, sizeof(s2));
  276:     inet_fmt(group, s3, sizeof(s3));
  277:     IF_DEBUG(DEBUG_IGMP)
  278: 	logit(LOG_DEBUG, 0, "%s(): igmp_src %s ssm_src %s group %s report_type %i",
  279: 	      __func__, s1, s2, s3, igmp_report_type);
  280: 
  281:     v = &uvifs[vifi];
  282: 
  283:     /*
  284:      * Look for the group in our group list; if found, reset its timer.
  285:      */
  286:     for (g = v->uv_groups; g != NULL; g = g->al_next) {
  287: 	if (group == g->al_addr) {
  288: 	    if (igmp_report_type == IGMP_V1_MEMBERSHIP_REPORT) {
  289: 		g->al_old = DVMRP_OLD_AGE_THRESHOLD;
  290: 		if (!IN_PIM_SSM_RANGE(group) && g->al_pv>1) {
  291: 		    IF_DEBUG(DEBUG_IGMP)
  292: 			logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v1 for group %s", s3);
  293: 		    g->al_pv = 1;
  294: 		}
  295: 	    } else if (!IN_PIM_SSM_RANGE(group) && igmp_report_type == IGMP_V2_MEMBERSHIP_REPORT) {
  296: 		IF_DEBUG(DEBUG_IGMP)
  297: 		    logit(LOG_DEBUG,0, "%s(): al_pv=%d", __func__, g->al_pv);
  298: 		if (g->al_pv > 2) {
  299: 		    IF_DEBUG(DEBUG_IGMP)
  300: 			logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v2 for group %s", s3);
  301: 		    g->al_pv = 2;
  302: 		}
  303: 	    }
  304: 
  305: 	    g->al_reporter = igmp_src;
  306: 
  307: 	    /** delete old timers, set a timer for expiration **/
  308: 	    g->al_timer = igmp_group_membership_timeout();
  309: 	    if (g->al_query)
  310: 		g->al_query = DeleteTimer(g->al_query);
  311: 
  312: 	    if (g->al_timerid)
  313: 		g->al_timerid = DeleteTimer(g->al_timerid);
  314: 
  315: 	    g->al_timerid = SetTimer(vifi, g, ssm_src);
  316: 
  317: 	    /* Reset timer for switching version back every time an older version report is received */
  318: 	    if (!IN_PIM_SSM_RANGE(group) && g->al_pv<3 && (igmp_report_type == IGMP_V1_MEMBERSHIP_REPORT ||
  319: 		igmp_report_type == IGMP_V2_MEMBERSHIP_REPORT)) {
  320: 		if (g->al_versiontimer)
  321: 			g->al_versiontimer = DeleteTimer(g->al_versiontimer);
  322: 
  323: 		g->al_versiontimer = SetVersionTimer(vifi, g);
  324: 	    }
  325: 
  326: 	    /* Find source */
  327: 	    if (IN_PIM_SSM_RANGE(group)) {
  328: 		for (s = g->al_sources; s; s = s->al_next) {
  329: 		    IF_DEBUG(DEBUG_IGMP)
  330: 			logit(LOG_DEBUG, 0, "%s(): Seek source %s, curr=%s", __func__,
  331: 			      inet_fmt(ssm_src, s1, sizeof(s1)),
  332: 			      inet_fmt(s->al_addr, s2, sizeof(s2)));
  333: 		    if (ssm_src == s->al_addr) {
  334: 			IF_DEBUG(DEBUG_IGMP)
  335: 			    logit(LOG_DEBUG, 0, "%s(): Source found", __func__);
  336: 			break;
  337: 		    }
  338: 		}
  339: 		if (!s) {
  340: 		    /* Add new source */
  341: 		    s = (struct listaddr *)calloc(1, sizeof(struct listaddr));
  342: 		    if (!s) {
  343: 			logit(LOG_ERR, errno, "%s(): Ran out of memory", __func__);
  344: 			return;
  345: 		    }
  346: 		    s->al_addr = ssm_src;
  347: 		    s->al_next = g->al_sources;
  348: 		    g->al_sources = s;
  349: 
  350: 		    IF_DEBUG(DEBUG_IGMP)
  351: 			logit(LOG_DEBUG, 0, "%s(): Source %s added to g:%p", __func__, s2, g);
  352: 		}
  353: 	    }
  354: 
  355: 	    /* TODO: might need to add a check if I am the forwarder??? */
  356: 	    /* if (v->uv_flags & VIFF_DR) */
  357: 	    if (IN_PIM_SSM_RANGE(group)) {
  358: 		IF_DEBUG(DEBUG_IGMP)
  359: 		    logit(LOG_INFO, 0, "Add leaf (%s,%s)", s1, s3);
  360: 		add_leaf(vifi, ssm_src, group);
  361: 	    } else {
  362: 		add_leaf(vifi, INADDR_ANY_N, group);
  363: 	    }
  364: 	    break;
  365: 	}
  366:     }
  367: 
  368:     /*
  369:      * If not found, add it to the list and update kernel cache.
  370:      */
  371:     if (!g) {
  372: 	g = (struct listaddr *)calloc(1, sizeof(struct listaddr));
  373: 	if (!g) {
  374: 	    logit(LOG_ERR, errno, "%s(): Ran out of memory", __func__);
  375: 	    return;
  376: 	}
  377: 
  378: 	g->al_addr = group;
  379: 	if (!IN_PIM_SSM_RANGE(group) && igmp_report_type == IGMP_V1_MEMBERSHIP_REPORT) {
  380: 	    g->al_old = DVMRP_OLD_AGE_THRESHOLD;
  381: 	    IF_DEBUG(DEBUG_IGMP)
  382: 		logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v1 for group %s", s3);
  383: 	    g->al_pv = 1;
  384: 	} else if (!IN_PIM_SSM_RANGE(group) && igmp_report_type == IGMP_V2_MEMBERSHIP_REPORT) {
  385: 	    IF_DEBUG(DEBUG_IGMP)
  386: 		logit(LOG_DEBUG, 0, "Change IGMP compatibility mode to v2 for group %s", s3);
  387: 	    g->al_pv = 2;
  388: 	} else {
  389: 	    g->al_pv = 3;
  390: 	}
  391: 
  392: 	/* Add new source */
  393: 	if (IN_PIM_SSM_RANGE(group)) {
  394: 	    s = (struct listaddr *)calloc(1, sizeof(struct listaddr));
  395: 	    if (!s) {
  396: 		logit(LOG_ERR, errno, "%s(): Ran out of memory", __func__);
  397: 		return;
  398: 	    }
  399: 	    s->al_addr = ssm_src;
  400: 	    s->al_next = g->al_sources;
  401: 	    g->al_sources = s;
  402: 	    IF_DEBUG(DEBUG_IGMP)
  403: 		logit(LOG_DEBUG, 0, "%s(): Source %s added to new g:%p", __func__, s2, g);
  404: 	}
  405: 
  406: 	/** set a timer for expiration **/
  407: 	g->al_query     = 0;
  408: 	g->al_timer     = IGMP_GROUP_MEMBERSHIP_INTERVAL;
  409: 	g->al_reporter  = igmp_src;
  410: 	g->al_timerid   = SetTimer(vifi, g, ssm_src);
  411: 
  412: 	/* Set timer for swithing version back if an older version report is received */
  413: 	if (!IN_PIM_SSM_RANGE(group) && g->al_pv<3) {
  414: 	    g->al_versiontimer = SetVersionTimer(vifi, g);
  415: 	}
  416: 
  417: 	g->al_next      = v->uv_groups;
  418: 	v->uv_groups    = g;
  419: 	time(&g->al_ctime);
  420: 
  421: 	/* TODO: might need to add a check if I am the forwarder??? */
  422: 	/* if (v->uv_flags & VIFF_DR) */
  423: 	if (IN_PIM_SSM_RANGE(group)) {
  424: 	    IF_DEBUG(DEBUG_IGMP)
  425: 		logit(LOG_INFO, 0, "SSM group order from  %s (%s,%s)", s1, s2, s3);
  426: 	    add_leaf(vifi, ssm_src, group);
  427: 	} else {
  428: 	    IF_DEBUG(DEBUG_IGMP)
  429: 		logit(LOG_INFO, 0, "SM group order from  %s (*,%s)", s1, s3);
  430: 	    add_leaf(vifi, INADDR_ANY_N, group);
  431: 	}
  432:     }
  433: }
  434: 
  435: 
  436: /* TODO: send PIM prune message if the last member? */
  437: void accept_leave_message(uint32_t src, uint32_t dst __attribute__((unused)), uint32_t group)
  438: {
  439:     vifi_t vifi;
  440:     struct uvif *v;
  441:     struct listaddr *g;
  442: 
  443:     int datalen = 4;
  444:     int code = IGMP_LAST_MEMBER_QUERY_INTERVAL * IGMP_TIMER_SCALE;
  445: 
  446:     /* TODO: modify for DVMRP ??? */
  447:     if ((vifi = find_vif_direct_local(src)) == NO_VIF) {
  448: 	IF_DEBUG(DEBUG_IGMP)
  449: 	    logit(LOG_INFO, 0, "ignoring group leave report from non-adjacent host %s",
  450: 		  inet_fmt(src, s1, sizeof(s1)));
  451: 	return;
  452:     }
  453: 
  454:     inet_fmt(src, s1, sizeof(s1));
  455:     inet_fmt(dst, s2, sizeof(s2));
  456:     inet_fmt(group, s3, sizeof(s3));
  457:     IF_DEBUG(DEBUG_IGMP)
  458: 	logit(LOG_DEBUG, 0, "%s(): src %s dst %s group %s", __func__, s1, s2, s3);
  459:     v = &uvifs[vifi];
  460: 
  461: #if 0
  462:     /* XXX: a PIM-SM last-hop router needs to know when a local member
  463:      * has left.
  464:      */
  465:     if (!(v->uv_flags & (VIFF_QUERIER | VIFF_DR))
  466: 	|| (v->uv_flags & VIFF_IGMPV1))
  467: 	return;
  468: #endif
  469: 
  470:     /*
  471:      * Look for the group in our group list in order to set up a short-timeout
  472:      * query.
  473:      */
  474:     for (g = v->uv_groups; g; g = g->al_next) {
  475: 	if (group == g->al_addr) {
  476: 	    IF_DEBUG(DEBUG_IGMP)
  477: 		logit(LOG_DEBUG, 0, "accept_leave_message(): old=%d query=%d", g->al_old, g->al_query);
  478: 
  479: 	    /* Ignore the leave message if there are old hosts present */
  480: 	    if (g->al_old)
  481: 		return;
  482: 
  483: 	    /* still waiting for a reply to a query, ignore the leave */
  484: 	    if (g->al_query)
  485: 		return;
  486: 
  487: 	    /* TODO: Remove the source. Ignore the leave if there
  488: 	       are still sources left
  489: 	    if (IN_PIM_SSM_RANGE(g->al_addr)) {
  490: 		for (s = g->al_sources; s != NULL; s = s->al_next) {
  491: 		    if (dst == s->al_addr) {
  492: 		    }
  493: 		}
  494: 	    }
  495: 	    */
  496: 
  497: 	    /** delete old timer set a timer for expiration **/
  498: 	    if (g->al_timerid)
  499: 		g->al_timerid = DeleteTimer(g->al_timerid);
  500: 
  501: #if IGMP_LAST_MEMBER_QUERY_COUNT != 2
  502: /*
  503:   This code needs to be updated to keep a counter of the number
  504:   of queries remaining.
  505: */
  506: #endif
  507: 
  508: 	    if (v->uv_flags & VIFF_QUERIER) {
  509: 		/* Use lowest IGMP version */
  510: 		if (v->uv_flags & VIFF_IGMPV2 || g->al_pv <= 2) {
  511: 		    datalen = 0;
  512: 		} else if (v->uv_flags & VIFF_IGMPV1 || g->al_pv == 1) {
  513: 		    datalen = 0;
  514: 		    code = 0;
  515: 		}
  516: 
  517: 		IF_DEBUG(DEBUG_IGMP)
  518: 		    logit(LOG_DEBUG, 0, "%s(): Sending IGMP v%s query (al_pv=%d)",
  519: 			  __func__, datalen == 4 ? "3" : "2", g->al_pv);
  520: 		send_igmp(igmp_send_buf, v->uv_lcl_addr, g->al_addr,
  521: 			  IGMP_MEMBERSHIP_QUERY,
  522: 			  code,
  523: 			  g->al_addr, datalen);
  524: 	    }
  525: 
  526: 	    g->al_timer = IGMP_LAST_MEMBER_QUERY_INTERVAL * (IGMP_LAST_MEMBER_QUERY_COUNT + 1);
  527: 	    g->al_query = SetQueryTimer(g, vifi,
  528: 					IGMP_LAST_MEMBER_QUERY_INTERVAL,
  529: 					code, datalen);
  530: 	    g->al_timerid = SetTimer(vifi, g, dst);
  531: 	    break;
  532: 	}
  533:     }
  534: }
  535: 
  536: /*
  537:  * Time out old version compatibility mode
  538:  */
  539: static void SwitchVersion(void *arg)
  540: {
  541:     cbk_t *cbk = (cbk_t *)arg;
  542: 
  543:     if (cbk->g->al_pv < 3)
  544: 	cbk->g->al_pv += 1;
  545: 
  546:     logit(LOG_INFO, 0, "Switch IGMP compatibility mode back to v%d for group %s",
  547: 	  cbk->g->al_pv, inet_fmt(cbk->g->al_addr, s1, sizeof(s1)));
  548: }
  549: 
  550: /*
  551:  * Loop through and process all sources in a v3 record.
  552:  *
  553:  * Parameters:
  554:  *     igmp_report_type   Report type of IGMP message
  555:  *     igmp_src           Src address of IGMP message
  556:  *     group              Multicast group
  557:  *     sources            Pointer to the beginning of sources list in the IGMP message
  558:  *     report_pastend     Pointer to the end of IGMP message
  559:  *
  560:  * Returns:
  561:  *     1 if succeeded, 0 if failed
  562:  */
  563: int accept_sources(int igmp_report_type, uint32_t igmp_src, uint32_t group, uint8_t *sources,
  564:     uint8_t *report_pastend, int rec_num_sources) {
  565:     int j;
  566:     uint8_t *src;
  567:     char src_str[200];
  568: 
  569:     for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
  570:         if ((src + 4) > report_pastend) {
  571: 	    IF_DEBUG(DEBUG_IGMP)
  572: 		logit(LOG_DEBUG, 0, "src +4 > report_pastend");
  573:             return 0;
  574:         }
  575: 
  576:         inet_ntop(AF_INET, src, src_str , sizeof(src_str));
  577: 	IF_DEBUG(DEBUG_IGMP)
  578: 	    logit(LOG_DEBUG, 0, "Add source (%s,%s)", src_str, inet_fmt(group, s1, sizeof(s1)));
  579: 
  580:         accept_group_report(igmp_src, ((struct in_addr*)src)->s_addr, group, igmp_report_type);
  581: 
  582: 	IF_DEBUG(DEBUG_IGMP)
  583: 	    logit(LOG_DEBUG, 0, "Accepted, switch SPT (%s,%s)", src_str, inet_fmt(group, s1, sizeof(s1)));
  584:         switch_shortest_path(((struct in_addr*)src)->s_addr, group);
  585:     }
  586: 
  587:     return 1;
  588: }
  589: 
  590: /*
  591:  * Handle IGMP v3 membership reports (join/leave)
  592:  */
  593: void accept_membership_report(uint32_t src, uint32_t dst, struct igmpv3_report *report, ssize_t reportlen)
  594: {
  595:     struct igmpv3_grec *record;
  596:     int num_groups, i;
  597:     uint8_t *report_pastend = (uint8_t *)report + reportlen;
  598: 
  599:     num_groups = ntohs(report->ngrec);
  600:     if (num_groups < 0) {
  601: 	logit(LOG_INFO, 0, "Invalid Membership Report from %s: num_groups = %d",
  602: 	      inet_fmt(src, s1, sizeof(s1)), num_groups);
  603: 	return;
  604:     }
  605: 
  606:     IF_DEBUG(DEBUG_IGMP)
  607: 	logit(LOG_DEBUG, 0, "%s(): IGMP v3 report, %d bytes, from %s to %s with %d group records.",
  608: 	      __func__, reportlen, inet_fmt(src, s1, sizeof(s1)), inet_fmt(dst, s2, sizeof(s2)), num_groups);
  609: 
  610:     record = &report->grec[0];
  611: 
  612:     for (i = 0; i < num_groups; i++) {
  613: 	struct in_addr  rec_group;
  614: 	uint8_t        *sources;
  615: 	int             rec_type;
  616: 	int             rec_auxdatalen;
  617: 	int             rec_num_sources;
  618: 	int             j;
  619: 	char src_str[200];
  620: 	int record_size = 0;
  621: 
  622: 	rec_num_sources = ntohs(record->grec_nsrcs);
  623: 	rec_auxdatalen = record->grec_auxwords;
  624: 	record_size = sizeof(struct igmpv3_grec) + sizeof(uint32_t) * rec_num_sources + rec_auxdatalen;
  625: 	if ((uint8_t *)record + record_size > report_pastend) {
  626: 	    logit(LOG_INFO, 0, "Invalid group report %p > %p",
  627: 		  (uint8_t *)record + record_size, report_pastend);
  628: 	    return;
  629: 	}
  630: 
  631: 	rec_type = record->grec_type;
  632: 	rec_group.s_addr = (in_addr_t)record->grec_mca;
  633: 	sources = (u_int8_t *)record->grec_src;
  634: 
  635: 	switch (rec_type) {
  636: 	    case IGMP_MODE_IS_EXCLUDE:
  637: 		/* RFC 4604: A router SHOULD ignore a group record of
  638: 		   type MODE_IS_EXCLUDE if it refers to an SSM destination address */
  639: 		if (!IN_PIM_SSM_RANGE(rec_group.s_addr)) {
  640: 		    if (rec_num_sources==0) {
  641: 			/* RFC 5790: EXCLUDE (*,G) join can be interpreted by the router
  642: 			   as a request to include all sources. */
  643: 			accept_group_report(src, 0 /*dst*/, rec_group.s_addr, report->type);
  644: 		    } else {
  645: 			/* RFC 5790: LW-IGMPv3 does not use EXCLUDE filter-mode with a non-null source address list.*/
  646: 			logit(LOG_INFO, 0, "Record type MODE_IS_EXCLUDE with non-null source list is currently unsupported.");
  647: 		    }
  648: 		}
  649: 		break;
  650: 
  651: 	    case IGMP_CHANGE_TO_EXCLUDE_MODE:
  652: 		/* RFC 4604: A router SHOULD ignore a group record of
  653: 		   type CHANGE_TO_EXCLUDE_MODE if it refers to an SSM destination address */
  654: 		if (!IN_PIM_SSM_RANGE(rec_group.s_addr)) {
  655: 		    if (rec_num_sources==0) {
  656: 			/* RFC 5790: EXCLUDE (*,G) join can be interpreted by the router
  657: 			   as a request to include all sources. */
  658: 			accept_group_report(src, 0 /*dst*/, rec_group.s_addr, report->type);
  659: 		    } else {
  660: 			/* RFC 5790: LW-IGMPv3 does not use EXCLUDE filter-mode with a non-null source address list.*/
  661: 			logit(LOG_DEBUG, 0, "Record type MODE_TO_EXCLUDE with non-null source list is currently unsupported.");
  662: 		    }
  663: 		}
  664: 		break;
  665: 
  666: 	    case IGMP_MODE_IS_INCLUDE:
  667: 		if (!accept_sources(report->type, src, rec_group.s_addr, sources, report_pastend, rec_num_sources)) {
  668: 		    IF_DEBUG(DEBUG_IGMP)
  669: 			logit(LOG_DEBUG, 0, "Accept sources failed.");
  670: 		    return;
  671: 		}
  672: 		break;
  673: 
  674: 	    case IGMP_CHANGE_TO_INCLUDE_MODE:
  675: 		if (!accept_sources(report->type, src, rec_group.s_addr, sources, report_pastend, rec_num_sources)) {
  676: 		    IF_DEBUG(DEBUG_IGMP)
  677: 			logit(LOG_DEBUG, 0, "Accept sources failed.");
  678: 		    return;
  679: 		}
  680: 		break;
  681: 
  682: 	    case IGMP_ALLOW_NEW_SOURCES:
  683: 		if (!accept_sources(report->type, src, rec_group.s_addr, sources, report_pastend, rec_num_sources)) {
  684: 		    logit(LOG_DEBUG, 0, "Accept sources failed.");
  685: 		    return;
  686: 		}
  687: 		break;
  688: 
  689: 	    case IGMP_BLOCK_OLD_SOURCES:
  690: 		for (j = 0; j < rec_num_sources; j++) {
  691: 		    uint32_t *gsrc = (uint32_t *)&record->grec_src[j];
  692: 
  693: 		    if ((uint8_t *)gsrc > report_pastend) {
  694: 			logit(LOG_INFO, 0, "Invalid group record");
  695: 			return;
  696: 		    }
  697: 
  698: 		    inet_ntop(AF_INET, gsrc, src_str , sizeof(src_str));
  699: 		    IF_DEBUG(DEBUG_IGMP)
  700: 			logit(LOG_DEBUG, 0, "Remove source[%d] (%s,%s)", j, src_str, inet_ntoa(rec_group));
  701: 		    accept_leave_message(src, *gsrc, rec_group.s_addr);
  702: 		    IF_DEBUG(DEBUG_IGMP)
  703: 			logit(LOG_DEBUG, 0, "Accepted");
  704: 		}
  705: 		break;
  706: 
  707: 	    default:
  708: 		//  RFC3376: Unrecognized Record Type values MUST be silently ignored.
  709: 		break;
  710: 	}
  711: 
  712: 	record = (struct igmpv3_grec *)((uint8_t *)record + record_size);
  713:     }
  714: }
  715: 
  716: /*
  717:  * Calculate group membership timeout
  718:  */
  719: static uint32_t igmp_group_membership_timeout(void)
  720: {
  721:     return IGMP_ROBUSTNESS_VARIABLE * igmp_query_interval + IGMP_QUERY_RESPONSE_INTERVAL;
  722: }
  723: 
  724: /*
  725:  * Time out record of a group membership on a vif
  726:  */
  727: static void DelVif(void *arg)
  728: {
  729:     cbk_t *cbk = (cbk_t *)arg;
  730:     vifi_t vifi = cbk->vifi;
  731:     struct uvif *v = &uvifs[vifi];
  732:     struct listaddr *a, **anp, *g = cbk->g;
  733:     struct listaddr *curr, *prev = NULL;
  734: 
  735:     if (IN_PIM_SSM_RANGE(g->al_addr)) {
  736: 	for (curr = g->al_sources; curr; prev = curr, curr = curr->al_next) {
  737: 	    inet_fmt(cbk->source, s1, sizeof(s1));
  738: 	    inet_fmt(curr->al_addr, s2, sizeof(s2));
  739: 	    IF_DEBUG(DEBUG_IGMP)
  740: 		logit(LOG_DEBUG, 0, "DelVif: Seek source %s, curr=%s (%p)", s1, s2, curr);
  741: 
  742: 	    if (curr->al_addr == cbk->source) {
  743: 		if (!prev)
  744: 		    g->al_sources = curr->al_next; /* Remove from beginning */
  745: 		else
  746: 		    prev->al_next = curr->al_next;
  747: 
  748: 		free(curr);
  749: 		break;
  750: 	    }
  751: 	}
  752: 
  753: 	IF_DEBUG(DEBUG_IGMP)
  754: 	    logit(LOG_DEBUG, 0, "DelVif: %s sources left", g->al_sources ? "Still" : "No");
  755: 	if (g->al_sources) {
  756: 	    IF_DEBUG(DEBUG_IGMP)
  757: 		logit(LOG_DEBUG, 0, "DelVif: Not last source, g->al_sources --> %s",
  758: 		      inet_fmt(g->al_sources->al_addr, s1, sizeof(s1)));
  759: 	    delete_leaf(vifi, cbk->source, g->al_addr);
  760: 	    free(cbk);
  761: 
  762: 	    return;    /* This was not last source for this interface */
  763: 	}
  764:     }
  765: 
  766:     /*
  767:      * Group has expired
  768:      * delete all kernel cache entries with this group
  769:      */
  770:     if (g->al_query)
  771: 	DeleteTimer(g->al_query);
  772: 
  773:     if (g->al_versiontimer)
  774: 	DeleteTimer(g->al_versiontimer);
  775: 
  776:     if (IN_PIM_SSM_RANGE(g->al_addr)) {
  777: 	inet_fmt(g->al_addr, s1, sizeof(s1));
  778: 	inet_fmt(cbk->source, s2, sizeof(s2));
  779: 	IF_DEBUG(DEBUG_IGMP)
  780: 	    logit(LOG_DEBUG, 0, "SSM range, source specific delete");
  781: 
  782: 	/* delete (S,G) entry */
  783: 	IF_DEBUG(DEBUG_IGMP)
  784: 	    logit(LOG_DEBUG, 0, "DelVif: vif:%d(%s), (S=%s,G=%s)", vifi, v->uv_name, s2, s1);
  785: 	delete_leaf(vifi, cbk->source, g->al_addr);
  786:     } else {
  787: 	delete_leaf(vifi, INADDR_ANY_N, g->al_addr);
  788:     }
  789: 
  790:     anp = &(v->uv_groups);
  791:     while ((a = *anp)) {
  792: 	if (a == g) {
  793: 	    *anp = a->al_next;
  794: 	    free(a->al_sources);
  795: 	    free(a);
  796: 	} else {
  797: 	    anp = &a->al_next;
  798: 	}
  799:     }
  800: 
  801:     free(cbk);
  802: }
  803: 
  804: /*
  805:  * Set a timer to switch version back on a vif.
  806:  */
  807: static int SetVersionTimer(vifi_t vifi, struct listaddr *g)
  808: {
  809:     cbk_t *cbk;
  810: 
  811:     cbk = (cbk_t *)calloc(1, sizeof(cbk_t));
  812:     if (!cbk) {
  813: 	logit(LOG_ERR, 0, "Failed calloc() in SetVersionTimer()\n");
  814: 	return -1;
  815:     }
  816: 
  817:     cbk->vifi = vifi;
  818:     cbk->g = g;
  819: 
  820:     return timer_setTimer(IGMP_ROBUSTNESS_VARIABLE * igmp_query_interval + IGMP_QUERY_RESPONSE_INTERVAL,
  821: 			  SwitchVersion, cbk);
  822: }
  823: 
  824: /*
  825:  * Set a timer to delete the record of a group membership on a vif.
  826:  */
  827: static int SetTimer(vifi_t vifi, struct listaddr *g, uint32_t source)
  828: {
  829:     cbk_t *cbk;
  830: 
  831:     cbk = (cbk_t *) calloc(1, sizeof(cbk_t));
  832:     if (!cbk) {
  833: 	logit(LOG_ERR, 0, "Failed calloc() in SetTimer()");
  834: 	return -1;
  835:     }
  836: 
  837:     cbk->vifi = vifi;
  838:     cbk->g = g;
  839:     cbk->source = source;
  840: 
  841:     IF_DEBUG(DEBUG_IGMP)
  842: 	logit(LOG_DEBUG, 0, "Set delete timer for group: %s", inet_ntoa(*((struct in_addr *)&g->al_addr)));
  843: 
  844:     return timer_setTimer(g->al_timer, DelVif, cbk);
  845: }
  846: 
  847: 
  848: /*
  849:  * Delete a timer that was set above.
  850:  */
  851: static int DeleteTimer(int id)
  852: {
  853:     timer_clearTimer(id);
  854: 
  855:     return 0;
  856: }
  857: 
  858: 
  859: /*
  860:  * Send IGMP Query
  861:  */
  862: static void send_query(struct uvif *v, uint32_t group, int interval)
  863: {
  864:     if (v->uv_flags & VIFF_QUERIER) {
  865: 	send_igmp(igmp_send_buf, v->uv_lcl_addr, group,
  866: 		  IGMP_MEMBERSHIP_QUERY, interval, group != allhosts_group ? group : 0, 0);
  867:     }
  868: }
  869: 
  870: /*
  871:  * Send a group-specific query.
  872:  */
  873: static void SendQuery(void *arg)
  874: {
  875:     cbk_t *cbk = (cbk_t *)arg;
  876: 
  877:     IF_DEBUG(DEBUG_IGMP)
  878: 	logit(LOG_DEBUG, 0, "SendQuery: Send IGMP v%s query", cbk->q_len == 4 ? "3" : "2");
  879:     send_query(&uvifs[cbk->vifi], cbk->g->al_addr, cbk->q_time);
  880:     cbk->g->al_query = 0;
  881:     free(cbk);
  882: }
  883: 
  884: 
  885: /*
  886:  * Set a timer to send a group-specific query.
  887:  */
  888: static int SetQueryTimer(struct listaddr *g, vifi_t vifi, int to_expire, int q_time, int q_len)
  889: {
  890:     cbk_t *cbk;
  891: 
  892:     cbk = (cbk_t *)calloc(1, sizeof(cbk_t));
  893:     if (!cbk) {
  894: 	logit(LOG_ERR, 0, "Failed calloc() in SetQueryTimer()");
  895: 	return -1;
  896:     }
  897: 
  898:     cbk->g = g;
  899:     cbk->q_time = q_time;
  900:     cbk->q_len = q_len;
  901:     cbk->vifi = vifi;
  902: 
  903:     return timer_setTimer(to_expire, SendQuery, cbk);
  904: }
  905: 
  906: /**
  907:  * Local Variables:
  908:  *  version-control: t
  909:  *  indent-tabs-mode: t
  910:  *  c-file-style: "ellemtel"
  911:  *  c-basic-offset: 4
  912:  * End:
  913:  */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>