]> git.proxmox.com Git - mirror_frr.git/blob - pimd/pim_igmp.c
*: Add camelCase JSON keys in addition to PascalCase
[mirror_frr.git] / pimd / pim_igmp.c
1 /*
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 along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <zebra.h>
21
22 #include "memory.h"
23 #include "prefix.h"
24 #include "if.h"
25 #include "hash.h"
26 #include "jhash.h"
27 #include "lib_errors.h"
28
29 #include "pimd.h"
30 #include "pim_igmp.h"
31 #include "pim_igmpv2.h"
32 #include "pim_igmpv3.h"
33 #include "pim_igmp_mtrace.h"
34 #include "pim_iface.h"
35 #include "pim_sock.h"
36 #include "pim_mroute.h"
37 #include "pim_str.h"
38 #include "pim_util.h"
39 #include "pim_time.h"
40 #include "pim_zebra.h"
41
42 static void group_timer_off(struct gm_group *group);
43 static int pim_igmp_general_query(struct thread *t);
44
45 /* This socket is used for TXing IGMP packets only, IGMP RX happens
46 * in pim_mroute_msg()
47 */
48 static int igmp_sock_open(struct in_addr ifaddr, struct interface *ifp,
49 uint32_t pim_options)
50 {
51 int fd;
52 int join = 0;
53 struct in_addr group;
54
55 fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifp, 1);
56
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, ifp->ifindex))
63 ++join;
64 } else {
65 zlog_warn(
66 "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s",
67 __FILE__, __func__, fd, &ifaddr,
68 PIM_ALL_ROUTERS, errno, safe_strerror(errno));
69 }
70 }
71
72 /*
73 IGMP routers periodically send IGMP general queries to
74 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, ifp->ifindex))
79 ++join;
80 } else {
81 zlog_warn(
82 "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s",
83 __FILE__, __func__, fd, &ifaddr,
84 PIM_ALL_SYSTEMS, errno, safe_strerror(errno));
85 }
86
87 if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
88 if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex)) {
89 ++join;
90 }
91 } else {
92 zlog_warn(
93 "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s",
94 __FILE__, __func__, fd, &ifaddr,
95 PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno));
96 }
97
98 if (!join) {
99 flog_err_sys(
100 EC_LIB_SOCKET,
101 "IGMP socket fd=%d could not join any group on interface address %pI4",
102 fd, &ifaddr);
103 close(fd);
104 fd = -1;
105 }
106
107 return fd;
108 }
109
110 #undef IGMP_SOCK_DUMP
111
112 #ifdef IGMP_SOCK_DUMP
113 static void igmp_sock_dump(array_t *igmp_sock_array)
114 {
115 int size = array_size(igmp_sock_array);
116 for (int i = 0; i < size; ++i) {
117
118 struct gm_sock *igmp = array_get(igmp_sock_array, i);
119
120 zlog_debug("%s %s: [%d/%d] igmp_addr=%pI4 fd=%d", __FILE__,
121 __func__, i, size, &igmp->ifaddr,
122 igmp->fd);
123 }
124 }
125 #endif
126
127 struct gm_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
128 struct in_addr ifaddr)
129 {
130 struct listnode *sock_node;
131 struct gm_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 NULL;
142 }
143
144 static int pim_igmp_other_querier_expire(struct thread *t)
145 {
146 struct gm_sock *igmp;
147
148 igmp = THREAD_ARG(t);
149
150 assert(!igmp->t_igmp_query_timer);
151
152 if (PIM_DEBUG_IGMP_TRACE) {
153 char ifaddr_str[INET_ADDRSTRLEN];
154 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
155 sizeof(ifaddr_str));
156 zlog_debug("%s: Querier %s resuming", __func__, ifaddr_str);
157 }
158 /* Mark the interface address as querier address */
159 igmp->querier_addr = igmp->ifaddr;
160
161 /*
162 We are the current querier, then
163 re-start sending general queries.
164 RFC 2236 - sec 7 Other Querier
165 present timer expired (Send General
166 Query, Set Gen. Query. timer)
167 */
168 pim_igmp_general_query(t);
169
170 return 0;
171 }
172
173 void pim_igmp_other_querier_timer_on(struct gm_sock *igmp)
174 {
175 long other_querier_present_interval_msec;
176 struct pim_interface *pim_ifp;
177
178 assert(igmp);
179 assert(igmp->interface);
180 assert(igmp->interface->info);
181
182 pim_ifp = igmp->interface->info;
183
184 if (igmp->t_other_querier_timer) {
185 /*
186 There is other querier present already,
187 then reset the other-querier-present timer.
188 */
189
190 if (PIM_DEBUG_IGMP_TRACE) {
191 char ifaddr_str[INET_ADDRSTRLEN];
192 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
193 sizeof(ifaddr_str));
194 zlog_debug(
195 "Querier %s resetting TIMER event for Other-Querier-Present",
196 ifaddr_str);
197 }
198 THREAD_OFF(igmp->t_other_querier_timer);
199 } else {
200 /*
201 We are the current querier, then stop sending general queries:
202 igmp->t_igmp_query_timer = NULL;
203 */
204 pim_igmp_general_query_off(igmp);
205 }
206
207 /*
208 Since this socket is starting the other-querier-present timer,
209 there should not be periodic query timer for this socket.
210 */
211 assert(!igmp->t_igmp_query_timer);
212
213 /*
214 RFC 3376: 8.5. Other Querier Present Interval
215
216 The Other Querier Present Interval is the length of time that must
217 pass before a multicast router decides that there is no longer
218 another multicast router which should be the querier. This value
219 MUST be ((the Robustness Variable) times (the Query Interval)) plus
220 (one half of one Query Response Interval).
221
222 other_querier_present_interval_msec = \
223 igmp->querier_robustness_variable * \
224 1000 * igmp->querier_query_interval + \
225 100 * (pim_ifp->query_max_response_time_dsec >> 1);
226 */
227 other_querier_present_interval_msec = PIM_IGMP_OQPI_MSEC(
228 igmp->querier_robustness_variable, igmp->querier_query_interval,
229 pim_ifp->gm_query_max_response_time_dsec);
230
231 if (PIM_DEBUG_IGMP_TRACE) {
232 char ifaddr_str[INET_ADDRSTRLEN];
233 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
234 sizeof(ifaddr_str));
235 zlog_debug(
236 "Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
237 ifaddr_str, other_querier_present_interval_msec / 1000,
238 other_querier_present_interval_msec % 1000);
239 }
240
241 thread_add_timer_msec(router->master, pim_igmp_other_querier_expire,
242 igmp, other_querier_present_interval_msec,
243 &igmp->t_other_querier_timer);
244 }
245
246 void pim_igmp_other_querier_timer_off(struct gm_sock *igmp)
247 {
248 assert(igmp);
249
250 if (PIM_DEBUG_IGMP_TRACE) {
251 if (igmp->t_other_querier_timer) {
252 char ifaddr_str[INET_ADDRSTRLEN];
253 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
254 sizeof(ifaddr_str));
255 zlog_debug(
256 "IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
257 ifaddr_str, igmp->fd, igmp->interface->name);
258 }
259 }
260 THREAD_OFF(igmp->t_other_querier_timer);
261 }
262
263 int igmp_validate_checksum(char *igmp_msg, int igmp_msg_len)
264 {
265 uint16_t recv_checksum;
266 uint16_t checksum;
267
268 IGMP_GET_INT16((unsigned char *)(igmp_msg + IGMP_CHECKSUM_OFFSET),
269 recv_checksum);
270
271 /* Clear the checksum field */
272 memset(igmp_msg + IGMP_CHECKSUM_OFFSET, 0, 2);
273
274 checksum = in_cksum(igmp_msg, igmp_msg_len);
275 if (ntohs(checksum) != recv_checksum) {
276 zlog_warn("Invalid checksum received %x, calculated %x",
277 recv_checksum, ntohs(checksum));
278 return -1;
279 }
280
281 return 0;
282 }
283
284 static int igmp_recv_query(struct gm_sock *igmp, int query_version,
285 int max_resp_code, struct in_addr from,
286 const char *from_str, char *igmp_msg,
287 int igmp_msg_len)
288 {
289 struct interface *ifp;
290 struct pim_interface *pim_ifp;
291 struct in_addr group_addr;
292
293 if (igmp->mtrace_only)
294 return 0;
295
296 memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
297
298 ifp = igmp->interface;
299 pim_ifp = ifp->info;
300
301 if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
302 zlog_warn(
303 "Recv IGMP query v%d from %s on %s with invalid checksum",
304 query_version, from_str, ifp->name);
305 return -1;
306 }
307
308 if (!pim_if_connected_to_source(ifp, from)) {
309 if (PIM_DEBUG_IGMP_PACKETS)
310 zlog_debug("Recv IGMP query on interface: %s from a non-connected source: %s",
311 ifp->name, from_str);
312 return 0;
313 }
314
315 if (if_address_is_local(&from, AF_INET, ifp->vrf->vrf_id)) {
316 if (PIM_DEBUG_IGMP_PACKETS)
317 zlog_debug("Recv IGMP query on interface: %s from ourself %s",
318 ifp->name, from_str);
319 return 0;
320 }
321
322 /* Collecting IGMP Rx stats */
323 switch (query_version) {
324 case 1:
325 igmp->rx_stats.query_v1++;
326 break;
327 case 2:
328 igmp->rx_stats.query_v2++;
329 break;
330 case 3:
331 igmp->rx_stats.query_v3++;
332 break;
333 default:
334 igmp->rx_stats.unsupported++;
335 }
336
337 /*
338 * RFC 3376 defines some guidelines on operating in backwards
339 * compatibility with older versions of IGMP but there are some gaps in
340 * the logic:
341 *
342 * - once we drop from say version 3 to version 2 we will never go back
343 * to version 3 even if the node that TXed an IGMP v2 query upgrades
344 * to v3
345 *
346 * - The node with the lowest IP is the querier so we will only know to
347 * drop from v3 to v2 if the node that is the querier is also the one
348 * that is running igmp v2. If a non-querier only supports igmp v2
349 * we will have no way of knowing.
350 *
351 * For now we will simplify things and inform the user that they need to
352 * configure all PIM routers to use the same version of IGMP.
353 */
354 if (query_version != pim_ifp->igmp_version) {
355 zlog_warn(
356 "Recv IGMP query v%d from %s on %s but we are using v%d, please configure all PIM routers on this subnet to use the same IGMP version",
357 query_version, from_str, ifp->name,
358 pim_ifp->igmp_version);
359 return 0;
360 }
361
362 if (PIM_DEBUG_IGMP_PACKETS) {
363 char group_str[INET_ADDRSTRLEN];
364 pim_inet4_dump("<group?>", group_addr, group_str,
365 sizeof(group_str));
366 zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
367 query_version, from_str, ifp->name, group_str);
368 }
369
370 /*
371 RFC 3376: 6.6.2. Querier Election
372
373 When a router receives a query with a lower IP address, it sets
374 the Other-Querier-Present timer to Other Querier Present Interval
375 and ceases to send queries on the network if it was the previously
376 elected querier.
377 */
378 if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
379
380 if (PIM_DEBUG_IGMP_TRACE) {
381 char ifaddr_str[INET_ADDRSTRLEN];
382 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
383 sizeof(ifaddr_str));
384 zlog_debug(
385 "%s: local address %s (%u) lost querier election to %s (%u)",
386 ifp->name, ifaddr_str,
387 ntohl(igmp->ifaddr.s_addr), from_str,
388 ntohl(from.s_addr));
389 }
390 if (ntohl(from.s_addr) < ntohl(igmp->querier_addr.s_addr))
391 igmp->querier_addr.s_addr = from.s_addr;
392
393 pim_igmp_other_querier_timer_on(igmp);
394 }
395
396 /* IGMP version 3 is the only one where we process the RXed query */
397 if (query_version == 3) {
398 igmp_v3_recv_query(igmp, from_str, igmp_msg);
399 }
400
401 return 0;
402 }
403
404 static void on_trace(const char *label, struct interface *ifp,
405 struct in_addr from)
406 {
407 if (PIM_DEBUG_IGMP_TRACE) {
408 char from_str[INET_ADDRSTRLEN];
409 pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
410 zlog_debug("%s: from %s on %s", label, from_str, ifp->name);
411 }
412 }
413
414 static int igmp_v1_recv_report(struct gm_sock *igmp, struct in_addr from,
415 const char *from_str, char *igmp_msg,
416 int igmp_msg_len)
417 {
418 struct interface *ifp = igmp->interface;
419 struct gm_group *group;
420 struct in_addr group_addr;
421
422 on_trace(__func__, igmp->interface, from);
423
424 if (igmp->mtrace_only)
425 return 0;
426
427 if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
428 zlog_warn(
429 "Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
430 from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
431 return -1;
432 }
433
434 if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
435 zlog_warn(
436 "Recv IGMP report v1 from %s on %s with invalid checksum",
437 from_str, ifp->name);
438 return -1;
439 }
440
441 /* Collecting IGMP Rx stats */
442 igmp->rx_stats.report_v1++;
443
444 if (PIM_DEBUG_IGMP_TRACE) {
445 zlog_warn("%s %s: FIXME WRITEME", __FILE__, __func__);
446 }
447
448 memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
449
450 if (pim_is_group_filtered(ifp->info, &group_addr))
451 return -1;
452
453 /* non-existant group is created as INCLUDE {empty} */
454 group = igmp_add_group_by_addr(igmp, group_addr);
455 if (!group) {
456 return -1;
457 }
458
459 group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec();
460
461 return 0;
462 }
463
464 bool pim_igmp_verify_header(struct ip *ip_hdr, size_t len, size_t *hlen)
465 {
466 char *igmp_msg;
467 int igmp_msg_len;
468 int msg_type;
469 size_t ip_hlen; /* ip header length in bytes */
470
471 if (len < sizeof(*ip_hdr)) {
472 zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len,
473 sizeof(*ip_hdr));
474 return false;
475 }
476
477 ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
478 *hlen = ip_hlen;
479
480 if (ip_hlen > len) {
481 zlog_warn(
482 "IGMP packet header claims size %zu, but we only have %zu bytes",
483 ip_hlen, len);
484 return false;
485 }
486
487 igmp_msg = (char *)ip_hdr + ip_hlen;
488 igmp_msg_len = len - ip_hlen;
489 msg_type = *igmp_msg;
490
491 if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
492 zlog_warn("IGMP message size=%d shorter than minimum=%d",
493 igmp_msg_len, PIM_IGMP_MIN_LEN);
494 return false;
495 }
496
497 if ((msg_type != PIM_IGMP_MTRACE_RESPONSE)
498 && (msg_type != PIM_IGMP_MTRACE_QUERY_REQUEST)) {
499 if (ip_hdr->ip_ttl != 1) {
500 zlog_warn(
501 "Recv IGMP packet with invalid ttl=%u, discarding the packet",
502 ip_hdr->ip_ttl);
503 return false;
504 }
505 }
506
507 if ((msg_type == PIM_IGMP_V3_MEMBERSHIP_REPORT)
508 || ((msg_type == PIM_IGMP_MEMBERSHIP_QUERY)
509 && (igmp_msg_len >= IGMP_V3_SOURCES_OFFSET))) {
510 /* All IGMPv3 messages must be received with TOS set to 0xC0*/
511 if (ip_hdr->ip_tos != IPTOS_PREC_INTERNETCONTROL) {
512 zlog_warn("Received IGMP Packet with invalid TOS %u",
513 ip_hdr->ip_tos);
514 return false;
515 }
516 }
517
518 return true;
519 }
520
521 int pim_igmp_packet(struct gm_sock *igmp, char *buf, size_t len)
522 {
523 struct ip *ip_hdr = (struct ip *)buf;
524 size_t ip_hlen; /* ip header length in bytes */
525 char *igmp_msg;
526 int igmp_msg_len;
527 int msg_type;
528 char from_str[INET_ADDRSTRLEN];
529 char to_str[INET_ADDRSTRLEN];
530
531 if (!pim_igmp_verify_header(ip_hdr, len, &ip_hlen))
532 return -1;
533
534 igmp_msg = buf + ip_hlen;
535 igmp_msg_len = len - ip_hlen;
536 msg_type = *igmp_msg;
537
538 pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str, sizeof(from_str));
539 pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str, sizeof(to_str));
540
541 if (PIM_DEBUG_IGMP_PACKETS) {
542 zlog_debug(
543 "Recv IGMP packet from %s to %s on %s: size=%zu ttl=%d msg_type=%d msg_size=%d",
544 from_str, to_str, igmp->interface->name, len, ip_hdr->ip_ttl,
545 msg_type, igmp_msg_len);
546 }
547
548 switch (msg_type) {
549 case PIM_IGMP_MEMBERSHIP_QUERY: {
550 int max_resp_code = igmp_msg[1];
551 int query_version;
552
553 /*
554 RFC 3376: 7.1. Query Version Distinctions
555 IGMPv1 Query: length = 8 octets AND Max Resp Code field is
556 zero
557 IGMPv2 Query: length = 8 octets AND Max Resp Code field is
558 non-zero
559 IGMPv3 Query: length >= 12 octets
560 */
561
562 if (igmp_msg_len == 8) {
563 query_version = max_resp_code ? 2 : 1;
564 } else if (igmp_msg_len >= 12) {
565 query_version = 3;
566 } else {
567 zlog_warn("Unknown IGMP query version");
568 return -1;
569 }
570
571 return igmp_recv_query(igmp, query_version, max_resp_code,
572 ip_hdr->ip_src, from_str, igmp_msg,
573 igmp_msg_len);
574 }
575
576 case PIM_IGMP_V3_MEMBERSHIP_REPORT:
577 return igmp_v3_recv_report(igmp, ip_hdr->ip_src, from_str,
578 igmp_msg, igmp_msg_len);
579
580 case PIM_IGMP_V2_MEMBERSHIP_REPORT:
581 return igmp_v2_recv_report(igmp, ip_hdr->ip_src, from_str,
582 igmp_msg, igmp_msg_len);
583
584 case PIM_IGMP_V1_MEMBERSHIP_REPORT:
585 return igmp_v1_recv_report(igmp, ip_hdr->ip_src, from_str,
586 igmp_msg, igmp_msg_len);
587
588 case PIM_IGMP_V2_LEAVE_GROUP:
589 return igmp_v2_recv_leave(igmp, ip_hdr, from_str, igmp_msg,
590 igmp_msg_len);
591
592 case PIM_IGMP_MTRACE_RESPONSE:
593 return igmp_mtrace_recv_response(igmp, ip_hdr, ip_hdr->ip_src,
594 from_str, igmp_msg,
595 igmp_msg_len);
596 case PIM_IGMP_MTRACE_QUERY_REQUEST:
597 return igmp_mtrace_recv_qry_req(igmp, ip_hdr, ip_hdr->ip_src,
598 from_str, igmp_msg,
599 igmp_msg_len);
600 }
601
602 zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
603
604 /* Collecting IGMP Rx stats */
605 igmp->rx_stats.unsupported++;
606
607 return -1;
608 }
609
610 void pim_igmp_general_query_on(struct gm_sock *igmp)
611 {
612 struct pim_interface *pim_ifp;
613 int startup_mode;
614 int query_interval;
615
616 /*
617 Since this socket is starting as querier,
618 there should not exist a timer for other-querier-present.
619 */
620 assert(!igmp->t_other_querier_timer);
621 pim_ifp = igmp->interface->info;
622 assert(pim_ifp);
623
624 /*
625 RFC 3376: 8.6. Startup Query Interval
626
627 The Startup Query Interval is the interval between General Queries
628 sent by a Querier on startup. Default: 1/4 the Query Interval.
629 The first one should be sent out immediately instead of 125/4
630 seconds from now.
631 */
632 startup_mode = igmp->startup_query_count > 0;
633 if (startup_mode) {
634 /*
635 * If this is the first time we are sending a query on a
636 * newly configured igmp interface send it out in 1 second
637 * just to give the entire world a tiny bit of time to settle
638 * else the query interval is:
639 * query_interval = pim_ifp->gm_default_query_interval >> 2;
640 */
641 if (igmp->startup_query_count ==
642 igmp->querier_robustness_variable)
643 query_interval = 1;
644 else
645 query_interval = PIM_IGMP_SQI(
646 pim_ifp->gm_default_query_interval);
647
648 --igmp->startup_query_count;
649 } else {
650 query_interval = igmp->querier_query_interval;
651 }
652
653 if (PIM_DEBUG_IGMP_TRACE) {
654 char ifaddr_str[INET_ADDRSTRLEN];
655 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
656 sizeof(ifaddr_str));
657 zlog_debug(
658 "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
659 ifaddr_str, query_interval,
660 startup_mode ? "startup" : "non-startup", igmp->fd);
661 }
662 thread_add_timer(router->master, pim_igmp_general_query, igmp,
663 query_interval, &igmp->t_igmp_query_timer);
664 }
665
666 void pim_igmp_general_query_off(struct gm_sock *igmp)
667 {
668 assert(igmp);
669
670 if (PIM_DEBUG_IGMP_TRACE) {
671 if (igmp->t_igmp_query_timer) {
672 char ifaddr_str[INET_ADDRSTRLEN];
673 pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
674 sizeof(ifaddr_str));
675 zlog_debug(
676 "IGMP querier %s fd=%d cancelling query TIMER event on %s",
677 ifaddr_str, igmp->fd, igmp->interface->name);
678 }
679 }
680 THREAD_OFF(igmp->t_igmp_query_timer);
681 }
682
683 /* Issue IGMP general query */
684 static int pim_igmp_general_query(struct thread *t)
685 {
686 struct gm_sock *igmp;
687 struct in_addr dst_addr;
688 struct in_addr group_addr;
689 struct pim_interface *pim_ifp;
690 int query_buf_size;
691
692 igmp = THREAD_ARG(t);
693
694 assert(igmp->interface);
695 assert(igmp->interface->info);
696
697 pim_ifp = igmp->interface->info;
698
699 if (pim_ifp->igmp_version == 3) {
700 query_buf_size = PIM_IGMP_BUFSIZE_WRITE;
701 } else {
702 query_buf_size = IGMP_V12_MSG_SIZE;
703 }
704
705 char query_buf[query_buf_size];
706
707 /*
708 RFC3376: 4.1.12. IP Destination Addresses for Queries
709
710 In IGMPv3, General Queries are sent with an IP destination address
711 of 224.0.0.1, the all-systems multicast address. Group-Specific
712 and Group-and-Source-Specific Queries are sent with an IP
713 destination address equal to the multicast address of interest.
714 */
715
716 dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
717 group_addr.s_addr = PIM_NET_INADDR_ANY;
718
719 if (PIM_DEBUG_IGMP_TRACE) {
720 char querier_str[INET_ADDRSTRLEN];
721 char dst_str[INET_ADDRSTRLEN];
722 pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
723 sizeof(querier_str));
724 pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
725 zlog_debug("Querier %s issuing IGMP general query to %s on %s",
726 querier_str, dst_str, igmp->interface->name);
727 }
728
729 igmp_send_query(pim_ifp->igmp_version, 0 /* igmp_group */, igmp->fd,
730 igmp->interface->name, query_buf, sizeof(query_buf),
731 0 /* num_sources */, dst_addr, group_addr,
732 pim_ifp->gm_query_max_response_time_dsec,
733 1 /* s_flag: always set for general queries */,
734 igmp->querier_robustness_variable,
735 igmp->querier_query_interval);
736
737 pim_igmp_general_query_on(igmp);
738
739 return 0;
740 }
741
742 static void sock_close(struct gm_sock *igmp)
743 {
744 pim_igmp_other_querier_timer_off(igmp);
745 pim_igmp_general_query_off(igmp);
746
747 if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
748 if (igmp->t_igmp_read) {
749 zlog_debug(
750 "Cancelling READ event on IGMP socket %pI4 fd=%d on interface %s",
751 &igmp->ifaddr, igmp->fd,
752 igmp->interface->name);
753 }
754 }
755 THREAD_OFF(igmp->t_igmp_read);
756
757 if (close(igmp->fd)) {
758 flog_err(
759 EC_LIB_SOCKET,
760 "Failure closing IGMP socket %pI4 fd=%d on interface %s: errno=%d: %s",
761 &igmp->ifaddr, igmp->fd,
762 igmp->interface->name, errno, safe_strerror(errno));
763 }
764
765 if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
766 zlog_debug("Deleted IGMP socket %pI4 fd=%d on interface %s",
767 &igmp->ifaddr, igmp->fd,
768 igmp->interface->name);
769 }
770 }
771
772 void igmp_startup_mode_on(struct gm_sock *igmp)
773 {
774 struct pim_interface *pim_ifp;
775
776 pim_ifp = igmp->interface->info;
777
778 /*
779 RFC 3376: 8.7. Startup Query Count
780
781 The Startup Query Count is the number of Queries sent out on
782 startup, separated by the Startup Query Interval. Default: the
783 Robustness Variable.
784 */
785 igmp->startup_query_count = igmp->querier_robustness_variable;
786
787 /*
788 Since we're (re)starting, reset QQI to default Query Interval
789 */
790 igmp->querier_query_interval = pim_ifp->gm_default_query_interval;
791 }
792
793 static void igmp_group_free(struct gm_group *group)
794 {
795 list_delete(&group->group_source_list);
796
797 XFREE(MTYPE_PIM_IGMP_GROUP, group);
798 }
799
800 static void igmp_group_count_incr(struct pim_interface *pim_ifp)
801 {
802 ++pim_ifp->pim->igmp_group_count;
803 if (pim_ifp->pim->igmp_group_count
804 == pim_ifp->pim->igmp_watermark_limit) {
805 zlog_warn(
806 "IGMP group count reached watermark limit: %u(vrf: %s)",
807 pim_ifp->pim->igmp_group_count,
808 VRF_LOGNAME(pim_ifp->pim->vrf));
809 }
810 }
811
812 static void igmp_group_count_decr(struct pim_interface *pim_ifp)
813 {
814 if (pim_ifp->pim->igmp_group_count == 0) {
815 zlog_warn("Cannot decrement igmp group count below 0(vrf: %s)",
816 VRF_LOGNAME(pim_ifp->pim->vrf));
817 return;
818 }
819
820 --pim_ifp->pim->igmp_group_count;
821 }
822
823 void igmp_group_delete(struct gm_group *group)
824 {
825 struct listnode *src_node;
826 struct listnode *src_nextnode;
827 struct gm_source *src;
828 struct pim_interface *pim_ifp = group->interface->info;
829
830 if (PIM_DEBUG_IGMP_TRACE) {
831 char group_str[INET_ADDRSTRLEN];
832 pim_inet4_dump("<group?>", group->group_addr, group_str,
833 sizeof(group_str));
834 zlog_debug("Deleting IGMP group %s from interface %s",
835 group_str, group->interface->name);
836 }
837
838 for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode,
839 src)) {
840 igmp_source_delete(src);
841 }
842
843 THREAD_OFF(group->t_group_query_retransmit_timer);
844
845 group_timer_off(group);
846 igmp_group_count_decr(pim_ifp);
847 listnode_delete(pim_ifp->gm_group_list, group);
848 hash_release(pim_ifp->gm_group_hash, group);
849
850 igmp_group_free(group);
851 }
852
853 void igmp_group_delete_empty_include(struct gm_group *group)
854 {
855 assert(!group->group_filtermode_isexcl);
856 assert(!listcount(group->group_source_list));
857
858 igmp_group_delete(group);
859 }
860
861 void igmp_sock_free(struct gm_sock *igmp)
862 {
863 assert(!igmp->t_igmp_read);
864 assert(!igmp->t_igmp_query_timer);
865 assert(!igmp->t_other_querier_timer);
866
867 XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
868 }
869
870 void igmp_sock_delete(struct gm_sock *igmp)
871 {
872 struct pim_interface *pim_ifp;
873
874 sock_close(igmp);
875
876 pim_ifp = igmp->interface->info;
877
878 listnode_delete(pim_ifp->gm_socket_list, igmp);
879
880 igmp_sock_free(igmp);
881
882 if (!listcount(pim_ifp->gm_socket_list))
883 pim_igmp_if_reset(pim_ifp);
884 }
885
886 void igmp_sock_delete_all(struct interface *ifp)
887 {
888 struct pim_interface *pim_ifp;
889 struct listnode *igmp_node, *igmp_nextnode;
890 struct gm_sock *igmp;
891
892 pim_ifp = ifp->info;
893
894 for (ALL_LIST_ELEMENTS(pim_ifp->gm_socket_list, igmp_node,
895 igmp_nextnode, igmp)) {
896 igmp_sock_delete(igmp);
897 }
898 }
899
900 static unsigned int igmp_group_hash_key(const void *arg)
901 {
902 const struct gm_group *group = arg;
903
904 return jhash_1word(group->group_addr.s_addr, 0);
905 }
906
907 static bool igmp_group_hash_equal(const void *arg1, const void *arg2)
908 {
909 const struct gm_group *g1 = (const struct gm_group *)arg1;
910 const struct gm_group *g2 = (const struct gm_group *)arg2;
911
912 if (g1->group_addr.s_addr == g2->group_addr.s_addr)
913 return true;
914
915 return false;
916 }
917
918 void pim_igmp_if_init(struct pim_interface *pim_ifp, struct interface *ifp)
919 {
920 char hash_name[64];
921
922 pim_ifp->gm_socket_list = list_new();
923 pim_ifp->gm_socket_list->del = (void (*)(void *))igmp_sock_free;
924
925 pim_ifp->gm_group_list = list_new();
926 pim_ifp->gm_group_list->del = (void (*)(void *))igmp_group_free;
927
928 snprintf(hash_name, sizeof(hash_name), "IGMP %s hash", ifp->name);
929 pim_ifp->gm_group_hash = hash_create(igmp_group_hash_key,
930 igmp_group_hash_equal, hash_name);
931 }
932
933 void pim_igmp_if_reset(struct pim_interface *pim_ifp)
934 {
935 struct listnode *grp_node, *grp_nextnode;
936 struct gm_group *grp;
937
938 for (ALL_LIST_ELEMENTS(pim_ifp->gm_group_list, grp_node, grp_nextnode,
939 grp)) {
940 igmp_group_delete(grp);
941 }
942 }
943
944 void pim_igmp_if_fini(struct pim_interface *pim_ifp)
945 {
946 pim_igmp_if_reset(pim_ifp);
947
948 assert(pim_ifp->gm_group_list);
949 assert(!listcount(pim_ifp->gm_group_list));
950
951 list_delete(&pim_ifp->gm_group_list);
952 hash_free(pim_ifp->gm_group_hash);
953
954 list_delete(&pim_ifp->gm_socket_list);
955 }
956
957 static struct gm_sock *igmp_sock_new(int fd, struct in_addr ifaddr,
958 struct interface *ifp, int mtrace_only)
959 {
960 struct pim_interface *pim_ifp;
961 struct gm_sock *igmp;
962
963 pim_ifp = ifp->info;
964
965 if (PIM_DEBUG_IGMP_TRACE) {
966 zlog_debug(
967 "Creating IGMP socket fd=%d for address %pI4 on interface %s",
968 fd, &ifaddr, ifp->name);
969 }
970
971 igmp = XCALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
972
973 igmp->fd = fd;
974 igmp->interface = ifp;
975 igmp->ifaddr = ifaddr;
976 igmp->querier_addr = ifaddr;
977 igmp->t_igmp_read = NULL;
978 igmp->t_igmp_query_timer = NULL;
979 igmp->t_other_querier_timer = NULL; /* no other querier present */
980 igmp->querier_robustness_variable =
981 pim_ifp->gm_default_robustness_variable;
982 igmp->sock_creation = pim_time_monotonic_sec();
983
984 igmp_stats_init(&igmp->rx_stats);
985
986 if (mtrace_only) {
987 igmp->mtrace_only = mtrace_only;
988 return igmp;
989 }
990
991 igmp->mtrace_only = false;
992
993 /*
994 igmp_startup_mode_on() will reset QQI:
995
996 igmp->querier_query_interval = pim_ifp->gm_default_query_interval;
997 */
998 igmp_startup_mode_on(igmp);
999 pim_igmp_general_query_on(igmp);
1000
1001 return igmp;
1002 }
1003
1004 static void igmp_read_on(struct gm_sock *igmp);
1005
1006 static int pim_igmp_read(struct thread *t)
1007 {
1008 uint8_t buf[10000];
1009 struct gm_sock *igmp = (struct gm_sock *)THREAD_ARG(t);
1010 struct sockaddr_in from;
1011 struct sockaddr_in to;
1012 socklen_t fromlen = sizeof(from);
1013 socklen_t tolen = sizeof(to);
1014 ifindex_t ifindex = -1;
1015 int len;
1016
1017 while (1) {
1018 len = pim_socket_recvfromto(igmp->fd, buf, sizeof(buf), &from,
1019 &fromlen, &to, &tolen, &ifindex);
1020 if (len < 0) {
1021 if (errno == EINTR)
1022 continue;
1023 if (errno == EWOULDBLOCK || errno == EAGAIN)
1024 break;
1025
1026 goto done;
1027 }
1028 }
1029
1030 done:
1031 igmp_read_on(igmp);
1032 return 0;
1033 }
1034
1035 static void igmp_read_on(struct gm_sock *igmp)
1036 {
1037
1038 if (PIM_DEBUG_IGMP_TRACE_DETAIL) {
1039 zlog_debug("Scheduling READ event on IGMP socket fd=%d",
1040 igmp->fd);
1041 }
1042 thread_add_read(router->master, pim_igmp_read, igmp, igmp->fd,
1043 &igmp->t_igmp_read);
1044 }
1045
1046 struct gm_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
1047 struct in_addr ifaddr, struct interface *ifp,
1048 bool mtrace_only)
1049 {
1050 struct pim_interface *pim_ifp;
1051 struct gm_sock *igmp;
1052 struct sockaddr_in sin;
1053 int fd;
1054
1055 pim_ifp = ifp->info;
1056
1057 fd = igmp_sock_open(ifaddr, ifp, pim_ifp->options);
1058 if (fd < 0) {
1059 zlog_warn("Could not open IGMP socket for %pI4 on %s",
1060 &ifaddr, ifp->name);
1061 return NULL;
1062 }
1063
1064 sin.sin_family = AF_INET;
1065 sin.sin_addr = ifaddr;
1066 sin.sin_port = 0;
1067 if (bind(fd, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
1068 zlog_warn("Could not bind IGMP socket for %pI4 on %s: %s(%d)",
1069 &ifaddr, ifp->name, strerror(errno), errno);
1070 close(fd);
1071
1072 return NULL;
1073 }
1074
1075 igmp = igmp_sock_new(fd, ifaddr, ifp, mtrace_only);
1076
1077 igmp_read_on(igmp);
1078
1079 listnode_add(igmp_sock_list, igmp);
1080
1081 #ifdef IGMP_SOCK_DUMP
1082 igmp_sock_dump(igmp_sock_array);
1083 #endif
1084
1085 return igmp;
1086 }
1087
1088 /*
1089 RFC 3376: 6.5. Switching Router Filter-Modes
1090
1091 When a router's filter-mode for a group is EXCLUDE and the group
1092 timer expires, the router filter-mode for the group transitions to
1093 INCLUDE.
1094
1095 A router uses source records with running source timers as its state
1096 for the switch to a filter-mode of INCLUDE. If there are any source
1097 records with source timers greater than zero (i.e., requested to be
1098 forwarded), a router switches to filter-mode of INCLUDE using those
1099 source records. Source records whose timers are zero (from the
1100 previous EXCLUDE mode) are deleted.
1101 */
1102 static int igmp_group_timer(struct thread *t)
1103 {
1104 struct gm_group *group;
1105
1106 group = THREAD_ARG(t);
1107
1108 if (PIM_DEBUG_IGMP_TRACE) {
1109 char group_str[INET_ADDRSTRLEN];
1110 pim_inet4_dump("<group?>", group->group_addr, group_str,
1111 sizeof(group_str));
1112 zlog_debug("%s: Timer for group %s on interface %s", __func__,
1113 group_str, group->interface->name);
1114 }
1115
1116 assert(group->group_filtermode_isexcl);
1117
1118 group->group_filtermode_isexcl = 0;
1119
1120 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1121 igmp_anysource_forward_stop(group);
1122
1123 igmp_source_delete_expired(group->group_source_list);
1124
1125 assert(!group->group_filtermode_isexcl);
1126
1127 /*
1128 RFC 3376: 6.2.2. Definition of Group Timers
1129
1130 If there are no more source records for the group, delete group
1131 record.
1132 */
1133 if (listcount(group->group_source_list) < 1) {
1134 igmp_group_delete_empty_include(group);
1135 }
1136
1137 return 0;
1138 }
1139
1140 static void group_timer_off(struct gm_group *group)
1141 {
1142 if (!group->t_group_timer)
1143 return;
1144
1145 if (PIM_DEBUG_IGMP_TRACE) {
1146 char group_str[INET_ADDRSTRLEN];
1147 pim_inet4_dump("<group?>", group->group_addr, group_str,
1148 sizeof(group_str));
1149 zlog_debug("Cancelling TIMER event for group %s on %s",
1150 group_str, group->interface->name);
1151 }
1152 THREAD_OFF(group->t_group_timer);
1153 }
1154
1155 void igmp_group_timer_on(struct gm_group *group, long interval_msec,
1156 const char *ifname)
1157 {
1158 group_timer_off(group);
1159
1160 if (PIM_DEBUG_IGMP_EVENTS) {
1161 char group_str[INET_ADDRSTRLEN];
1162 pim_inet4_dump("<group?>", group->group_addr, group_str,
1163 sizeof(group_str));
1164 zlog_debug(
1165 "Scheduling %ld.%03ld sec TIMER event for group %s on %s",
1166 interval_msec / 1000, interval_msec % 1000, group_str,
1167 ifname);
1168 }
1169
1170 /*
1171 RFC 3376: 6.2.2. Definition of Group Timers
1172
1173 The group timer is only used when a group is in EXCLUDE mode and
1174 it represents the time for the *filter-mode* of the group to
1175 expire and switch to INCLUDE mode.
1176 */
1177 assert(group->group_filtermode_isexcl);
1178
1179 thread_add_timer_msec(router->master, igmp_group_timer, group,
1180 interval_msec, &group->t_group_timer);
1181 }
1182
1183 struct gm_group *find_group_by_addr(struct gm_sock *igmp,
1184 struct in_addr group_addr)
1185 {
1186 struct gm_group lookup;
1187 struct pim_interface *pim_ifp = igmp->interface->info;
1188
1189 lookup.group_addr.s_addr = group_addr.s_addr;
1190
1191 return hash_lookup(pim_ifp->gm_group_hash, &lookup);
1192 }
1193
1194 struct gm_group *igmp_add_group_by_addr(struct gm_sock *igmp,
1195 struct in_addr group_addr)
1196 {
1197 struct gm_group *group;
1198 struct pim_interface *pim_ifp = igmp->interface->info;
1199
1200 group = find_group_by_addr(igmp, group_addr);
1201 if (group) {
1202 return group;
1203 }
1204
1205 if (!pim_is_group_224_4(group_addr)) {
1206 zlog_warn("%s: Group Specified is not part of 224.0.0.0/4",
1207 __func__);
1208 return NULL;
1209 }
1210
1211 if (pim_is_group_224_0_0_0_24(group_addr)) {
1212 if (PIM_DEBUG_IGMP_TRACE)
1213 zlog_debug(
1214 "%s: Group specified %pI4 is part of 224.0.0.0/24",
1215 __func__, &group_addr);
1216 return NULL;
1217 }
1218 /*
1219 Non-existant group is created as INCLUDE {empty}:
1220
1221 RFC 3376 - 5.1. Action on Change of Interface State
1222
1223 If no interface state existed for that multicast address before
1224 the change (i.e., the change consisted of creating a new
1225 per-interface record), or if no state exists after the change
1226 (i.e., the change consisted of deleting a per-interface record),
1227 then the "non-existent" state is considered to have a filter mode
1228 of INCLUDE and an empty source list.
1229 */
1230
1231 group = XCALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
1232
1233 group->group_source_list = list_new();
1234 group->group_source_list->del = (void (*)(void *))igmp_source_free;
1235
1236 group->t_group_timer = NULL;
1237 group->t_group_query_retransmit_timer = NULL;
1238 group->group_specific_query_retransmit_count = 0;
1239 group->group_addr = group_addr;
1240 group->interface = igmp->interface;
1241 group->last_igmp_v1_report_dsec = -1;
1242 group->last_igmp_v2_report_dsec = -1;
1243 group->group_creation = pim_time_monotonic_sec();
1244 group->igmp_version = IGMP_DEFAULT_VERSION;
1245
1246 /* initialize new group as INCLUDE {empty} */
1247 group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
1248
1249 listnode_add(pim_ifp->gm_group_list, group);
1250 group = hash_get(pim_ifp->gm_group_hash, group, hash_alloc_intern);
1251
1252 if (PIM_DEBUG_IGMP_TRACE) {
1253 char group_str[INET_ADDRSTRLEN];
1254 pim_inet4_dump("<group?>", group->group_addr, group_str,
1255 sizeof(group_str));
1256 zlog_debug(
1257 "Creating new IGMP group %s on socket %d interface %s",
1258 group_str, igmp->fd, igmp->interface->name);
1259 }
1260
1261 igmp_group_count_incr(pim_ifp);
1262
1263 /*
1264 RFC 3376: 6.2.2. Definition of Group Timers
1265
1266 The group timer is only used when a group is in EXCLUDE mode and
1267 it represents the time for the *filter-mode* of the group to
1268 expire and switch to INCLUDE mode.
1269 */
1270 assert(!group->group_filtermode_isexcl); /* INCLUDE mode */
1271 assert(!group->t_group_timer); /* group timer == 0 */
1272
1273 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1274 igmp_anysource_forward_stop(group);
1275
1276 return group;
1277 }
1278
1279 void igmp_send_query(int igmp_version, struct gm_group *group, int fd,
1280 const char *ifname, char *query_buf, int query_buf_size,
1281 int num_sources, struct in_addr dst_addr,
1282 struct in_addr group_addr,
1283 int query_max_response_time_dsec, uint8_t s_flag,
1284 uint8_t querier_robustness_variable,
1285 uint16_t querier_query_interval)
1286 {
1287 if (igmp_version == 3) {
1288 igmp_v3_send_query(group, fd, ifname, query_buf, query_buf_size,
1289 num_sources, dst_addr, group_addr,
1290 query_max_response_time_dsec, s_flag,
1291 querier_robustness_variable,
1292 querier_query_interval);
1293 } else if (igmp_version == 2) {
1294 igmp_v2_send_query(group, fd, ifname, query_buf, dst_addr,
1295 group_addr, query_max_response_time_dsec);
1296 }
1297 }
1298
1299 void igmp_send_query_on_intf(struct interface *ifp, int igmp_ver)
1300 {
1301 struct pim_interface *pim_ifp = ifp->info;
1302 struct listnode *sock_node = NULL;
1303 struct gm_sock *igmp = NULL;
1304 struct in_addr dst_addr;
1305 struct in_addr group_addr;
1306 int query_buf_size;
1307
1308 if (!igmp_ver)
1309 igmp_ver = 2;
1310
1311 if (igmp_ver == 3)
1312 query_buf_size = PIM_IGMP_BUFSIZE_WRITE;
1313 else
1314 query_buf_size = IGMP_V12_MSG_SIZE;
1315
1316 dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
1317 group_addr.s_addr = PIM_NET_INADDR_ANY;
1318
1319 if (PIM_DEBUG_IGMP_TRACE)
1320 zlog_debug("Issuing general query on request on %s", ifp->name);
1321
1322 for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) {
1323
1324 char query_buf[query_buf_size];
1325
1326 igmp_send_query(igmp_ver, 0 /* igmp_group */, igmp->fd,
1327 igmp->interface->name, query_buf,
1328 sizeof(query_buf), 0 /* num_sources */,
1329 dst_addr, group_addr,
1330 pim_ifp->gm_query_max_response_time_dsec,
1331 1 /* s_flag: always set for general queries */,
1332 igmp->querier_robustness_variable,
1333 igmp->querier_query_interval);
1334 }
1335 }