blob: 03cf3cd1b2bc78a02e15d145f6c716134af6618f [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
Everton Marquesd4595862014-02-13 19:50:30 -0200291 //group_addr = *(struct in_addr *)(igmp_msg + 4);
292 memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
Everton Marques871dbcf2009-08-11 15:43:05 -0300293
294 ifp = igmp->interface;
295 pim_ifp = ifp->info;
296
297 recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
298
299 /* for computing checksum */
300 *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
301
302 checksum = pim_inet_checksum(igmp_msg, igmp_msg_len);
303 if (checksum != recv_checksum) {
304 zlog_warn("Recv IGMP query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
305 query_version, from_str, ifp->name, recv_checksum, checksum);
306 return -1;
307 }
308
309 if (PIM_DEBUG_IGMP_PACKETS) {
310 char group_str[100];
311 pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
312 zlog_debug("Recv IGMP query v%d from %s on %s: size=%d checksum=%x group=%s",
313 query_version, from_str, ifp->name,
314 igmp_msg_len, checksum, group_str);
315 }
316
317 /*
318 RFC 3376: 6.6.2. Querier Election
319
320 When a router receives a query with a lower IP address, it sets
321 the Other-Querier-Present timer to Other Querier Present Interval
322 and ceases to send queries on the network if it was the previously
323 elected querier.
324 */
325 if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
326
327 if (PIM_DEBUG_IGMP_TRACE) {
328 char ifaddr_str[100];
329 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
330 zlog_debug("%s: local address %s (%u) lost querier election to %s (%u)",
331 ifp->name,
332 ifaddr_str, ntohl(igmp->ifaddr.s_addr),
333 from_str, ntohl(from.s_addr));
334 }
335
336 pim_igmp_other_querier_timer_on(igmp);
337 }
338
339 /*
340 RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
341
342 Routers adopt the QRV value from the most recently received Query
343 as their own [Robustness Variable] value, unless that most
344 recently received QRV was zero, in which case the receivers use
345 the default [Robustness Variable] value specified in section 8.1
346 or a statically configured value.
347 */
348 resv_s_qrv = igmp_msg[8];
349 qrv = 7 & resv_s_qrv;
350 igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable;
351
352 /*
353 RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
354
355 Multicast routers that are not the current querier adopt the QQI
356 value from the most recently received Query as their own [Query
357 Interval] value, unless that most recently received QQI was zero,
358 in which case the receiving routers use the default.
359 */
360 if (igmp->t_other_querier_timer) {
361 /* other querier present */
362 uint8_t qqic;
363 uint16_t qqi;
364 qqic = igmp_msg[9];
365 qqi = igmp_msg_decode8to16(qqic);
366 igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval;
367
368 if (PIM_DEBUG_IGMP_TRACE) {
369 char ifaddr_str[100];
370 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
371 zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)",
372 ifaddr_str,
373 qqi ? "recv-non-default" : "default",
374 igmp->querier_query_interval,
375 qqic,
376 from_str);
377 }
378 }
379
380 /*
381 RFC 3376: 6.6.1. Timer Updates
382
383 When a router sends or receives a query with a clear Suppress
384 Router-Side Processing flag, it must update its timers to reflect
385 the correct timeout values for the group or sources being queried.
386
387 General queries don't trigger timer update.
388 */
389 s_flag = (1 << 3) & resv_s_qrv;
390 if (!s_flag) {
391 /* s_flag is clear */
392
393 if (PIM_INADDR_IS_ANY(group_addr)) {
394 /* this is a general query */
395
396 /* log that general query should have the s_flag set */
Everton Marquesd12beab2009-08-12 10:52:22 -0300397 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 -0300398 query_version, from_str, ifp->name);
399 }
400 else {
401 struct igmp_group *group;
402
403 /* this is a non-general query: perform timer updates */
404
405 group = find_group_by_addr(igmp, group_addr);
406 if (group) {
407 int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET));
408
409 /*
410 RFC 3376: 6.6.1. Timer Updates
411 Query Q(G,A): Source Timer for sources in A are lowered to LMQT
412 Query Q(G): Group Timer is lowered to LMQT
413 */
414 if (recv_num_sources < 1) {
415 /* Query Q(G): Group Timer is lowered to LMQT */
416
417 igmp_group_timer_lower_to_lmqt(group);
418 }
419 else {
420 /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */
421
422 /* Scan sources in query and lower their timers to LMQT */
423 struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET);
Leonard Herve942b0fd2009-08-14 15:49:06 +0200424 for (i = 0; i < recv_num_sources; ++i) {
Everton Marquesd4595862014-02-13 19:50:30 -0200425 //struct in_addr src_addr = sources[i];
426 //struct igmp_source *src = igmp_find_source_by_addr(group, src_addr);
427 struct in_addr src_addr;
428 struct igmp_source *src;
429 memcpy(&src_addr, sources + i, sizeof(struct in_addr));
430 src = igmp_find_source_by_addr(group, src_addr);
Everton Marques871dbcf2009-08-11 15:43:05 -0300431 if (src) {
432 igmp_source_timer_lower_to_lmqt(src);
433 }
434 }
435 }
436
437 }
438 else {
439 char group_str[100];
440 pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
441 zlog_warn("IGMP query v%d from %s on %s: could not find group %s for timer update",
442 query_version, from_str, ifp->name, group_str);
443 }
444 }
445 } /* s_flag is clear: timer updates */
446
447 return 0;
448}
449
450static int igmp_v3_report(struct igmp_sock *igmp,
451 struct in_addr from, const char *from_str,
452 char *igmp_msg, int igmp_msg_len)
453{
454 uint16_t recv_checksum;
455 uint16_t checksum;
456 int num_groups;
457 uint8_t *group_record;
458 uint8_t *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len;
459 struct interface *ifp = igmp->interface;
Leonard Herve942b0fd2009-08-14 15:49:06 +0200460 int i;
Everton Marques871dbcf2009-08-11 15:43:05 -0300461
462 if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) {
463 zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d",
464 from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE);
465 return -1;
466 }
467
468 recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
469
470 /* for computing checksum */
471 *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
472
473 checksum = pim_inet_checksum(igmp_msg, igmp_msg_len);
474 if (checksum != recv_checksum) {
475 zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x",
476 from_str, ifp->name, recv_checksum, checksum);
477 return -1;
478 }
479
480 num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET));
481 if (num_groups < 1) {
482 zlog_warn("Recv IGMP report v3 from %s on %s: missing group records",
483 from_str, ifp->name);
484 return -1;
485 }
486
487 if (PIM_DEBUG_IGMP_PACKETS) {
488 zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d",
489 from_str, ifp->name, igmp_msg_len, checksum, num_groups);
490 }
491
492 group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
493
494 /* Scan groups */
Leonard Herve942b0fd2009-08-14 15:49:06 +0200495 for (i = 0; i < num_groups; ++i) {
Everton Marques871dbcf2009-08-11 15:43:05 -0300496 struct in_addr rec_group;
497 uint8_t *sources;
498 uint8_t *src;
499 int rec_type;
500 int rec_auxdatalen;
501 int rec_num_sources;
502 int j;
503
504 if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) {
505 zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end",
506 from_str, ifp->name);
507 return -1;
508 }
509
510 rec_type = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET];
511 rec_auxdatalen = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET];
512 rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET));
513
Everton Marquesd4595862014-02-13 19:50:30 -0200514 //rec_group = *(struct in_addr *)(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET);
515 memcpy(&rec_group, group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, sizeof(struct in_addr));
Everton Marques871dbcf2009-08-11 15:43:05 -0300516
517 if (PIM_DEBUG_IGMP_PACKETS) {
518 zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s",
519 from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group));
520 }
521
522 /* Scan sources */
523
524 sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET;
525
526 for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
527
528 if ((src + 4) > report_pastend) {
529 zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end",
530 from_str, ifp->name);
531 return -1;
532 }
533
534 if (PIM_DEBUG_IGMP_PACKETS) {
535 char src_str[200];
536
537 if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str)))
538 sprintf(src_str, "<source?>");
539
540 zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s",
541 from_str, ifp->name, i, inet_ntoa(rec_group), src_str);
542 }
543 } /* for (sources) */
544
545 switch (rec_type) {
546 case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE:
547 igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
548 break;
549 case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE:
550 igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
551 break;
552 case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE:
553 igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
554 break;
555 case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE:
556 igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
557 break;
558 case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES:
559 igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
560 break;
561 case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES:
562 igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
563 break;
564 default:
565 zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d",
566 from_str, ifp->name, rec_type);
567 }
568
569 group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2);
570
571 } /* for (group records) */
572
573 return 0;
574}
575
576static void on_trace(const char *label,
577 struct interface *ifp, struct in_addr from)
578{
579 if (PIM_DEBUG_IGMP_TRACE) {
580 char from_str[100];
581 pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
582 zlog_debug("%s: from %s on %s",
583 label, from_str, ifp->name);
584 }
585}
586
587static int igmp_v2_report(struct igmp_sock *igmp,
588 struct in_addr from, const char *from_str,
589 char *igmp_msg, int igmp_msg_len)
590{
591 struct interface *ifp = igmp->interface;
592 struct igmp_group *group;
593 struct in_addr group_addr;
594
595 on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
596
597 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
598 zlog_warn("Recv IGMP report v2 from %s on %s: size=%d other than correct=%d",
599 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
600 return -1;
601 }
602
603 if (PIM_DEBUG_IGMP_TRACE) {
604 zlog_warn("%s %s: FIXME WRITEME",
605 __FILE__, __PRETTY_FUNCTION__);
606 }
607
Everton Marquesd4595862014-02-13 19:50:30 -0200608 //group_addr = *(struct in_addr *)(igmp_msg + 4);
609 memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
Everton Marques871dbcf2009-08-11 15:43:05 -0300610
611 /* non-existant group is created as INCLUDE {empty} */
612 group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
613 if (!group) {
614 return -1;
615 }
616
617 group->last_igmp_v2_report_dsec = pim_time_monotonic_dsec();
618
619 return 0;
620}
621
622static int igmp_v2_leave(struct igmp_sock *igmp,
623 struct in_addr from, const char *from_str,
624 char *igmp_msg, int igmp_msg_len)
625{
626 struct interface *ifp = igmp->interface;
627
628 on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
629
630 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
631 zlog_warn("Recv IGMP leave v2 from %s on %s: size=%d other than correct=%d",
632 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
633 return -1;
634 }
635
636 if (PIM_DEBUG_IGMP_TRACE) {
637 zlog_warn("%s %s: FIXME WRITEME",
638 __FILE__, __PRETTY_FUNCTION__);
639 }
640
641 return 0;
642}
643
644static int igmp_v1_report(struct igmp_sock *igmp,
645 struct in_addr from, const char *from_str,
646 char *igmp_msg, int igmp_msg_len)
647{
648 struct interface *ifp = igmp->interface;
649 struct igmp_group *group;
650 struct in_addr group_addr;
651
652 on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
653
654 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
655 zlog_warn("Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
656 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
657 return -1;
658 }
659
660 if (PIM_DEBUG_IGMP_TRACE) {
661 zlog_warn("%s %s: FIXME WRITEME",
662 __FILE__, __PRETTY_FUNCTION__);
663 }
664
Everton Marquesd4595862014-02-13 19:50:30 -0200665 //group_addr = *(struct in_addr *)(igmp_msg + 4);
666 memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
Everton Marques871dbcf2009-08-11 15:43:05 -0300667
668 /* non-existant group is created as INCLUDE {empty} */
669 group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
670 if (!group) {
671 return -1;
672 }
673
674 group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec();
675
676 return 0;
677}
678
679int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len)
680{
681 struct ip *ip_hdr;
682 size_t ip_hlen; /* ip header length in bytes */
683 char *igmp_msg;
684 int igmp_msg_len;
685 int msg_type;
686 char from_str[100];
687 char to_str[100];
688
689 if (len < sizeof(*ip_hdr)) {
David Lamparter5c697982012-02-16 04:47:56 +0100690 zlog_warn("IGMP packet size=%zu shorter than minimum=%zu",
Everton Marques871dbcf2009-08-11 15:43:05 -0300691 len, sizeof(*ip_hdr));
692 return -1;
693 }
694
695 ip_hdr = (struct ip *) buf;
696
697 pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str , sizeof(from_str));
698 pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str , sizeof(to_str));
699
700 ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
701
702 if (PIM_DEBUG_IGMP_PACKETS) {
David Lamparter5c697982012-02-16 04:47:56 +0100703 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 -0300704 from_str, to_str, igmp->interface->name, len, ip_hlen, ip_hdr->ip_p);
705 }
706
707 if (ip_hdr->ip_p != PIM_IP_PROTO_IGMP) {
708 zlog_warn("IP packet protocol=%d is not IGMP=%d",
709 ip_hdr->ip_p, PIM_IP_PROTO_IGMP);
710 return -1;
711 }
712
713 if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
David Lamparter5c697982012-02-16 04:47:56 +0100714 zlog_warn("IP packet header size=%zu shorter than minimum=%d",
Everton Marques871dbcf2009-08-11 15:43:05 -0300715 ip_hlen, PIM_IP_HEADER_MIN_LEN);
716 return -1;
717 }
718 if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
David Lamparter5c697982012-02-16 04:47:56 +0100719 zlog_warn("IP packet header size=%zu greater than maximum=%d",
Everton Marques871dbcf2009-08-11 15:43:05 -0300720 ip_hlen, PIM_IP_HEADER_MAX_LEN);
721 return -1;
722 }
723
724 igmp_msg = buf + ip_hlen;
725 msg_type = *igmp_msg;
726 igmp_msg_len = len - ip_hlen;
727
728 if (PIM_DEBUG_IGMP_PACKETS) {
729 zlog_debug("Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
730 from_str, to_str, igmp->interface->name, ip_hdr->ip_ttl, msg_type,
731 igmp_msg_len);
732 }
733
734 if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
735 zlog_warn("IGMP message size=%d shorter than minimum=%d",
736 igmp_msg_len, PIM_IGMP_MIN_LEN);
737 return -1;
738 }
739
740 switch (msg_type) {
741 case PIM_IGMP_MEMBERSHIP_QUERY:
742 {
743 int max_resp_code = igmp_msg[1];
744 int query_version;
745
746 /*
747 RFC 3376: 7.1. Query Version Distinctions
748 IGMPv1 Query: length = 8 octets AND Max Resp Code field is zero
749 IGMPv2 Query: length = 8 octets AND Max Resp Code field is non-zero
750 IGMPv3 Query: length >= 12 octets
751 */
752
753 if (igmp_msg_len == 8) {
754 query_version = max_resp_code ? 2 : 1;
755 }
756 else if (igmp_msg_len >= 12) {
757 query_version = 3;
758 }
759 else {
760 zlog_warn("Unknown IGMP query version");
761 return -1;
762 }
763
764 return recv_igmp_query(igmp, query_version, max_resp_code,
765 ip_hdr->ip_src, from_str,
766 igmp_msg, igmp_msg_len);
767 }
768
769 case PIM_IGMP_V3_MEMBERSHIP_REPORT:
770 return igmp_v3_report(igmp, ip_hdr->ip_src, from_str,
771 igmp_msg, igmp_msg_len);
772
773 case PIM_IGMP_V2_MEMBERSHIP_REPORT:
774 return igmp_v2_report(igmp, ip_hdr->ip_src, from_str,
775 igmp_msg, igmp_msg_len);
776
777 case PIM_IGMP_V1_MEMBERSHIP_REPORT:
778 return igmp_v1_report(igmp, ip_hdr->ip_src, from_str,
779 igmp_msg, igmp_msg_len);
780
781 case PIM_IGMP_V2_LEAVE_GROUP:
782 return igmp_v2_leave(igmp, ip_hdr->ip_src, from_str,
783 igmp_msg, igmp_msg_len);
784 }
785
786 zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
787
788 return -1;
789}
790
791static int pim_igmp_general_query(struct thread *t);
792
793void pim_igmp_general_query_on(struct igmp_sock *igmp)
794{
795 struct pim_interface *pim_ifp;
796 int startup_mode;
797 int query_interval;
798
799 zassert(igmp);
800 zassert(igmp->interface);
801
802 /*
803 Since this socket is starting as querier,
804 there should not exist a timer for other-querier-present.
805 */
806 zassert(!igmp->t_other_querier_timer);
807 pim_ifp = igmp->interface->info;
808 zassert(pim_ifp);
809
810 /*
811 RFC 3376: 8.6. Startup Query Interval
812
813 The Startup Query Interval is the interval between General Queries
814 sent by a Querier on startup. Default: 1/4 the Query Interval.
815 */
816 startup_mode = igmp->startup_query_count > 0;
817 if (startup_mode) {
818 --igmp->startup_query_count;
819
820 /* query_interval = pim_ifp->igmp_default_query_interval >> 2; */
821 query_interval = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
822 }
823 else {
824 query_interval = igmp->querier_query_interval;
825 }
826
827 if (PIM_DEBUG_IGMP_TRACE) {
828 char ifaddr_str[100];
829 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
830 zlog_debug("Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
831 ifaddr_str,
832 query_interval,
833 startup_mode ? "startup" : "non-startup",
834 igmp->fd);
835 }
836 igmp->t_igmp_query_timer = 0;
837 zassert(!igmp->t_igmp_query_timer);
838 THREAD_TIMER_ON(master, igmp->t_igmp_query_timer,
839 pim_igmp_general_query,
840 igmp, query_interval);
841}
842
843void pim_igmp_general_query_off(struct igmp_sock *igmp)
844{
845 zassert(igmp);
846
847 if (PIM_DEBUG_IGMP_TRACE) {
848 if (igmp->t_igmp_query_timer) {
849 char ifaddr_str[100];
850 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
851 zlog_debug("IGMP querier %s fd=%d cancelling query TIMER event on %s",
852 ifaddr_str, igmp->fd, igmp->interface->name);
853 }
854 }
855 THREAD_OFF(igmp->t_igmp_query_timer);
856 zassert(!igmp->t_igmp_query_timer);
857}
858
859/* Issue IGMP general query */
860static int pim_igmp_general_query(struct thread *t)
861{
862 char query_buf[PIM_IGMP_BUFSIZE_WRITE];
863 struct igmp_sock *igmp;
864 struct in_addr dst_addr;
865 struct in_addr group_addr;
866 struct pim_interface *pim_ifp;
867
868 zassert(t);
869
870 igmp = THREAD_ARG(t);
871
872 zassert(igmp);
873 zassert(igmp->interface);
874 zassert(igmp->interface->info);
875
876 pim_ifp = igmp->interface->info;
877
878 /*
879 RFC3376: 4.1.12. IP Destination Addresses for Queries
880
881 In IGMPv3, General Queries are sent with an IP destination address
882 of 224.0.0.1, the all-systems multicast address. Group-Specific
883 and Group-and-Source-Specific Queries are sent with an IP
884 destination address equal to the multicast address of interest.
885 */
886
887 dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
888 group_addr.s_addr = PIM_NET_INADDR_ANY;
889
890 if (PIM_DEBUG_IGMP_TRACE) {
891 char querier_str[100];
892 char dst_str[100];
893 pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
894 sizeof(querier_str));
895 pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
896 zlog_debug("Querier %s issuing IGMP general query to %s on %s",
897 querier_str, dst_str, igmp->interface->name);
898 }
899
900 pim_igmp_send_membership_query(0 /* igmp_group */,
901 igmp->fd,
902 igmp->interface->name,
903 query_buf,
904 sizeof(query_buf),
905 0 /* num_sources */,
906 dst_addr,
907 group_addr,
908 pim_ifp->igmp_query_max_response_time_dsec,
909 1 /* s_flag: always set for general queries */,
910 igmp->querier_robustness_variable,
911 igmp->querier_query_interval);
912
913 pim_igmp_general_query_on(igmp);
914
915 return 0;
916}
917
918static int pim_igmp_read(struct thread *t);
919
920static void igmp_read_on(struct igmp_sock *igmp)
921{
922 zassert(igmp);
923
924 if (PIM_DEBUG_IGMP_TRACE) {
925 zlog_debug("Scheduling READ event on IGMP socket fd=%d",
926 igmp->fd);
927 }
928 igmp->t_igmp_read = 0;
929 zassert(!igmp->t_igmp_read);
930 THREAD_READ_ON(master, igmp->t_igmp_read, pim_igmp_read, igmp, igmp->fd);
931}
932
933static int pim_igmp_read(struct thread *t)
934{
935 struct igmp_sock *igmp;
936 int fd;
937 struct sockaddr_in from;
938 struct sockaddr_in to;
939 socklen_t fromlen = sizeof(from);
940 socklen_t tolen = sizeof(to);
David Lamparterf8cfeb22012-02-16 04:31:08 +0000941 uint8_t buf[PIM_IGMP_BUFSIZE_READ];
Everton Marques871dbcf2009-08-11 15:43:05 -0300942 int len;
943 int ifindex = -1;
944 int result = -1; /* defaults to bad */
945
946 zassert(t);
947
948 igmp = THREAD_ARG(t);
949
950 zassert(igmp);
951
952 fd = THREAD_FD(t);
953
954 zassert(fd == igmp->fd);
955
956 len = pim_socket_recvfromto(fd, buf, sizeof(buf),
957 &from, &fromlen,
958 &to, &tolen,
959 &ifindex);
960 if (len < 0) {
961 zlog_warn("Failure receiving IP IGMP packet on fd=%d: errno=%d: %s",
Everton Marquese96f0af2009-08-11 15:48:02 -0300962 fd, errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -0300963 goto done;
964 }
965
966 if (PIM_DEBUG_IGMP_PACKETS) {
967 char from_str[100];
968 char to_str[100];
969
970 if (!inet_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str)))
971 sprintf(from_str, "<from?>");
972 if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str)))
973 sprintf(to_str, "<to?>");
974
975 zlog_debug("Recv IP IGMP pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)",
976 len, from_str, to_str, fd, ifindex, igmp->interface->ifindex);
977 }
978
979#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
980 /* ifindex sanity check */
981 if (ifindex != (int) igmp->interface->ifindex) {
982 char from_str[100];
983 char to_str[100];
984 struct interface *ifp;
985
986 if (!inet_ntop(AF_INET, &from.sin_addr, from_str , sizeof(from_str)))
987 sprintf(from_str, "<from?>");
988 if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str)))
989 sprintf(to_str, "<to?>");
990
991 ifp = if_lookup_by_index(ifindex);
992 if (ifp) {
993 zassert(ifindex == (int) ifp->ifindex);
994 }
995
996#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
997 zlog_warn("Interface mismatch: recv IGMP pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)",
998 from_str, to_str, fd,
999 ifindex, ifp ? ifp->name : "<if-notfound>",
1000 igmp->interface->ifindex, igmp->interface->name);
1001#endif
1002 goto done;
1003 }
1004#endif
1005
David Lamparterf8cfeb22012-02-16 04:31:08 +00001006 if (pim_igmp_packet(igmp, (char *)buf, len)) {
Everton Marques871dbcf2009-08-11 15:43:05 -03001007 goto done;
1008 }
1009
1010 result = 0; /* good */
1011
1012 done:
1013 igmp_read_on(igmp);
1014
1015 return result;
1016}
1017
1018static void sock_close(struct igmp_sock *igmp)
1019{
1020 pim_igmp_other_querier_timer_off(igmp);
1021 pim_igmp_general_query_off(igmp);
1022
1023 if (PIM_DEBUG_IGMP_TRACE) {
1024 if (igmp->t_igmp_read) {
1025 zlog_debug("Cancelling READ event on IGMP socket %s fd=%d on interface %s",
1026 inet_ntoa(igmp->ifaddr), igmp->fd,
1027 igmp->interface->name);
1028 }
1029 }
1030 THREAD_OFF(igmp->t_igmp_read);
1031 zassert(!igmp->t_igmp_read);
1032
1033 if (close(igmp->fd)) {
1034 zlog_err("Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
1035 inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name,
Everton Marquese96f0af2009-08-11 15:48:02 -03001036 errno, safe_strerror(errno));
Everton Marques871dbcf2009-08-11 15:43:05 -03001037 }
1038
1039 if (PIM_DEBUG_IGMP_TRACE) {
1040 zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
1041 inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name);
1042 }
1043}
1044
1045void igmp_startup_mode_on(struct igmp_sock *igmp)
1046{
1047 struct pim_interface *pim_ifp;
1048
1049 pim_ifp = igmp->interface->info;
1050
1051 /*
1052 RFC 3376: 8.7. Startup Query Count
1053
1054 The Startup Query Count is the number of Queries sent out on
1055 startup, separated by the Startup Query Interval. Default: the
1056 Robustness Variable.
1057 */
1058 igmp->startup_query_count = igmp->querier_robustness_variable;
1059
1060 /*
1061 Since we're (re)starting, reset QQI to default Query Interval
1062 */
1063 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
1064}
1065
1066static void igmp_group_free(struct igmp_group *group)
1067{
1068 zassert(!group->t_group_query_retransmit_timer);
1069 zassert(!group->t_group_timer);
1070 zassert(group->group_source_list);
1071 zassert(!listcount(group->group_source_list));
1072
1073 list_free(group->group_source_list);
1074
1075 XFREE(MTYPE_PIM_IGMP_GROUP, group);
1076}
1077
1078static void igmp_group_delete(struct igmp_group *group)
1079{
1080 struct listnode *src_node;
1081 struct listnode *src_nextnode;
1082 struct igmp_source *src;
1083
1084 if (PIM_DEBUG_IGMP_TRACE) {
1085 char group_str[100];
1086 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1087 zlog_debug("Deleting IGMP group %s from socket %d interface %s",
1088 group_str,
1089 group->group_igmp_sock->fd,
1090 group->group_igmp_sock->interface->name);
1091 }
1092
1093 for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, src)) {
1094 igmp_source_delete(src);
1095 }
1096
1097 if (group->t_group_query_retransmit_timer) {
1098 THREAD_OFF(group->t_group_query_retransmit_timer);
1099 zassert(!group->t_group_query_retransmit_timer);
1100 }
1101
1102 group_timer_off(group);
1103 listnode_delete(group->group_igmp_sock->igmp_group_list, group);
1104 igmp_group_free(group);
1105}
1106
1107void igmp_group_delete_empty_include(struct igmp_group *group)
1108{
1109 zassert(!group->group_filtermode_isexcl);
1110 zassert(!listcount(group->group_source_list));
1111
1112 igmp_group_delete(group);
1113}
1114
1115void igmp_sock_free(struct igmp_sock *igmp)
1116{
1117 zassert(!igmp->t_igmp_read);
1118 zassert(!igmp->t_igmp_query_timer);
1119 zassert(!igmp->t_other_querier_timer);
1120 zassert(igmp->igmp_group_list);
1121 zassert(!listcount(igmp->igmp_group_list));
1122
1123 list_free(igmp->igmp_group_list);
1124
1125 XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
1126}
1127
1128void igmp_sock_delete(struct igmp_sock *igmp)
1129{
1130 struct pim_interface *pim_ifp;
1131 struct listnode *grp_node;
1132 struct listnode *grp_nextnode;
1133 struct igmp_group *grp;
1134
1135 for (ALL_LIST_ELEMENTS(igmp->igmp_group_list, grp_node, grp_nextnode, grp)) {
1136 igmp_group_delete(grp);
1137 }
1138
1139 sock_close(igmp);
1140
1141 pim_ifp = igmp->interface->info;
1142
1143 listnode_delete(pim_ifp->igmp_socket_list, igmp);
1144
1145 igmp_sock_free(igmp);
1146}
1147
1148static struct igmp_sock *igmp_sock_new(int fd,
1149 struct in_addr ifaddr,
1150 struct interface *ifp)
1151{
1152 struct pim_interface *pim_ifp;
1153 struct igmp_sock *igmp;
1154
1155 pim_ifp = ifp->info;
1156
1157 if (PIM_DEBUG_IGMP_TRACE) {
1158 zlog_debug("Creating IGMP socket fd=%d for address %s on interface %s",
1159 fd, inet_ntoa(ifaddr), ifp->name);
1160 }
1161
1162 igmp = XMALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
1163 if (!igmp) {
1164 zlog_warn("%s %s: XMALLOC() failure",
1165 __FILE__, __PRETTY_FUNCTION__);
1166 return 0;
1167 }
1168
1169 igmp->igmp_group_list = list_new();
1170 if (!igmp->igmp_group_list) {
1171 zlog_err("%s %s: failure: igmp_group_list = list_new()",
1172 __FILE__, __PRETTY_FUNCTION__);
1173 return 0;
1174 }
1175 igmp->igmp_group_list->del = (void (*)(void *)) igmp_group_free;
1176
1177 igmp->fd = fd;
1178 igmp->interface = ifp;
1179 igmp->ifaddr = ifaddr;
1180 igmp->t_igmp_read = 0;
1181 igmp->t_igmp_query_timer = 0;
1182 igmp->t_other_querier_timer = 0; /* no other querier present */
1183 igmp->querier_robustness_variable = pim_ifp->igmp_default_robustness_variable;
1184 igmp->sock_creation = pim_time_monotonic_sec();
1185
1186 /*
1187 igmp_startup_mode_on() will reset QQI:
1188
1189 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
1190 */
1191 igmp_startup_mode_on(igmp);
1192
1193 igmp_read_on(igmp);
1194 pim_igmp_general_query_on(igmp);
1195
1196 return igmp;
1197}
1198
1199struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
1200 struct in_addr ifaddr,
1201 struct interface *ifp)
1202{
1203 struct pim_interface *pim_ifp;
1204 struct igmp_sock *igmp;
1205 int fd;
1206
1207 pim_ifp = ifp->info;
1208
1209 fd = igmp_sock_open(ifaddr, ifp->ifindex, pim_ifp->options);
1210 if (fd < 0) {
1211 zlog_warn("Could not open IGMP socket for %s on %s",
1212 inet_ntoa(ifaddr), ifp->name);
1213 return 0;
1214 }
1215
1216 igmp = igmp_sock_new(fd, ifaddr, ifp);
1217 if (!igmp) {
1218 zlog_err("%s %s: igmp_sock_new() failure",
1219 __FILE__, __PRETTY_FUNCTION__);
1220 close(fd);
1221 return 0;
1222 }
1223
1224 listnode_add(igmp_sock_list, igmp);
1225
1226#ifdef IGMP_SOCK_DUMP
1227 igmp_sock_dump(igmp_sock_array);
1228#endif
1229
1230 return igmp;
1231}
1232
1233/*
1234 RFC 3376: 6.5. Switching Router Filter-Modes
1235
1236 When a router's filter-mode for a group is EXCLUDE and the group
1237 timer expires, the router filter-mode for the group transitions to
1238 INCLUDE.
1239
1240 A router uses source records with running source timers as its state
1241 for the switch to a filter-mode of INCLUDE. If there are any source
1242 records with source timers greater than zero (i.e., requested to be
1243 forwarded), a router switches to filter-mode of INCLUDE using those
1244 source records. Source records whose timers are zero (from the
1245 previous EXCLUDE mode) are deleted.
1246 */
1247static int igmp_group_timer(struct thread *t)
1248{
1249 struct igmp_group *group;
1250
1251 zassert(t);
1252 group = THREAD_ARG(t);
1253 zassert(group);
1254
1255 if (PIM_DEBUG_IGMP_TRACE) {
1256 char group_str[100];
1257 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1258 zlog_debug("%s: Timer for group %s on interface %s",
1259 __PRETTY_FUNCTION__,
1260 group_str, group->group_igmp_sock->interface->name);
1261 }
1262
1263 zassert(group->group_filtermode_isexcl);
1264
1265 group->t_group_timer = 0;
1266 group->group_filtermode_isexcl = 0;
1267
1268 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1269 igmp_anysource_forward_stop(group);
1270
1271 igmp_source_delete_expired(group->group_source_list);
1272
1273 zassert(!group->t_group_timer);
1274 zassert(!group->group_filtermode_isexcl);
1275
1276 /*
1277 RFC 3376: 6.2.2. Definition of Group Timers
1278
1279 If there are no more source records for the group, delete group
1280 record.
1281 */
1282 if (listcount(group->group_source_list) < 1) {
1283 igmp_group_delete_empty_include(group);
1284 }
1285
1286 return 0;
1287}
1288
1289static void group_timer_off(struct igmp_group *group)
1290{
1291 if (!group->t_group_timer)
1292 return;
1293
1294 if (PIM_DEBUG_IGMP_TRACE) {
1295 char group_str[100];
1296 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1297 zlog_debug("Cancelling TIMER event for group %s on %s",
1298 group_str, group->group_igmp_sock->interface->name);
1299 }
1300
1301 THREAD_OFF(group->t_group_timer);
1302 zassert(!group->t_group_timer);
1303}
1304
1305void igmp_group_timer_on(struct igmp_group *group,
1306 long interval_msec, const char *ifname)
1307{
1308 group_timer_off(group);
1309
1310 if (PIM_DEBUG_IGMP_EVENTS) {
1311 char group_str[100];
1312 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1313 zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s on %s",
1314 interval_msec / 1000,
1315 interval_msec % 1000,
1316 group_str, ifname);
1317 }
1318
1319 /*
1320 RFC 3376: 6.2.2. Definition of Group Timers
1321
1322 The group timer is only used when a group is in EXCLUDE mode and
1323 it represents the time for the *filter-mode* of the group to
1324 expire and switch to INCLUDE mode.
1325 */
1326 zassert(group->group_filtermode_isexcl);
1327
1328 THREAD_TIMER_MSEC_ON(master, group->t_group_timer,
1329 igmp_group_timer,
1330 group, interval_msec);
1331}
1332
1333static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
1334 struct in_addr group_addr)
1335{
1336 struct igmp_group *group;
1337 struct listnode *node;
1338
1339 for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, node, group))
1340 if (group_addr.s_addr == group->group_addr.s_addr)
1341 return group;
1342
1343 return 0;
1344}
1345
1346struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
1347 struct in_addr group_addr,
1348 const char *ifname)
1349{
1350 struct igmp_group *group;
1351
1352 group = find_group_by_addr(igmp, group_addr);
1353 if (group) {
1354 return group;
1355 }
1356
1357 /*
1358 Non-existant group is created as INCLUDE {empty}:
1359
1360 RFC 3376 - 5.1. Action on Change of Interface State
1361
1362 If no interface state existed for that multicast address before
1363 the change (i.e., the change consisted of creating a new
1364 per-interface record), or if no state exists after the change
1365 (i.e., the change consisted of deleting a per-interface record),
1366 then the "non-existent" state is considered to have a filter mode
1367 of INCLUDE and an empty source list.
1368 */
1369
1370 group = XMALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
1371 if (!group) {
1372 zlog_warn("%s %s: XMALLOC() failure",
1373 __FILE__, __PRETTY_FUNCTION__);
1374 return 0; /* error, not found, could not create */
1375 }
1376
1377 group->group_source_list = list_new();
1378 if (!group->group_source_list) {
1379 zlog_warn("%s %s: list_new() failure",
1380 __FILE__, __PRETTY_FUNCTION__);
1381 XFREE(MTYPE_PIM_IGMP_GROUP, group); /* discard group */
1382 return 0; /* error, not found, could not initialize */
1383 }
1384 group->group_source_list->del = (void (*)(void *)) igmp_source_free;
1385
1386 group->t_group_timer = 0;
1387 group->t_group_query_retransmit_timer = 0;
1388 group->group_specific_query_retransmit_count = 0;
1389 group->group_addr = group_addr;
1390 group->group_igmp_sock = igmp;
1391 group->last_igmp_v1_report_dsec = -1;
1392 group->last_igmp_v2_report_dsec = -1;
1393 group->group_creation = pim_time_monotonic_sec();
1394
1395 /* initialize new group as INCLUDE {empty} */
1396 group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
1397
1398 listnode_add(igmp->igmp_group_list, group);
1399
1400 if (PIM_DEBUG_IGMP_TRACE) {
1401 char group_str[100];
1402 pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
1403 zlog_debug("Creating new IGMP group %s on socket %d interface %s",
1404 group_str, group->group_igmp_sock->fd, ifname);
1405 }
1406
1407 /*
1408 RFC 3376: 6.2.2. Definition of Group Timers
1409
1410 The group timer is only used when a group is in EXCLUDE mode and
1411 it represents the time for the *filter-mode* of the group to
1412 expire and switch to INCLUDE mode.
1413 */
1414 zassert(!group->group_filtermode_isexcl); /* INCLUDE mode */
1415 zassert(!group->t_group_timer); /* group timer == 0 */
1416
1417 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1418 igmp_anysource_forward_stop(group);
1419
1420 return group;
1421}