Annotation of embedaddon/pimd/igmp_proto.c, revision 1.1.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>