Annotation of embedaddon/pimd/igmp_proto.c, revision 1.1
1.1 ! misho 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.15 2001/09/10 20:31:36 pavlin 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>