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