3 * Copyright (C) 2008 Everton da Silva Marques
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.
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.
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
27 #include "lib_errors.h"
31 #include "pim_igmpv2.h"
32 #include "pim_igmpv3.h"
33 #include "pim_igmp_mtrace.h"
34 #include "pim_iface.h"
36 #include "pim_mroute.h"
40 #include "pim_zebra.h"
42 static void group_timer_off(struct igmp_group
*group
);
43 static int pim_igmp_general_query(struct thread
*t
);
45 /* This socket is used for TXing IGMP packets only, IGMP RX happens
48 static int igmp_sock_open(struct in_addr ifaddr
, struct interface
*ifp
,
55 fd
= pim_socket_mcast(IPPROTO_IGMP
, ifaddr
, ifp
, 1);
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
))
66 "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
67 __FILE__
, __PRETTY_FUNCTION__
, fd
,
68 inet_ntoa(ifaddr
), PIM_ALL_ROUTERS
, errno
,
69 safe_strerror(errno
));
74 IGMP routers periodically send IGMP general queries to
76 IGMP routers must receive general queries for querier election.
78 if (inet_aton(PIM_ALL_SYSTEMS
, &group
)) {
79 if (!pim_socket_join(fd
, group
, ifaddr
, ifp
->ifindex
))
83 "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
84 __FILE__
, __PRETTY_FUNCTION__
, fd
, inet_ntoa(ifaddr
),
85 PIM_ALL_SYSTEMS
, errno
, safe_strerror(errno
));
88 if (inet_aton(PIM_ALL_IGMP_ROUTERS
, &group
)) {
89 if (!pim_socket_join(fd
, group
, ifaddr
, ifp
->ifindex
)) {
94 "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
95 __FILE__
, __PRETTY_FUNCTION__
, fd
, inet_ntoa(ifaddr
),
96 PIM_ALL_IGMP_ROUTERS
, errno
, safe_strerror(errno
));
102 "IGMP socket fd=%d could not join any group on interface address %s",
103 fd
, inet_ntoa(ifaddr
));
111 #undef IGMP_SOCK_DUMP
113 #ifdef IGMP_SOCK_DUMP
114 static void igmp_sock_dump(array_t
*igmp_sock_array
)
116 int size
= array_size(igmp_sock_array
);
117 for (int i
= 0; i
< size
; ++i
) {
119 struct igmp_sock
*igmp
= array_get(igmp_sock_array
, i
);
121 zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d", __FILE__
,
122 __PRETTY_FUNCTION__
, i
, size
,
123 inet_ntoa(igmp
->ifaddr
), igmp
->fd
);
128 struct igmp_sock
*pim_igmp_sock_lookup_ifaddr(struct list
*igmp_sock_list
,
129 struct in_addr ifaddr
)
131 struct listnode
*sock_node
;
132 struct igmp_sock
*igmp
;
134 #ifdef IGMP_SOCK_DUMP
135 igmp_sock_dump(igmp_sock_list
);
138 for (ALL_LIST_ELEMENTS_RO(igmp_sock_list
, sock_node
, igmp
))
139 if (ifaddr
.s_addr
== igmp
->ifaddr
.s_addr
)
145 struct igmp_sock
*igmp_sock_lookup_by_fd(struct list
*igmp_sock_list
, int fd
)
147 struct listnode
*sock_node
;
148 struct igmp_sock
*igmp
;
150 for (ALL_LIST_ELEMENTS_RO(igmp_sock_list
, sock_node
, igmp
))
157 static int pim_igmp_other_querier_expire(struct thread
*t
)
159 struct igmp_sock
*igmp
;
161 igmp
= THREAD_ARG(t
);
163 zassert(!igmp
->t_igmp_query_timer
);
165 if (PIM_DEBUG_IGMP_TRACE
) {
166 char ifaddr_str
[INET_ADDRSTRLEN
];
167 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
169 zlog_debug("%s: Querier %s resuming", __PRETTY_FUNCTION__
,
174 We are the current querier, then
175 re-start sending general queries.
176 RFC 2236 - sec 7 Other Querier
177 present timer expired (Send General
178 Query, Set Gen. Query. timer)
180 pim_igmp_general_query(t
);
185 void pim_igmp_other_querier_timer_on(struct igmp_sock
*igmp
)
187 long other_querier_present_interval_msec
;
188 struct pim_interface
*pim_ifp
;
191 zassert(igmp
->interface
);
192 zassert(igmp
->interface
->info
);
194 pim_ifp
= igmp
->interface
->info
;
196 if (igmp
->t_other_querier_timer
) {
198 There is other querier present already,
199 then reset the other-querier-present timer.
202 if (PIM_DEBUG_IGMP_TRACE
) {
203 char ifaddr_str
[INET_ADDRSTRLEN
];
204 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
207 "Querier %s resetting TIMER event for Other-Querier-Present",
210 THREAD_OFF(igmp
->t_other_querier_timer
);
213 We are the current querier, then stop sending general queries:
214 igmp->t_igmp_query_timer = NULL;
216 pim_igmp_general_query_off(igmp
);
220 Since this socket is starting the other-querier-present timer,
221 there should not be periodic query timer for this socket.
223 zassert(!igmp
->t_igmp_query_timer
);
226 RFC 3376: 8.5. Other Querier Present Interval
228 The Other Querier Present Interval is the length of time that must
229 pass before a multicast router decides that there is no longer
230 another multicast router which should be the querier. This value
231 MUST be ((the Robustness Variable) times (the Query Interval)) plus
232 (one half of one Query Response Interval).
234 other_querier_present_interval_msec = \
235 igmp->querier_robustness_variable * \
236 1000 * igmp->querier_query_interval + \
237 100 * (pim_ifp->query_max_response_time_dsec >> 1);
239 other_querier_present_interval_msec
= PIM_IGMP_OQPI_MSEC(
240 igmp
->querier_robustness_variable
, igmp
->querier_query_interval
,
241 pim_ifp
->igmp_query_max_response_time_dsec
);
243 if (PIM_DEBUG_IGMP_TRACE
) {
244 char ifaddr_str
[INET_ADDRSTRLEN
];
245 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
248 "Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
249 ifaddr_str
, other_querier_present_interval_msec
/ 1000,
250 other_querier_present_interval_msec
% 1000);
253 thread_add_timer_msec(router
->master
, pim_igmp_other_querier_expire
,
254 igmp
, other_querier_present_interval_msec
,
255 &igmp
->t_other_querier_timer
);
258 void pim_igmp_other_querier_timer_off(struct igmp_sock
*igmp
)
262 if (PIM_DEBUG_IGMP_TRACE
) {
263 if (igmp
->t_other_querier_timer
) {
264 char ifaddr_str
[INET_ADDRSTRLEN
];
265 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
268 "IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
269 ifaddr_str
, igmp
->fd
, igmp
->interface
->name
);
272 THREAD_OFF(igmp
->t_other_querier_timer
);
275 static int igmp_recv_query(struct igmp_sock
*igmp
, int query_version
,
276 int max_resp_code
, struct in_addr from
,
277 const char *from_str
, char *igmp_msg
,
280 struct interface
*ifp
;
281 struct pim_interface
*pim_ifp
;
282 struct in_addr group_addr
;
283 uint16_t recv_checksum
;
286 if (igmp
->mtrace_only
)
289 memcpy(&group_addr
, igmp_msg
+ 4, sizeof(struct in_addr
));
291 ifp
= igmp
->interface
;
294 recv_checksum
= *(uint16_t *)(igmp_msg
+ IGMP_CHECKSUM_OFFSET
);
296 /* for computing checksum */
297 *(uint16_t *)(igmp_msg
+ IGMP_CHECKSUM_OFFSET
) = 0;
299 checksum
= in_cksum(igmp_msg
, igmp_msg_len
);
300 if (checksum
!= recv_checksum
) {
302 "Recv IGMP query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
303 query_version
, from_str
, ifp
->name
, recv_checksum
,
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
);
315 /* Collecting IGMP Rx stats */
316 switch (query_version
) {
318 igmp
->rx_stats
.query_v1
++;
321 igmp
->rx_stats
.query_v2
++;
324 igmp
->rx_stats
.query_v3
++;
327 igmp
->rx_stats
.unsupported
++;
331 * RFC 3376 defines some guidelines on operating in backwards
332 * compatibility with older versions of IGMP but there are some gaps in
335 * - once we drop from say version 3 to version 2 we will never go back
336 * to version 3 even if the node that TXed an IGMP v2 query upgrades
339 * - The node with the lowest IP is the querier so we will only know to
340 * drop from v3 to v2 if the node that is the querier is also the one
341 * that is running igmp v2. If a non-querier only supports igmp v2
342 * we will have no way of knowing.
344 * For now we will simplify things and inform the user that they need to
345 * configure all PIM routers to use the same version of IGMP.
347 if (query_version
!= pim_ifp
->igmp_version
) {
349 "Recv IGMP query v%d from %s on %s but we are using v%d, please "
350 "configure all PIM routers on this subnet to use the same "
352 query_version
, from_str
, ifp
->name
,
353 pim_ifp
->igmp_version
);
357 if (PIM_DEBUG_IGMP_PACKETS
) {
358 char group_str
[INET_ADDRSTRLEN
];
359 pim_inet4_dump("<group?>", group_addr
, group_str
,
361 zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
362 query_version
, from_str
, ifp
->name
, group_str
);
366 RFC 3376: 6.6.2. Querier Election
368 When a router receives a query with a lower IP address, it sets
369 the Other-Querier-Present timer to Other Querier Present Interval
370 and ceases to send queries on the network if it was the previously
373 if (ntohl(from
.s_addr
) < ntohl(igmp
->ifaddr
.s_addr
)) {
375 if (PIM_DEBUG_IGMP_TRACE
) {
376 char ifaddr_str
[INET_ADDRSTRLEN
];
377 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
380 "%s: local address %s (%u) lost querier election to %s (%u)",
381 ifp
->name
, ifaddr_str
,
382 ntohl(igmp
->ifaddr
.s_addr
), from_str
,
386 pim_igmp_other_querier_timer_on(igmp
);
389 /* IGMP version 3 is the only one where we process the RXed query */
390 if (query_version
== 3) {
391 igmp_v3_recv_query(igmp
, from_str
, igmp_msg
);
397 static void on_trace(const char *label
, struct interface
*ifp
,
400 if (PIM_DEBUG_IGMP_TRACE
) {
401 char from_str
[INET_ADDRSTRLEN
];
402 pim_inet4_dump("<from?>", from
, from_str
, sizeof(from_str
));
403 zlog_debug("%s: from %s on %s", label
, from_str
, ifp
->name
);
407 static int igmp_v1_recv_report(struct igmp_sock
*igmp
, struct in_addr from
,
408 const char *from_str
, char *igmp_msg
,
411 struct interface
*ifp
= igmp
->interface
;
412 struct igmp_group
*group
;
413 struct in_addr group_addr
;
415 on_trace(__PRETTY_FUNCTION__
, igmp
->interface
, from
);
417 if (igmp
->mtrace_only
)
420 if (igmp_msg_len
!= IGMP_V12_MSG_SIZE
) {
422 "Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
423 from_str
, ifp
->name
, igmp_msg_len
, IGMP_V12_MSG_SIZE
);
427 /* Collecting IGMP Rx stats */
428 igmp
->rx_stats
.report_v1
++;
430 if (PIM_DEBUG_IGMP_TRACE
) {
431 zlog_warn("%s %s: FIXME WRITEME", __FILE__
,
432 __PRETTY_FUNCTION__
);
435 memcpy(&group_addr
, igmp_msg
+ 4, sizeof(struct in_addr
));
437 if (pim_is_group_filtered(ifp
->info
, &group_addr
))
440 /* non-existant group is created as INCLUDE {empty} */
441 group
= igmp_add_group_by_addr(igmp
, group_addr
);
446 group
->last_igmp_v1_report_dsec
= pim_time_monotonic_dsec();
451 int pim_igmp_packet(struct igmp_sock
*igmp
, char *buf
, size_t len
)
454 size_t ip_hlen
; /* ip header length in bytes */
458 char from_str
[INET_ADDRSTRLEN
];
459 char to_str
[INET_ADDRSTRLEN
];
461 if (len
< sizeof(*ip_hdr
)) {
462 zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len
,
467 ip_hdr
= (struct ip
*)buf
;
469 pim_inet4_dump("<src?>", ip_hdr
->ip_src
, from_str
, sizeof(from_str
));
470 pim_inet4_dump("<dst?>", ip_hdr
->ip_dst
, to_str
, sizeof(to_str
));
472 ip_hlen
= ip_hdr
->ip_hl
<< 2; /* ip_hl gives length in 4-byte words */
474 if (PIM_DEBUG_IGMP_PACKETS
) {
476 "Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d",
477 from_str
, to_str
, igmp
->interface
->name
, len
, ip_hlen
,
481 igmp_msg
= buf
+ ip_hlen
;
482 msg_type
= *igmp_msg
;
483 igmp_msg_len
= len
- ip_hlen
;
485 if (PIM_DEBUG_IGMP_PACKETS
) {
487 "Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
488 from_str
, to_str
, igmp
->interface
->name
, ip_hdr
->ip_ttl
,
489 msg_type
, igmp_msg_len
);
492 if (igmp_msg_len
< PIM_IGMP_MIN_LEN
) {
493 zlog_warn("IGMP message size=%d shorter than minimum=%d",
494 igmp_msg_len
, PIM_IGMP_MIN_LEN
);
499 case PIM_IGMP_MEMBERSHIP_QUERY
: {
500 int max_resp_code
= igmp_msg
[1];
504 RFC 3376: 7.1. Query Version Distinctions
505 IGMPv1 Query: length = 8 octets AND Max Resp Code field is
507 IGMPv2 Query: length = 8 octets AND Max Resp Code field is
509 IGMPv3 Query: length >= 12 octets
512 if (igmp_msg_len
== 8) {
513 query_version
= max_resp_code
? 2 : 1;
514 } else if (igmp_msg_len
>= 12) {
517 zlog_warn("Unknown IGMP query version");
521 return igmp_recv_query(igmp
, query_version
, max_resp_code
,
522 ip_hdr
->ip_src
, from_str
, igmp_msg
,
526 case PIM_IGMP_V3_MEMBERSHIP_REPORT
:
527 return igmp_v3_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
528 igmp_msg
, igmp_msg_len
);
530 case PIM_IGMP_V2_MEMBERSHIP_REPORT
:
531 return igmp_v2_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
532 igmp_msg
, igmp_msg_len
);
534 case PIM_IGMP_V1_MEMBERSHIP_REPORT
:
535 return igmp_v1_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
536 igmp_msg
, igmp_msg_len
);
538 case PIM_IGMP_V2_LEAVE_GROUP
:
539 return igmp_v2_recv_leave(igmp
, ip_hdr
->ip_src
, from_str
,
540 igmp_msg
, igmp_msg_len
);
542 case PIM_IGMP_MTRACE_RESPONSE
:
543 return igmp_mtrace_recv_response(igmp
, ip_hdr
, ip_hdr
->ip_src
,
546 case PIM_IGMP_MTRACE_QUERY_REQUEST
:
547 return igmp_mtrace_recv_qry_req(igmp
, ip_hdr
, ip_hdr
->ip_src
,
552 zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type
);
554 /* Collecting IGMP Rx stats */
555 igmp
->rx_stats
.unsupported
++;
560 void pim_igmp_general_query_on(struct igmp_sock
*igmp
)
562 struct pim_interface
*pim_ifp
;
567 Since this socket is starting as querier,
568 there should not exist a timer for other-querier-present.
570 zassert(!igmp
->t_other_querier_timer
);
571 pim_ifp
= igmp
->interface
->info
;
575 RFC 3376: 8.6. Startup Query Interval
577 The Startup Query Interval is the interval between General Queries
578 sent by a Querier on startup. Default: 1/4 the Query Interval.
579 The first one should be sent out immediately instead of 125/4
582 startup_mode
= igmp
->startup_query_count
> 0;
585 * If this is the first time we are sending a query on a
586 * newly configured igmp interface send it out in 1 second
587 * just to give the entire world a tiny bit of time to settle
588 * else the query interval is:
589 * query_interval = pim_ifp->igmp_default_query_interval >> 2;
591 if (igmp
->startup_query_count
592 == igmp
->querier_robustness_variable
)
595 query_interval
= PIM_IGMP_SQI(
596 pim_ifp
->igmp_default_query_interval
);
598 --igmp
->startup_query_count
;
600 query_interval
= igmp
->querier_query_interval
;
603 if (PIM_DEBUG_IGMP_TRACE
) {
604 char ifaddr_str
[INET_ADDRSTRLEN
];
605 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
608 "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
609 ifaddr_str
, query_interval
,
610 startup_mode
? "startup" : "non-startup", igmp
->fd
);
612 igmp
->t_igmp_query_timer
= NULL
;
613 thread_add_timer(router
->master
, pim_igmp_general_query
, igmp
,
614 query_interval
, &igmp
->t_igmp_query_timer
);
617 void pim_igmp_general_query_off(struct igmp_sock
*igmp
)
621 if (PIM_DEBUG_IGMP_TRACE
) {
622 if (igmp
->t_igmp_query_timer
) {
623 char ifaddr_str
[INET_ADDRSTRLEN
];
624 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
627 "IGMP querier %s fd=%d cancelling query TIMER event on %s",
628 ifaddr_str
, igmp
->fd
, igmp
->interface
->name
);
631 THREAD_OFF(igmp
->t_igmp_query_timer
);
634 /* Issue IGMP general query */
635 static int pim_igmp_general_query(struct thread
*t
)
637 struct igmp_sock
*igmp
;
638 struct in_addr dst_addr
;
639 struct in_addr group_addr
;
640 struct pim_interface
*pim_ifp
;
643 igmp
= THREAD_ARG(t
);
645 zassert(igmp
->interface
);
646 zassert(igmp
->interface
->info
);
648 pim_ifp
= igmp
->interface
->info
;
650 if (pim_ifp
->igmp_version
== 3) {
651 query_buf_size
= PIM_IGMP_BUFSIZE_WRITE
;
653 query_buf_size
= IGMP_V12_MSG_SIZE
;
656 char query_buf
[query_buf_size
];
659 RFC3376: 4.1.12. IP Destination Addresses for Queries
661 In IGMPv3, General Queries are sent with an IP destination address
662 of 224.0.0.1, the all-systems multicast address. Group-Specific
663 and Group-and-Source-Specific Queries are sent with an IP
664 destination address equal to the multicast address of interest.
667 dst_addr
.s_addr
= htonl(INADDR_ALLHOSTS_GROUP
);
668 group_addr
.s_addr
= PIM_NET_INADDR_ANY
;
670 if (PIM_DEBUG_IGMP_TRACE
) {
671 char querier_str
[INET_ADDRSTRLEN
];
672 char dst_str
[INET_ADDRSTRLEN
];
673 pim_inet4_dump("<querier?>", igmp
->ifaddr
, querier_str
,
674 sizeof(querier_str
));
675 pim_inet4_dump("<dst?>", dst_addr
, dst_str
, sizeof(dst_str
));
676 zlog_debug("Querier %s issuing IGMP general query to %s on %s",
677 querier_str
, dst_str
, igmp
->interface
->name
);
680 igmp_send_query(pim_ifp
->igmp_version
, 0 /* igmp_group */, igmp
->fd
,
681 igmp
->interface
->name
, query_buf
, sizeof(query_buf
),
682 0 /* num_sources */, dst_addr
, group_addr
,
683 pim_ifp
->igmp_query_max_response_time_dsec
,
684 1 /* s_flag: always set for general queries */,
685 igmp
->querier_robustness_variable
,
686 igmp
->querier_query_interval
);
688 pim_igmp_general_query_on(igmp
);
693 static void sock_close(struct igmp_sock
*igmp
)
695 pim_igmp_other_querier_timer_off(igmp
);
696 pim_igmp_general_query_off(igmp
);
698 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
699 if (igmp
->t_igmp_read
) {
701 "Cancelling READ event on IGMP socket %s fd=%d on interface %s",
702 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
703 igmp
->interface
->name
);
706 THREAD_OFF(igmp
->t_igmp_read
);
708 if (close(igmp
->fd
)) {
711 "Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
712 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
713 igmp
->interface
->name
, errno
, safe_strerror(errno
));
716 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
717 zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
718 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
719 igmp
->interface
->name
);
723 void igmp_startup_mode_on(struct igmp_sock
*igmp
)
725 struct pim_interface
*pim_ifp
;
727 pim_ifp
= igmp
->interface
->info
;
730 RFC 3376: 8.7. Startup Query Count
732 The Startup Query Count is the number of Queries sent out on
733 startup, separated by the Startup Query Interval. Default: the
736 igmp
->startup_query_count
= igmp
->querier_robustness_variable
;
739 Since we're (re)starting, reset QQI to default Query Interval
741 igmp
->querier_query_interval
= pim_ifp
->igmp_default_query_interval
;
744 static void igmp_group_free(struct igmp_group
*group
)
746 list_delete(&group
->group_source_list
);
748 XFREE(MTYPE_PIM_IGMP_GROUP
, group
);
751 void igmp_group_delete(struct igmp_group
*group
)
753 struct listnode
*src_node
;
754 struct listnode
*src_nextnode
;
755 struct igmp_source
*src
;
757 if (PIM_DEBUG_IGMP_TRACE
) {
758 char group_str
[INET_ADDRSTRLEN
];
759 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
761 zlog_debug("Deleting IGMP group %s from socket %d interface %s",
762 group_str
, group
->group_igmp_sock
->fd
,
763 group
->group_igmp_sock
->interface
->name
);
766 for (ALL_LIST_ELEMENTS(group
->group_source_list
, src_node
, src_nextnode
,
768 igmp_source_delete(src
);
771 if (group
->t_group_query_retransmit_timer
) {
772 THREAD_OFF(group
->t_group_query_retransmit_timer
);
775 group_timer_off(group
);
776 listnode_delete(group
->group_igmp_sock
->igmp_group_list
, group
);
777 hash_release(group
->group_igmp_sock
->igmp_group_hash
, group
);
779 igmp_group_free(group
);
782 void igmp_group_delete_empty_include(struct igmp_group
*group
)
784 zassert(!group
->group_filtermode_isexcl
);
785 zassert(!listcount(group
->group_source_list
));
787 igmp_group_delete(group
);
790 void igmp_sock_free(struct igmp_sock
*igmp
)
792 zassert(!igmp
->t_igmp_read
);
793 zassert(!igmp
->t_igmp_query_timer
);
794 zassert(!igmp
->t_other_querier_timer
);
795 zassert(igmp
->igmp_group_list
);
796 zassert(!listcount(igmp
->igmp_group_list
));
798 list_delete(&igmp
->igmp_group_list
);
799 hash_free(igmp
->igmp_group_hash
);
801 XFREE(MTYPE_PIM_IGMP_SOCKET
, igmp
);
804 void igmp_sock_delete(struct igmp_sock
*igmp
)
806 struct pim_interface
*pim_ifp
;
807 struct listnode
*grp_node
;
808 struct listnode
*grp_nextnode
;
809 struct igmp_group
*grp
;
811 for (ALL_LIST_ELEMENTS(igmp
->igmp_group_list
, grp_node
, grp_nextnode
,
813 igmp_group_delete(grp
);
818 pim_ifp
= igmp
->interface
->info
;
820 listnode_delete(pim_ifp
->igmp_socket_list
, igmp
);
822 igmp_sock_free(igmp
);
825 void igmp_sock_delete_all(struct interface
*ifp
)
827 struct pim_interface
*pim_ifp
;
828 struct listnode
*igmp_node
, *igmp_nextnode
;
829 struct igmp_sock
*igmp
;
833 for (ALL_LIST_ELEMENTS(pim_ifp
->igmp_socket_list
, igmp_node
,
834 igmp_nextnode
, igmp
)) {
835 igmp_sock_delete(igmp
);
839 static unsigned int igmp_group_hash_key(const void *arg
)
841 const struct igmp_group
*group
= arg
;
843 return jhash_1word(group
->group_addr
.s_addr
, 0);
846 static bool igmp_group_hash_equal(const void *arg1
, const void *arg2
)
848 const struct igmp_group
*g1
= (const struct igmp_group
*)arg1
;
849 const struct igmp_group
*g2
= (const struct igmp_group
*)arg2
;
851 if (g1
->group_addr
.s_addr
== g2
->group_addr
.s_addr
)
857 static struct igmp_sock
*igmp_sock_new(int fd
, struct in_addr ifaddr
,
858 struct interface
*ifp
, int mtrace_only
)
860 struct pim_interface
*pim_ifp
;
861 struct igmp_sock
*igmp
;
866 if (PIM_DEBUG_IGMP_TRACE
) {
868 "Creating IGMP socket fd=%d for address %s on interface %s",
869 fd
, inet_ntoa(ifaddr
), ifp
->name
);
872 igmp
= XCALLOC(MTYPE_PIM_IGMP_SOCKET
, sizeof(*igmp
));
874 igmp
->igmp_group_list
= list_new();
875 igmp
->igmp_group_list
->del
= (void (*)(void *))igmp_group_free
;
877 snprintf(hash_name
, 64, "IGMP %s hash", ifp
->name
);
878 igmp
->igmp_group_hash
= hash_create(igmp_group_hash_key
,
879 igmp_group_hash_equal
, hash_name
);
882 igmp
->interface
= ifp
;
883 igmp
->ifaddr
= ifaddr
;
884 igmp
->t_igmp_read
= NULL
;
885 igmp
->t_igmp_query_timer
= NULL
;
886 igmp
->t_other_querier_timer
= NULL
; /* no other querier present */
887 igmp
->querier_robustness_variable
=
888 pim_ifp
->igmp_default_robustness_variable
;
889 igmp
->sock_creation
= pim_time_monotonic_sec();
891 igmp_stats_init(&igmp
->rx_stats
);
894 igmp
->mtrace_only
= mtrace_only
;
898 igmp
->mtrace_only
= false;
901 igmp_startup_mode_on() will reset QQI:
903 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
905 igmp_startup_mode_on(igmp
);
906 pim_igmp_general_query_on(igmp
);
911 static void igmp_read_on(struct igmp_sock
*igmp
);
913 static int pim_igmp_read(struct thread
*t
)
916 struct igmp_sock
*igmp
= (struct igmp_sock
*)THREAD_ARG(t
);
917 struct sockaddr_in from
;
918 struct sockaddr_in to
;
919 socklen_t fromlen
= sizeof(from
);
920 socklen_t tolen
= sizeof(to
);
921 ifindex_t ifindex
= -1;
925 len
= pim_socket_recvfromto(igmp
->fd
, buf
, sizeof(buf
), &from
,
926 &fromlen
, &to
, &tolen
, &ifindex
);
930 if (errno
== EWOULDBLOCK
|| errno
== EAGAIN
)
942 static void igmp_read_on(struct igmp_sock
*igmp
)
945 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
946 zlog_debug("Scheduling READ event on IGMP socket fd=%d",
949 igmp
->t_igmp_read
= NULL
;
950 thread_add_read(router
->master
, pim_igmp_read
, igmp
, igmp
->fd
,
954 struct igmp_sock
*pim_igmp_sock_add(struct list
*igmp_sock_list
,
955 struct in_addr ifaddr
,
956 struct interface
*ifp
,
959 struct pim_interface
*pim_ifp
;
960 struct igmp_sock
*igmp
;
965 fd
= igmp_sock_open(ifaddr
, ifp
, pim_ifp
->options
);
967 zlog_warn("Could not open IGMP socket for %s on %s",
968 inet_ntoa(ifaddr
), ifp
->name
);
972 igmp
= igmp_sock_new(fd
, ifaddr
, ifp
, mtrace_only
);
976 listnode_add(igmp_sock_list
, igmp
);
978 #ifdef IGMP_SOCK_DUMP
979 igmp_sock_dump(igmp_sock_array
);
986 RFC 3376: 6.5. Switching Router Filter-Modes
988 When a router's filter-mode for a group is EXCLUDE and the group
989 timer expires, the router filter-mode for the group transitions to
992 A router uses source records with running source timers as its state
993 for the switch to a filter-mode of INCLUDE. If there are any source
994 records with source timers greater than zero (i.e., requested to be
995 forwarded), a router switches to filter-mode of INCLUDE using those
996 source records. Source records whose timers are zero (from the
997 previous EXCLUDE mode) are deleted.
999 static int igmp_group_timer(struct thread
*t
)
1001 struct igmp_group
*group
;
1003 group
= THREAD_ARG(t
);
1005 if (PIM_DEBUG_IGMP_TRACE
) {
1006 char group_str
[INET_ADDRSTRLEN
];
1007 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1009 zlog_debug("%s: Timer for group %s on interface %s",
1010 __PRETTY_FUNCTION__
, group_str
,
1011 group
->group_igmp_sock
->interface
->name
);
1014 zassert(group
->group_filtermode_isexcl
);
1016 group
->group_filtermode_isexcl
= 0;
1018 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1019 igmp_anysource_forward_stop(group
);
1021 igmp_source_delete_expired(group
->group_source_list
);
1023 zassert(!group
->group_filtermode_isexcl
);
1026 RFC 3376: 6.2.2. Definition of Group Timers
1028 If there are no more source records for the group, delete group
1031 if (listcount(group
->group_source_list
) < 1) {
1032 igmp_group_delete_empty_include(group
);
1038 static void group_timer_off(struct igmp_group
*group
)
1040 if (!group
->t_group_timer
)
1043 if (PIM_DEBUG_IGMP_TRACE
) {
1044 char group_str
[INET_ADDRSTRLEN
];
1045 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1047 zlog_debug("Cancelling TIMER event for group %s on %s",
1048 group_str
, group
->group_igmp_sock
->interface
->name
);
1050 THREAD_OFF(group
->t_group_timer
);
1053 void igmp_group_timer_on(struct igmp_group
*group
, long interval_msec
,
1056 group_timer_off(group
);
1058 if (PIM_DEBUG_IGMP_EVENTS
) {
1059 char group_str
[INET_ADDRSTRLEN
];
1060 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1063 "Scheduling %ld.%03ld sec TIMER event for group %s on %s",
1064 interval_msec
/ 1000, interval_msec
% 1000, group_str
,
1069 RFC 3376: 6.2.2. Definition of Group Timers
1071 The group timer is only used when a group is in EXCLUDE mode and
1072 it represents the time for the *filter-mode* of the group to
1073 expire and switch to INCLUDE mode.
1075 zassert(group
->group_filtermode_isexcl
);
1077 thread_add_timer_msec(router
->master
, igmp_group_timer
, group
,
1078 interval_msec
, &group
->t_group_timer
);
1081 struct igmp_group
*find_group_by_addr(struct igmp_sock
*igmp
,
1082 struct in_addr group_addr
)
1084 struct igmp_group lookup
;
1086 lookup
.group_addr
.s_addr
= group_addr
.s_addr
;
1088 return hash_lookup(igmp
->igmp_group_hash
, &lookup
);
1091 struct igmp_group
*igmp_add_group_by_addr(struct igmp_sock
*igmp
,
1092 struct in_addr group_addr
)
1094 struct igmp_group
*group
;
1096 group
= find_group_by_addr(igmp
, group_addr
);
1101 if (!pim_is_group_224_4(group_addr
)) {
1102 zlog_warn("%s: Group Specified is not part of 224.0.0.0/4",
1103 __PRETTY_FUNCTION__
);
1107 if (pim_is_group_224_0_0_0_24(group_addr
)) {
1108 zlog_warn("%s: Group specified is part of 224.0.0.0/24",
1109 __PRETTY_FUNCTION__
);
1113 Non-existant group is created as INCLUDE {empty}:
1115 RFC 3376 - 5.1. Action on Change of Interface State
1117 If no interface state existed for that multicast address before
1118 the change (i.e., the change consisted of creating a new
1119 per-interface record), or if no state exists after the change
1120 (i.e., the change consisted of deleting a per-interface record),
1121 then the "non-existent" state is considered to have a filter mode
1122 of INCLUDE and an empty source list.
1125 group
= XCALLOC(MTYPE_PIM_IGMP_GROUP
, sizeof(*group
));
1127 group
->group_source_list
= list_new();
1128 group
->group_source_list
->del
= (void (*)(void *))igmp_source_free
;
1130 group
->t_group_timer
= NULL
;
1131 group
->t_group_query_retransmit_timer
= NULL
;
1132 group
->group_specific_query_retransmit_count
= 0;
1133 group
->group_addr
= group_addr
;
1134 group
->group_igmp_sock
= igmp
;
1135 group
->last_igmp_v1_report_dsec
= -1;
1136 group
->last_igmp_v2_report_dsec
= -1;
1137 group
->group_creation
= pim_time_monotonic_sec();
1138 group
->igmp_version
= IGMP_DEFAULT_VERSION
;
1140 /* initialize new group as INCLUDE {empty} */
1141 group
->group_filtermode_isexcl
= 0; /* 0=INCLUDE, 1=EXCLUDE */
1143 listnode_add(igmp
->igmp_group_list
, group
);
1144 group
= hash_get(igmp
->igmp_group_hash
, group
, hash_alloc_intern
);
1146 if (PIM_DEBUG_IGMP_TRACE
) {
1147 char group_str
[INET_ADDRSTRLEN
];
1148 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1151 "Creating new IGMP group %s on socket %d interface %s",
1152 group_str
, igmp
->fd
, igmp
->interface
->name
);
1156 RFC 3376: 6.2.2. Definition of Group Timers
1158 The group timer is only used when a group is in EXCLUDE mode and
1159 it represents the time for the *filter-mode* of the group to
1160 expire and switch to INCLUDE mode.
1162 zassert(!group
->group_filtermode_isexcl
); /* INCLUDE mode */
1163 zassert(!group
->t_group_timer
); /* group timer == 0 */
1165 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1166 igmp_anysource_forward_stop(group
);
1171 void igmp_send_query(int igmp_version
, struct igmp_group
*group
, int fd
,
1172 const char *ifname
, char *query_buf
, int query_buf_size
,
1173 int num_sources
, struct in_addr dst_addr
,
1174 struct in_addr group_addr
,
1175 int query_max_response_time_dsec
, uint8_t s_flag
,
1176 uint8_t querier_robustness_variable
,
1177 uint16_t querier_query_interval
)
1179 if (igmp_version
== 3) {
1180 igmp_v3_send_query(group
, fd
, ifname
, query_buf
, query_buf_size
,
1181 num_sources
, dst_addr
, group_addr
,
1182 query_max_response_time_dsec
, s_flag
,
1183 querier_robustness_variable
,
1184 querier_query_interval
);
1185 } else if (igmp_version
== 2) {
1186 igmp_v2_send_query(group
, fd
, ifname
, query_buf
, dst_addr
,
1187 group_addr
, query_max_response_time_dsec
);
1191 void igmp_send_query_on_intf(struct interface
*ifp
, int igmp_ver
)
1193 struct pim_interface
*pim_ifp
= ifp
->info
;
1194 struct listnode
*sock_node
= NULL
;
1195 struct igmp_sock
*igmp
= NULL
;
1196 struct in_addr dst_addr
;
1197 struct in_addr group_addr
;
1204 query_buf_size
= PIM_IGMP_BUFSIZE_WRITE
;
1206 query_buf_size
= IGMP_V12_MSG_SIZE
;
1208 dst_addr
.s_addr
= htonl(INADDR_ALLHOSTS_GROUP
);
1209 group_addr
.s_addr
= PIM_NET_INADDR_ANY
;
1211 if (PIM_DEBUG_IGMP_TRACE
)
1212 zlog_debug("Issuing general query on request on %s",
1215 for (ALL_LIST_ELEMENTS_RO(pim_ifp
->igmp_socket_list
, sock_node
, igmp
)) {
1217 char query_buf
[query_buf_size
];
1219 igmp_send_query(igmp_ver
, 0 /* igmp_group */, igmp
->fd
,
1220 igmp
->interface
->name
, query_buf
,
1221 sizeof(query_buf
), 0 /* num_sources */,
1222 dst_addr
, group_addr
,
1223 pim_ifp
->igmp_query_max_response_time_dsec
,
1224 1 /* s_flag: always set for general queries */,
1225 igmp
->querier_robustness_variable
,
1226 igmp
->querier_query_interval
);