blob: 6ad521ca0d1292eb56aafbe7270e85a6ae438f7a [file] [log] [blame]
Everton Marques871dbcf2009-08-11 15:43:05 -03001/*
2 PIM for Quagga
3 Copyright (C) 2008 Everton da Silva Marques
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING; if not, write to the
17 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
18 MA 02110-1301 USA
19
20 $QuaggaId: $Format:%an, %ai, %h$ $
21*/
22
23#include <zebra.h>
24
25#include "memory.h"
26
27#include "pimd.h"
28#include "pim_igmp.h"
29#include "pim_igmpv3.h"
30#include "pim_iface.h"
31#include "pim_sock.h"
32#include "pim_mroute.h"
33#include "pim_str.h"
34#include "pim_util.h"
35#include "pim_time.h"
36#include "pim_zebra.h"
37
38#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE (1)
39#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE (2)
40#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3)
41#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4)
42#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES (5)
43#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES (6)
44
45static void group_timer_off(struct igmp_group *group);
46
47static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
48 struct in_addr group_addr);
49
50static int igmp_sock_open(struct in_addr ifaddr, int ifindex, uint32_t pim_options)
51{
52 int fd;
53 int join = 0;
54 struct in_addr group;
55
56 fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, 1 /* loop=true */);
57 if (fd < 0)
58 return -1;
59
60 if (PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(pim_options)) {
61 if (inet_aton(PIM_ALL_ROUTERS, &group)) {
62 if (!pim_socket_join(fd, group, ifaddr, ifindex))
63 ++join;
64 }
65 else {
66 zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
67 __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
Everton Marquese96f0af2009-08-11 15:48:02 -030068 PIM_ALL_ROUTERS, errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -030069 }
70 }
71
72 /*
73 IGMP routers periodically send IGMP general queries to AllSystems=224.0.0.1
74 IGMP routers must receive general queries for querier election.
75 */
76 if (inet_aton(PIM_ALL_SYSTEMS, &group)) {
77 if (!pim_socket_join(fd, group, ifaddr, ifindex))
78 ++join;
79 }
80 else {
81 zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
82 __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
Everton Marquese96f0af2009-08-11 15:48:02 -030083 PIM_ALL_SYSTEMS, errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -030084 }
85
86 if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
87 if (!pim_socket_join(fd, group, ifaddr, ifindex)) {
88 ++join;
89 }
90 }
91 else {
92 zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
93 __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
Everton Marquese96f0af2009-08-11 15:48:02 -030094 PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -030095 }
96
97 if (!join) {
98 zlog_err("IGMP socket fd=%d could not join any group on interface address %s",
99 fd, inet_ntoa(ifaddr));
100 close(fd);
101 fd = -1;
102 }
103
104 return fd;
105}
106
107#undef IGMP_SOCK_DUMP
108
109#ifdef IGMP_SOCK_DUMP
110static void igmp_sock_dump(array_t *igmp_sock_array)
111{
112 int size = array_size(igmp_sock_array);
113 for (int i = 0; i < size; ++i) {
114
115 struct igmp_sock *igmp = array_get(igmp_sock_array, i);
116
117 zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d",
118 __FILE__, __PRETTY_FUNCTION__,
119 i, size,
120 inet_ntoa(igmp->ifaddr),
121 igmp->fd);
122 }
123}
124#endif
125
126struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
127 struct in_addr ifaddr)
128{
129 struct listnode *sock_node;
130 struct igmp_sock *igmp;
131
132#ifdef IGMP_SOCK_DUMP
133 igmp_sock_dump(igmp_sock_list);
134#endif
135
136 for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
137 if (ifaddr.s_addr == igmp->ifaddr.s_addr)
138 return igmp;
139
140 return 0;
141}
142
143struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list,
144 int fd)
145{
146 struct listnode *sock_node;
147 struct igmp_sock *igmp;
148
149 for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
150 if (fd == igmp->fd)
151 return igmp;
152
153 return 0;
154}
155
156static int pim_igmp_other_querier_expire(struct thread *t)
157{
158 struct igmp_sock *igmp;
159
160 zassert(t);
161 igmp = THREAD_ARG(t);
162 zassert(igmp);
163
164 zassert(igmp->t_other_querier_timer);
165 zassert(!igmp->t_igmp_query_timer);
166
167 if (PIM_DEBUG_IGMP_TRACE) {
168 char ifaddr_str[100];
169 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
170 zlog_debug("%s: Querier %s resuming",
171 __PRETTY_FUNCTION__,
172 ifaddr_str);
173 }
174
175 igmp->t_other_querier_timer = 0;
176
177 /*
178 We are the current querier, then
179 re-start sending general queries.
180 */
181 pim_igmp_general_query_on(igmp);
182
183 return 0;
184}
185
186void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp)
187{
188 long other_querier_present_interval_msec;
189 struct pim_interface *pim_ifp;
190
191 zassert(igmp);
192 zassert(igmp->interface);
193 zassert(igmp->interface->info);
194
195 pim_ifp = igmp->interface->info;
196
197 if (igmp->t_other_querier_timer) {
198 /*
199 There is other querier present already,
200 then reset the other-querier-present timer.
201 */
202
203 if (PIM_DEBUG_IGMP_TRACE) {
204 char ifaddr_str[100];
205 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
206 zlog_debug("Querier %s resetting TIMER event for Other-Querier-Present",
207 ifaddr_str);
208 }
209
210 THREAD_OFF(igmp->t_other_querier_timer);
211 zassert(!igmp->t_other_querier_timer);
212 }
213 else {
214 /*
215 We are the current querier, then stop sending general queries:
216 igmp->t_igmp_query_timer = 0;
217 */
218 pim_igmp_general_query_off(igmp);
219 }
220
221 /*
222 Since this socket is starting the other-querier-present timer,
223 there should not be periodic query timer for this socket.
224 */
225 zassert(!igmp->t_igmp_query_timer);
226
227 /*
228 RFC 3376: 8.5. Other Querier Present Interval
229
230 The Other Querier Present Interval is the length of time that must
231 pass before a multicast router decides that there is no longer
232 another multicast router which should be the querier. This value
233 MUST be ((the Robustness Variable) times (the Query Interval)) plus
234 (one half of one Query Response Interval).
235
236 other_querier_present_interval_msec = \
237 igmp->querier_robustness_variable * \
238 1000 * igmp->querier_query_interval + \
239 100 * (pim_ifp->query_max_response_time_dsec >> 1);
240 */
241 other_querier_present_interval_msec =
242 PIM_IGMP_OQPI_MSEC(igmp->querier_robustness_variable,
243 igmp->querier_query_interval,
244 pim_ifp->igmp_query_max_response_time_dsec);
245
246 if (PIM_DEBUG_IGMP_TRACE) {
247 char ifaddr_str[100];
248 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
249 zlog_debug("Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
250 ifaddr_str,
251 other_querier_present_interval_msec / 1000,
252 other_querier_present_interval_msec % 1000);
253 }
254
255 THREAD_TIMER_MSEC_ON(master, igmp->t_other_querier_timer,
256 pim_igmp_other_querier_expire,
257 igmp, other_querier_present_interval_msec);
258}
259
260void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp)
261{
262 zassert(igmp);
263
264 if (PIM_DEBUG_IGMP_TRACE) {
265 if (igmp->t_other_querier_timer) {
266 char ifaddr_str[100];
267 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
268 zlog_debug("IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
269 ifaddr_str, igmp->fd, igmp->interface->name);
270 }
271 }
272 THREAD_OFF(igmp->t_other_querier_timer);
273 zassert(!igmp->t_other_querier_timer);
274}
275
276static int recv_igmp_query(struct igmp_sock *igmp, int query_version,
277 int max_resp_code,
278 struct in_addr from, const char *from_str,
279 char *igmp_msg, int igmp_msg_len)
280{
281 struct interface *ifp;
282 struct pim_interface *pim_ifp;
283 uint8_t resv_s_qrv;
284 uint8_t s_flag;
285 uint8_t qrv;
286 struct in_addr group_addr;
287 uint16_t recv_checksum;
288 uint16_t checksum;
Leonard Herve942b0fd2009-08-14 15:49:06 +0200289 int i;
Everton Marques871dbcf2009-08-11 15:43:05 -0300290
291 group_addr = *(struct in_addr *)(igmp_msg + 4);
292
293 ifp = igmp->interface;
294 pim_ifp = ifp->info;
295
296 recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
297
298 /* for computing checksum */
299 *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
300
301 checksum = pim_inet_checksum(igmp_msg, igmp_msg_len);
302 if (checksum != recv_checksum) {
303 zlog_warn("Recv IGMP query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
304 query_version, from_str, ifp->name, recv_checksum, checksum);
305 return -1;
306 }
307
308 if (PIM_DEBUG_IGMP_PACKETS) {
309 char group_str[100];
310 pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
311 zlog_debug("Recv IGMP query v%d from %s on %s: size=%d checksum=%x group=%s",
312 query_version, from_str, ifp->name,
313 igmp_msg_len, checksum, group_str);
314 }
315
316 /*
317 RFC 3376: 6.6.2. Querier Election
318
319 When a router receives a query with a lower IP address, it sets
320 the Other-Querier-Present timer to Other Querier Present Interval
321 and ceases to send queries on the network if it was the previously
322 elected querier.
323 */
324 if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
325
326 if (PIM_DEBUG_IGMP_TRACE) {
327 char ifaddr_str[100];
328 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
329 zlog_debug("%s: local address %s (%u) lost querier election to %s (%u)",
330 ifp->name,
331 ifaddr_str, ntohl(igmp->ifaddr.s_addr),
332 from_str, ntohl(from.s_addr));
333 }
334
335 pim_igmp_other_querier_timer_on(igmp);
336 }
337
338 /*
339 RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
340
341 Routers adopt the QRV value from the most recently received Query
342 as their own [Robustness Variable] value, unless that most
343 recently received QRV was zero, in which case the receivers use
344 the default [Robustness Variable] value specified in section 8.1
345 or a statically configured value.
346 */
347 resv_s_qrv = igmp_msg[8];
348 qrv = 7 & resv_s_qrv;
349 igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable;
350
351 /*
352 RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
353
354 Multicast routers that are not the current querier adopt the QQI
355 value from the most recently received Query as their own [Query
356 Interval] value, unless that most recently received QQI was zero,
357 in which case the receiving routers use the default.
358 */
359 if (igmp->t_other_querier_timer) {
360 /* other querier present */
361 uint8_t qqic;
362 uint16_t qqi;
363 qqic = igmp_msg[9];
364 qqi = igmp_msg_decode8to16(qqic);
365 igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval;
366
367 if (PIM_DEBUG_IGMP_TRACE) {
368 char ifaddr_str[100];
369 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
370 zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)",
371 ifaddr_str,
372 qqi ? "recv-non-default" : "default",
373 igmp->querier_query_interval,
374 qqic,
375 from_str);
376 }
377 }
378
379 /*
380 RFC 3376: 6.6.1. Timer Updates
381
382 When a router sends or receives a query with a clear Suppress
383 Router-Side Processing flag, it must update its timers to reflect
384 the correct timeout values for the group or sources being queried.
385
386 General queries don't trigger timer update.
387 */
388 s_flag = (1 << 3) & resv_s_qrv;
389 if (!s_flag) {
390 /* s_flag is clear */
391
392 if (PIM_INADDR_IS_ANY(group_addr)) {
393 /* this is a general query */
394
395 /* log that general query should have the s_flag set */
Everton Marquesd12beab2009-08-12 10:52:22 -0300396 zlog_warn("General IGMP query v%d from %s on %s: Suppress Router-Side Processing flag is clear",
Everton Marques871dbcf2009-08-11 15:43:05 -0300397 query_version, from_str, ifp->name);
398 }
399 else {
400 struct igmp_group *group;
401
402 /* this is a non-general query: perform timer updates */
403
404 group = find_group_by_addr(igmp, group_addr);
405 if (group) {
406 int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET));
407
408 /*
409 RFC 3376: 6.6.1. Timer Updates
410 Query Q(G,A): Source Timer for sources in A are lowered to LMQT
411 Query Q(G): Group Timer is lowered to LMQT
412 */
413 if (recv_num_sources < 1) {
414 /* Query Q(G): Group Timer is lowered to LMQT */
415
416 igmp_group_timer_lower_to_lmqt(group);
417 }
418 else {
419 /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */
420
421 /* Scan sources in query and lower their timers to LMQT */
422 struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET);
Leonard Herve942b0fd2009-08-14 15:49:06 +0200423 for (i = 0; i < recv_num_sources; ++i) {
Everton Marques871dbcf2009-08-11 15:43:05 -0300424 struct in_addr src_addr = sources[i];
425 struct igmp_source *src = igmp_find_source_by_addr(group, src_addr);
426 if (src) {
427 igmp_source_timer_lower_to_lmqt(src);
428 }
429 }
430 }
431
432 }
433 else {
434 char group_str[100];
435 pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
436 zlog_warn("IGMP query v%d from %s on %s: could not find group %s for timer update",
437 query_version, from_str, ifp->name, group_str);
438 }
439 }
440 } /* s_flag is clear: timer updates */
441
442 return 0;
443}
444
445static int igmp_v3_report(struct igmp_sock *igmp,
446 struct in_addr from, const char *from_str,
447 char *igmp_msg, int igmp_msg_len)
448{
449 uint16_t recv_checksum;
450 uint16_t checksum;
451 int num_groups;
452 uint8_t *group_record;
453 uint8_t *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len;
454 struct interface *ifp = igmp->interface;
Leonard Herve942b0fd2009-08-14 15:49:06 +0200455 int i;
Everton Marques871dbcf2009-08-11 15:43:05 -0300456
457 if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) {
458 zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d",
459 from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE);
460 return -1;
461 }
462
463 recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
464
465 /* for computing checksum */
466 *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
467
468 checksum = pim_inet_checksum(igmp_msg, igmp_msg_len);
469 if (checksum != recv_checksum) {
470 zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x",
471 from_str, ifp->name, recv_checksum, checksum);
472 return -1;
473 }
474
475 num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET));
476 if (num_groups < 1) {
477 zlog_warn("Recv IGMP report v3 from %s on %s: missing group records",
478 from_str, ifp->name);
479 return -1;
480 }
481
482 if (PIM_DEBUG_IGMP_PACKETS) {
483 zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d",
484 from_str, ifp->name, igmp_msg_len, checksum, num_groups);
485 }
486
487 group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
488
489 /* Scan groups */
Leonard Herve942b0fd2009-08-14 15:49:06 +0200490 for (i = 0; i < num_groups; ++i) {
Everton Marques871dbcf2009-08-11 15:43:05 -0300491 struct in_addr rec_group;
492 uint8_t *sources;
493 uint8_t *src;
494 int rec_type;
495 int rec_auxdatalen;
496 int rec_num_sources;
497 int j;
498
499 if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) {
500 zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end",
501 from_str, ifp->name);
502 return -1;
503 }
504
505 rec_type = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET];
506 rec_auxdatalen = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET];
507 rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET));
508
509 rec_group = *(struct in_addr *)(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET);
510
511 if (PIM_DEBUG_IGMP_PACKETS) {
512 zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s",
513 from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group));
514 }
515
516 /* Scan sources */
517
518 sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET;
519
520 for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
521
522 if ((src + 4) > report_pastend) {
523 zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end",
524 from_str, ifp->name);
525 return -1;
526 }
527
528 if (PIM_DEBUG_IGMP_PACKETS) {
529 char src_str[200];
530
531 if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str)))
532 sprintf(src_str, "<source?>");
533
534 zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s",
535 from_str, ifp->name, i, inet_ntoa(rec_group), src_str);
536 }
537 } /* for (sources) */
538
539 switch (rec_type) {
540 case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE:
541 igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
542 break;
543 case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE:
544 igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
545 break;
546 case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE:
547 igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
548 break;
549 case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE:
550 igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
551 break;
552 case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES:
553 igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
554 break;
555 case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES:
556 igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
557 break;
558 default:
559 zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d",
560 from_str, ifp->name, rec_type);
561 }
562
563 group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2);
564
565 } /* for (group records) */
566
567 return 0;
568}
569
570static void on_trace(const char *label,
571 struct interface *ifp, struct in_addr from)
572{
573 if (PIM_DEBUG_IGMP_TRACE) {
574 char from_str[100];
575 pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
576 zlog_debug("%s: from %s on %s",
577 label, from_str, ifp->name);
578 }
579}
580
581static int igmp_v2_report(struct igmp_sock *igmp,
582 struct in_addr from, const char *from_str,
583 char *igmp_msg, int igmp_msg_len)
584{
585 struct interface *ifp = igmp->interface;
586 struct igmp_group *group;
587 struct in_addr group_addr;
588
589 on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
590
591 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
592 zlog_warn("Recv IGMP report v2 from %s on %s: size=%d other than correct=%d",
593 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
594 return -1;
595 }
596
597 if (PIM_DEBUG_IGMP_TRACE) {
598 zlog_warn("%s %s: FIXME WRITEME",
599 __FILE__, __PRETTY_FUNCTION__);
600 }
601
602 group_addr = *(struct in_addr *)(igmp_msg + 4);
603
604 /* non-existant group is created as INCLUDE {empty} */
605 group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
606 if (!group) {
607 return -1;
608 }
609
610 group->last_igmp_v2_report_dsec = pim_time_monotonic_dsec();
611
612 return 0;
613}
614
615static int igmp_v2_leave(struct igmp_sock *igmp,
616 struct in_addr from, const char *from_str,
617 char *igmp_msg, int igmp_msg_len)
618{
619 struct interface *ifp = igmp->interface;
620
621 on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
622
623 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
624 zlog_warn("Recv IGMP leave v2 from %s on %s: size=%d other than correct=%d",
625 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
626 return -1;
627 }
628
629 if (PIM_DEBUG_IGMP_TRACE) {
630 zlog_warn("%s %s: FIXME WRITEME",
631 __FILE__, __PRETTY_FUNCTION__);
632 }
633
634 return 0;
635}
636
637static int igmp_v1_report(struct igmp_sock *igmp,
638 struct in_addr from, const char *from_str,
639 char *igmp_msg, int igmp_msg_len)
640{
641 struct interface *ifp = igmp->interface;
642 struct igmp_group *group;
643 struct in_addr group_addr;
644
645 on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
646
647 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
648 zlog_warn("Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
649 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
650 return -1;
651 }
652
653 if (PIM_DEBUG_IGMP_TRACE) {
654 zlog_warn("%s %s: FIXME WRITEME",
655 __FILE__, __PRETTY_FUNCTION__);
656 }
657
658 group_addr = *(struct in_addr *)(igmp_msg + 4);
659
660 /* non-existant group is created as INCLUDE {empty} */
661 group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
662 if (!group) {
663 return -1;
664 }
665
666 group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec();
667
668 return 0;
669}
670
671int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len)
672{
673 struct ip *ip_hdr;
674 size_t ip_hlen; /* ip header length in bytes */
675 char *igmp_msg;
676 int igmp_msg_len;
677 int msg_type;
678 char from_str[100];
679 char to_str[100];
680
681 if (len < sizeof(*ip_hdr)) {
David Lamparter5c697982012-02-16 04:47:56 +0100682 zlog_warn("IGMP packet size=%zu shorter than minimum=%zu",
Everton Marques871dbcf2009-08-11 15:43:05 -0300683 len, sizeof(*ip_hdr));
684 return -1;
685 }
686
687 ip_hdr = (struct ip *) buf;
688
689 pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str , sizeof(from_str));
690 pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str , sizeof(to_str));
691
692 ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
693
694 if (PIM_DEBUG_IGMP_PACKETS) {
David Lamparter5c697982012-02-16 04:47:56 +0100695 zlog_debug("Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d",
Everton Marques871dbcf2009-08-11 15:43:05 -0300696 from_str, to_str, igmp->interface->name, len, ip_hlen, ip_hdr->ip_p);
697 }
698
699 if (ip_hdr->ip_p != PIM_IP_PROTO_IGMP) {
700 zlog_warn("IP packet protocol=%d is not IGMP=%d",
701 ip_hdr->ip_p, PIM_IP_PROTO_IGMP);
702 return -1;
703 }
704
705 if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
David Lamparter5c697982012-02-16 04:47:56 +0100706 zlog_warn("IP packet header size=%zu shorter than minimum=%d",
Everton Marques871dbcf2009-08-11 15:43:05 -0300707 ip_hlen, PIM_IP_HEADER_MIN_LEN);
708 return -1;
709 }
710 if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
David Lamparter5c697982012-02-16 04:47:56 +0100711 zlog_warn("IP packet header size=%zu greater than maximum=%d",
Everton Marques871dbcf2009-08-11 15:43:05 -0300712 ip_hlen, PIM_IP_HEADER_MAX_LEN);
713 return -1;
714 }
715
716 igmp_msg = buf + ip_hlen;
717 msg_type = *igmp_msg;
718 igmp_msg_len = len - ip_hlen;
719
720 if (PIM_DEBUG_IGMP_PACKETS) {
721 zlog_debug("Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
722 from_str, to_str, igmp->interface->name, ip_hdr->ip_ttl, msg_type,
723 igmp_msg_len);
724 }
725
726 if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
727 zlog_warn("IGMP message size=%d shorter than minimum=%d",
728 igmp_msg_len, PIM_IGMP_MIN_LEN);
729 return -1;
730 }
731
732 switch (msg_type) {
733 case PIM_IGMP_MEMBERSHIP_QUERY:
734 {
735 int max_resp_code = igmp_msg[1];
736 int query_version;
737
738 /*
739 RFC 3376: 7.1. Query Version Distinctions
740 IGMPv1 Query: length = 8 octets AND Max Resp Code field is zero
741 IGMPv2 Query: length = 8 octets AND Max Resp Code field is non-zero
742 IGMPv3 Query: length >= 12 octets
743 */
744
745 if (igmp_msg_len == 8) {
746 query_version = max_resp_code ? 2 : 1;
747 }
748 else if (igmp_msg_len >= 12) {
749 query_version = 3;
750 }
751 else {
752 zlog_warn("Unknown IGMP query version");
753 return -1;
754 }
755
756 return recv_igmp_query(igmp, query_version, max_resp_code,
757 ip_hdr->ip_src, from_str,
758 igmp_msg, igmp_msg_len);
759 }
760
761 case PIM_IGMP_V3_MEMBERSHIP_REPORT:
762 return igmp_v3_report(igmp, ip_hdr->ip_src, from_str,
763 igmp_msg, igmp_msg_len);
764
765 case PIM_IGMP_V2_MEMBERSHIP_REPORT:
766 return igmp_v2_report(igmp, ip_hdr->ip_src, from_str,
767 igmp_msg, igmp_msg_len);
768
769 case PIM_IGMP_V1_MEMBERSHIP_REPORT:
770 return igmp_v1_report(igmp, ip_hdr->ip_src, from_str,
771 igmp_msg, igmp_msg_len);
772
773 case PIM_IGMP_V2_LEAVE_GROUP:
774 return igmp_v2_leave(igmp, ip_hdr->ip_src, from_str,
775 igmp_msg, igmp_msg_len);
776 }
777
778 zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
779
780 return -1;
781}
782
783static int pim_igmp_general_query(struct thread *t);
784
785void pim_igmp_general_query_on(struct igmp_sock *igmp)
786{
787 struct pim_interface *pim_ifp;
788 int startup_mode;
789 int query_interval;
790
791 zassert(igmp);
792 zassert(igmp->interface);
793
794 /*
795 Since this socket is starting as querier,
796 there should not exist a timer for other-querier-present.
797 */
798 zassert(!igmp->t_other_querier_timer);
799 pim_ifp = igmp->interface->info;
800 zassert(pim_ifp);
801
802 /*
803 RFC 3376: 8.6. Startup Query Interval
804
805 The Startup Query Interval is the interval between General Queries
806 sent by a Querier on startup. Default: 1/4 the Query Interval.
807 */
808 startup_mode = igmp->startup_query_count > 0;
809 if (startup_mode) {
810 --igmp->startup_query_count;
811
812 /* query_interval = pim_ifp->igmp_default_query_interval >> 2; */
813 query_interval = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
814 }
815 else {
816 query_interval = igmp->querier_query_interval;
817 }
818
819 if (PIM_DEBUG_IGMP_TRACE) {
820 char ifaddr_str[100];
821 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
822 zlog_debug("Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
823 ifaddr_str,
824 query_interval,
825 startup_mode ? "startup" : "non-startup",
826 igmp->fd);
827 }
828 igmp->t_igmp_query_timer = 0;
829 zassert(!igmp->t_igmp_query_timer);
830 THREAD_TIMER_ON(master, igmp->t_igmp_query_timer,
831 pim_igmp_general_query,
832 igmp, query_interval);
833}
834
835void pim_igmp_general_query_off(struct igmp_sock *igmp)
836{
837 zassert(igmp);
838
839 if (PIM_DEBUG_IGMP_TRACE) {
840 if (igmp->t_igmp_query_timer) {
841 char ifaddr_str[100];
842 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
843 zlog_debug("IGMP querier %s fd=%d cancelling query TIMER event on %s",
844 ifaddr_str, igmp->fd, igmp->interface->name);
845 }
846 }
847 THREAD_OFF(igmp->t_igmp_query_timer);
848 zassert(!igmp->t_igmp_query_timer);
849}
850
851/* Issue IGMP general query */
852static int pim_igmp_general_query(struct thread *t)
853{
854 char query_buf[PIM_IGMP_BUFSIZE_WRITE];
855 struct igmp_sock *igmp;
856 struct in_addr dst_addr;
857 struct in_addr group_addr;
858 struct pim_interface *pim_ifp;
859
860 zassert(t);
861
862 igmp = THREAD_ARG(t);
863
864 zassert(igmp);
865 zassert(igmp->interface);
866 zassert(igmp->interface->info);
867
868 pim_ifp = igmp->interface->info;
869
870 /*
871 RFC3376: 4.1.12. IP Destination Addresses for Queries
872
873 In IGMPv3, General Queries are sent with an IP destination address
874 of 224.0.0.1, the all-systems multicast address. Group-Specific
875 and Group-and-Source-Specific Queries are sent with an IP
876 destination address equal to the multicast address of interest.
877 */
878
879 dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
880 group_addr.s_addr = PIM_NET_INADDR_ANY;
881
882 if (PIM_DEBUG_IGMP_TRACE) {
883 char querier_str[100];
884 char dst_str[100];
885 pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
886 sizeof(querier_str));
887 pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
888 zlog_debug("Querier %s issuing IGMP general query to %s on %s",
889 querier_str, dst_str, igmp->interface->name);
890 }
891
892 pim_igmp_send_membership_query(0 /* igmp_group */,
893 igmp->fd,
894 igmp->interface->name,
895 query_buf,
896 sizeof(query_buf),
897 0 /* num_sources */,
898 dst_addr,
899 group_addr,
900 pim_ifp->igmp_query_max_response_time_dsec,
901 1 /* s_flag: always set for general queries */,
902 igmp->querier_robustness_variable,
903 igmp->querier_query_interval);
904
905 pim_igmp_general_query_on(igmp);
906
907 return 0;
908}
909
910static int pim_igmp_read(struct thread *t);
911
912static void igmp_read_on(struct igmp_sock *igmp)
913{
914 zassert(igmp);
915
916 if (PIM_DEBUG_IGMP_TRACE) {
917 zlog_debug("Scheduling READ event on IGMP socket fd=%d",
918 igmp->fd);
919 }
920 igmp->t_igmp_read = 0;
921 zassert(!igmp->t_igmp_read);
922 THREAD_READ_ON(master, igmp->t_igmp_read, pim_igmp_read, igmp, igmp->fd);
923}
924
925static int pim_igmp_read(struct thread *t)
926{
927 struct igmp_sock *igmp;
928 int fd;
929 struct sockaddr_in from;
930 struct sockaddr_in to;
931 socklen_t fromlen = sizeof(from);
932 socklen_t tolen = sizeof(to);
David Lamparterf8cfeb22012-02-16 04:31:08 +0000933 uint8_t buf[PIM_IGMP_BUFSIZE_READ];
Everton Marques871dbcf2009-08-11 15:43:05 -0300934 int len;
935 int ifindex = -1;
936 int result = -1; /* defaults to bad */
937
938 zassert(t);
939
940 igmp = THREAD_ARG(t);
941
942 zassert(igmp);
943
944 fd = THREAD_FD(t);
945
946 zassert(fd == igmp->fd);
947
948 len = pim_socket_recvfromto(fd, buf, sizeof(buf),
949 &from, &fromlen,
950 &to, &tolen,
951 &ifindex);
952 if (len < 0) {
953 zlog_warn("Failure receiving IP IGMP packet on fd=%d: errno=%d: %s",
Everton Marquese96f0af2009-08-11 15:48:02 -0300954 fd, errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -0300955 goto done;
956 }
957
958 if (PIM_DEBUG_IGMP_PACKETS) {
959 char from_str[100];
960 char to_str[100];
961
962 if (!inet_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str)))
963 sprintf(from_str, "<from?>");
964 if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str)))
965 sprintf(to_str, "<to?>");
966
967 zlog_debug("Recv IP IGMP pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)",
968 len, from_str, to_str, fd, ifindex, igmp->interface->ifindex);
969 }
970
971#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
972 /* ifindex sanity check */
973 if (ifindex != (int) igmp->interface->ifindex) {
974 char from_str[100];
975 char to_str[100];
976 struct interface *ifp;
977
978 if (!inet_ntop(AF_INET, &from.sin_addr, from_str , sizeof(from_str)))
979 sprintf(from_str, "<from?>");
980 if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str)))
981 sprintf(to_str, "<to?>");
982
983 ifp = if_lookup_by_index(ifindex);
984 if (ifp) {
985 zassert(ifindex == (int) ifp->ifindex);
986 }
987
988#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
989 zlog_warn("Interface mismatch: recv IGMP pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)",
990 from_str, to_str, fd,
991 ifindex, ifp ? ifp->name : "<if-notfound>",
992 igmp->interface->ifindex, igmp->interface->name);
993#endif
994 goto done;
995 }
996#endif
997
David Lamparterf8cfeb22012-02-16 04:31:08 +0000998 if (pim_igmp_packet(igmp, (char *)buf, len)) {
Everton Marques871dbcf2009-08-11 15:43:05 -0300999 goto done;
1000 }
1001
1002 result = 0; /* good */
1003
1004 done:
1005 igmp_read_on(igmp);
1006
1007 return result;
1008}
1009
1010static void sock_close(struct igmp_sock *igmp)
1011{
1012 pim_igmp_other_querier_timer_off(igmp);
1013 pim_igmp_general_query_off(igmp);
1014
1015 if (PIM_DEBUG_IGMP_TRACE) {
1016 if (igmp->t_igmp_read) {
1017 zlog_debug("Cancelling READ event on IGMP socket %s fd=%d on interface %s",
1018 inet_ntoa(igmp->ifaddr), igmp->fd,
1019 igmp->interface->name);
1020 }
1021 }
1022 THREAD_OFF(igmp->t_igmp_read);
1023 zassert(!igmp->t_igmp_read);
1024
1025 if (close(igmp->fd)) {
1026 zlog_err("Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
1027 inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name,
Everton Marquese96f0af2009-08-11 15:48:02 -03001028 errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -03001029 }
1030
1031 if (PIM_DEBUG_IGMP_TRACE) {
1032 zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
1033 inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name);
1034 }
1035}
1036
1037void igmp_startup_mode_on(struct igmp_sock *igmp)
1038{
1039 struct pim_interface *pim_ifp;
1040
1041 pim_ifp = igmp->interface->info;
1042
1043 /*
1044 RFC 3376: 8.7. Startup Query Count
1045
1046 The Startup Query Count is the number of Queries sent out on
1047 startup, separated by the Startup Query Interval. Default: the
1048 Robustness Variable.
1049 */
1050 igmp->startup_query_count = igmp->querier_robustness_variable;
1051
1052 /*
1053 Since we're (re)starting, reset QQI to default Query Interval
1054 */
1055 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
1056}
1057
1058static void igmp_group_free(struct igmp_group *group)
1059{
1060 zassert(!group->t_group_query_retransmit_timer);
1061 zassert(!group->t_group_timer);
1062 zassert(group->group_source_list);
1063 zassert(!listcount(group->group_source_list));
1064
1065 list_free(group->group_source_list);
1066
1067 XFREE(MTYPE_PIM_IGMP_GROUP, group);
1068}
1069
1070static void igmp_group_delete(struct igmp_group *group)
1071{
1072 struct listnode *src_node;
1073 struct listnode *src_nextnode;
1074 struct igmp_source *src;
1075
1076 if (PIM_DEBUG_IGMP_TRACE) {
1077 char group_str[100];
1078 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1079 zlog_debug("Deleting IGMP group %s from socket %d interface %s",
1080 group_str,
1081 group->group_igmp_sock->fd,
1082 group->group_igmp_sock->interface->name);
1083 }
1084
1085 for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, src)) {
1086 igmp_source_delete(src);
1087 }
1088
1089 if (group->t_group_query_retransmit_timer) {
1090 THREAD_OFF(group->t_group_query_retransmit_timer);
1091 zassert(!group->t_group_query_retransmit_timer);
1092 }
1093
1094 group_timer_off(group);
1095 listnode_delete(group->group_igmp_sock->igmp_group_list, group);
1096 igmp_group_free(group);
1097}
1098
1099void igmp_group_delete_empty_include(struct igmp_group *group)
1100{
1101 zassert(!group->group_filtermode_isexcl);
1102 zassert(!listcount(group->group_source_list));
1103
1104 igmp_group_delete(group);
1105}
1106
1107void igmp_sock_free(struct igmp_sock *igmp)
1108{
1109 zassert(!igmp->t_igmp_read);
1110 zassert(!igmp->t_igmp_query_timer);
1111 zassert(!igmp->t_other_querier_timer);
1112 zassert(igmp->igmp_group_list);
1113 zassert(!listcount(igmp->igmp_group_list));
1114
1115 list_free(igmp->igmp_group_list);
1116
1117 XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
1118}
1119
1120void igmp_sock_delete(struct igmp_sock *igmp)
1121{
1122 struct pim_interface *pim_ifp;
1123 struct listnode *grp_node;
1124 struct listnode *grp_nextnode;
1125 struct igmp_group *grp;
1126
1127 for (ALL_LIST_ELEMENTS(igmp->igmp_group_list, grp_node, grp_nextnode, grp)) {
1128 igmp_group_delete(grp);
1129 }
1130
1131 sock_close(igmp);
1132
1133 pim_ifp = igmp->interface->info;
1134
1135 listnode_delete(pim_ifp->igmp_socket_list, igmp);
1136
1137 igmp_sock_free(igmp);
1138}
1139
1140static struct igmp_sock *igmp_sock_new(int fd,
1141 struct in_addr ifaddr,
1142 struct interface *ifp)
1143{
1144 struct pim_interface *pim_ifp;
1145 struct igmp_sock *igmp;
1146
1147 pim_ifp = ifp->info;
1148
1149 if (PIM_DEBUG_IGMP_TRACE) {
1150 zlog_debug("Creating IGMP socket fd=%d for address %s on interface %s",
1151 fd, inet_ntoa(ifaddr), ifp->name);
1152 }
1153
1154 igmp = XMALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
1155 if (!igmp) {
1156 zlog_warn("%s %s: XMALLOC() failure",
1157 __FILE__, __PRETTY_FUNCTION__);
1158 return 0;
1159 }
1160
1161 igmp->igmp_group_list = list_new();
1162 if (!igmp->igmp_group_list) {
1163 zlog_err("%s %s: failure: igmp_group_list = list_new()",
1164 __FILE__, __PRETTY_FUNCTION__);
1165 return 0;
1166 }
1167 igmp->igmp_group_list->del = (void (*)(void *)) igmp_group_free;
1168
1169 igmp->fd = fd;
1170 igmp->interface = ifp;
1171 igmp->ifaddr = ifaddr;
1172 igmp->t_igmp_read = 0;
1173 igmp->t_igmp_query_timer = 0;
1174 igmp->t_other_querier_timer = 0; /* no other querier present */
1175 igmp->querier_robustness_variable = pim_ifp->igmp_default_robustness_variable;
1176 igmp->sock_creation = pim_time_monotonic_sec();
1177
1178 /*
1179 igmp_startup_mode_on() will reset QQI:
1180
1181 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
1182 */
1183 igmp_startup_mode_on(igmp);
1184
1185 igmp_read_on(igmp);
1186 pim_igmp_general_query_on(igmp);
1187
1188 return igmp;
1189}
1190
1191struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
1192 struct in_addr ifaddr,
1193 struct interface *ifp)
1194{
1195 struct pim_interface *pim_ifp;
1196 struct igmp_sock *igmp;
1197 int fd;
1198
1199 pim_ifp = ifp->info;
1200
1201 fd = igmp_sock_open(ifaddr, ifp->ifindex, pim_ifp->options);
1202 if (fd < 0) {
1203 zlog_warn("Could not open IGMP socket for %s on %s",
1204 inet_ntoa(ifaddr), ifp->name);
1205 return 0;
1206 }
1207
1208 igmp = igmp_sock_new(fd, ifaddr, ifp);
1209 if (!igmp) {
1210 zlog_err("%s %s: igmp_sock_new() failure",
1211 __FILE__, __PRETTY_FUNCTION__);
1212 close(fd);
1213 return 0;
1214 }
1215
1216 listnode_add(igmp_sock_list, igmp);
1217
1218#ifdef IGMP_SOCK_DUMP
1219 igmp_sock_dump(igmp_sock_array);
1220#endif
1221
1222 return igmp;
1223}
1224
1225/*
1226 RFC 3376: 6.5. Switching Router Filter-Modes
1227
1228 When a router's filter-mode for a group is EXCLUDE and the group
1229 timer expires, the router filter-mode for the group transitions to
1230 INCLUDE.
1231
1232 A router uses source records with running source timers as its state
1233 for the switch to a filter-mode of INCLUDE. If there are any source
1234 records with source timers greater than zero (i.e., requested to be
1235 forwarded), a router switches to filter-mode of INCLUDE using those
1236 source records. Source records whose timers are zero (from the
1237 previous EXCLUDE mode) are deleted.
1238 */
1239static int igmp_group_timer(struct thread *t)
1240{
1241 struct igmp_group *group;
1242
1243 zassert(t);
1244 group = THREAD_ARG(t);
1245 zassert(group);
1246
1247 if (PIM_DEBUG_IGMP_TRACE) {
1248 char group_str[100];
1249 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1250 zlog_debug("%s: Timer for group %s on interface %s",
1251 __PRETTY_FUNCTION__,
1252 group_str, group->group_igmp_sock->interface->name);
1253 }
1254
1255 zassert(group->group_filtermode_isexcl);
1256
1257 group->t_group_timer = 0;
1258 group->group_filtermode_isexcl = 0;
1259
1260 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1261 igmp_anysource_forward_stop(group);
1262
1263 igmp_source_delete_expired(group->group_source_list);
1264
1265 zassert(!group->t_group_timer);
1266 zassert(!group->group_filtermode_isexcl);
1267
1268 /*
1269 RFC 3376: 6.2.2. Definition of Group Timers
1270
1271 If there are no more source records for the group, delete group
1272 record.
1273 */
1274 if (listcount(group->group_source_list) < 1) {
1275 igmp_group_delete_empty_include(group);
1276 }
1277
1278 return 0;
1279}
1280
1281static void group_timer_off(struct igmp_group *group)
1282{
1283 if (!group->t_group_timer)
1284 return;
1285
1286 if (PIM_DEBUG_IGMP_TRACE) {
1287 char group_str[100];
1288 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1289 zlog_debug("Cancelling TIMER event for group %s on %s",
1290 group_str, group->group_igmp_sock->interface->name);
1291 }
1292
1293 THREAD_OFF(group->t_group_timer);
1294 zassert(!group->t_group_timer);
1295}
1296
1297void igmp_group_timer_on(struct igmp_group *group,
1298 long interval_msec, const char *ifname)
1299{
1300 group_timer_off(group);
1301
1302 if (PIM_DEBUG_IGMP_EVENTS) {
1303 char group_str[100];
1304 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1305 zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s on %s",
1306 interval_msec / 1000,
1307 interval_msec % 1000,
1308 group_str, ifname);
1309 }
1310
1311 /*
1312 RFC 3376: 6.2.2. Definition of Group Timers
1313
1314 The group timer is only used when a group is in EXCLUDE mode and
1315 it represents the time for the *filter-mode* of the group to
1316 expire and switch to INCLUDE mode.
1317 */
1318 zassert(group->group_filtermode_isexcl);
1319
1320 THREAD_TIMER_MSEC_ON(master, group->t_group_timer,
1321 igmp_group_timer,
1322 group, interval_msec);
1323}
1324
1325static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
1326 struct in_addr group_addr)
1327{
1328 struct igmp_group *group;
1329 struct listnode *node;
1330
1331 for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, node, group))
1332 if (group_addr.s_addr == group->group_addr.s_addr)
1333 return group;
1334
1335 return 0;
1336}
1337
1338struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
1339 struct in_addr group_addr,
1340 const char *ifname)
1341{
1342 struct igmp_group *group;
1343
1344 group = find_group_by_addr(igmp, group_addr);
1345 if (group) {
1346 return group;
1347 }
1348
1349 /*
1350 Non-existant group is created as INCLUDE {empty}:
1351
1352 RFC 3376 - 5.1. Action on Change of Interface State
1353
1354 If no interface state existed for that multicast address before
1355 the change (i.e., the change consisted of creating a new
1356 per-interface record), or if no state exists after the change
1357 (i.e., the change consisted of deleting a per-interface record),
1358 then the "non-existent" state is considered to have a filter mode
1359 of INCLUDE and an empty source list.
1360 */
1361
1362 group = XMALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
1363 if (!group) {
1364 zlog_warn("%s %s: XMALLOC() failure",
1365 __FILE__, __PRETTY_FUNCTION__);
1366 return 0; /* error, not found, could not create */
1367 }
1368
1369 group->group_source_list = list_new();
1370 if (!group->group_source_list) {
1371 zlog_warn("%s %s: list_new() failure",
1372 __FILE__, __PRETTY_FUNCTION__);
1373 XFREE(MTYPE_PIM_IGMP_GROUP, group); /* discard group */
1374 return 0; /* error, not found, could not initialize */
1375 }
1376 group->group_source_list->del = (void (*)(void *)) igmp_source_free;
1377
1378 group->t_group_timer = 0;
1379 group->t_group_query_retransmit_timer = 0;
1380 group->group_specific_query_retransmit_count = 0;
1381 group->group_addr = group_addr;
1382 group->group_igmp_sock = igmp;
1383 group->last_igmp_v1_report_dsec = -1;
1384 group->last_igmp_v2_report_dsec = -1;
1385 group->group_creation = pim_time_monotonic_sec();
1386
1387 /* initialize new group as INCLUDE {empty} */
1388 group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
1389
1390 listnode_add(igmp->igmp_group_list, group);
1391
1392 if (PIM_DEBUG_IGMP_TRACE) {
1393 char group_str[100];
1394 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1395 zlog_debug("Creating new IGMP group %s on socket %d interface %s",
1396 group_str, group->group_igmp_sock->fd, ifname);
1397 }
1398
1399 /*
1400 RFC 3376: 6.2.2. Definition of Group Timers
1401
1402 The group timer is only used when a group is in EXCLUDE mode and
1403 it represents the time for the *filter-mode* of the group to
1404 expire and switch to INCLUDE mode.
1405 */
1406 zassert(!group->group_filtermode_isexcl); /* INCLUDE mode */
1407 zassert(!group->t_group_timer); /* group timer == 0 */
1408
1409 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1410 igmp_anysource_forward_stop(group);
1411
1412 return group;
1413}