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
30 #include "pim_igmpv2.h"
31 #include "pim_igmpv3.h"
32 #include "pim_igmp_mtrace.h"
33 #include "pim_iface.h"
35 #include "pim_mroute.h"
39 #include "pim_zebra.h"
41 static void group_timer_off(struct igmp_group
*group
);
42 static int pim_igmp_general_query(struct thread
*t
);
44 /* This socket is used for TXing IGMP packets only, IGMP RX happens
47 static int igmp_sock_open(struct in_addr ifaddr
, struct interface
*ifp
,
54 fd
= pim_socket_mcast(IPPROTO_IGMP
, ifaddr
, ifp
, 1);
59 if (PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(pim_options
)) {
60 if (inet_aton(PIM_ALL_ROUTERS
, &group
)) {
61 if (!pim_socket_join(fd
, group
, ifaddr
, ifp
->ifindex
))
65 "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
66 __FILE__
, __PRETTY_FUNCTION__
, fd
,
67 inet_ntoa(ifaddr
), PIM_ALL_ROUTERS
, errno
,
68 safe_strerror(errno
));
73 IGMP routers periodically send IGMP general queries to
75 IGMP routers must receive general queries for querier election.
77 if (inet_aton(PIM_ALL_SYSTEMS
, &group
)) {
78 if (!pim_socket_join(fd
, group
, ifaddr
, ifp
->ifindex
))
82 "%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
),
84 PIM_ALL_SYSTEMS
, errno
, safe_strerror(errno
));
87 if (inet_aton(PIM_ALL_IGMP_ROUTERS
, &group
)) {
88 if (!pim_socket_join(fd
, group
, ifaddr
, ifp
->ifindex
)) {
93 "%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
),
95 PIM_ALL_IGMP_ROUTERS
, errno
, safe_strerror(errno
));
100 "IGMP socket fd=%d could not join any group on interface address %s",
101 fd
, inet_ntoa(ifaddr
));
109 #undef IGMP_SOCK_DUMP
111 #ifdef IGMP_SOCK_DUMP
112 static void igmp_sock_dump(array_t
*igmp_sock_array
)
114 int size
= array_size(igmp_sock_array
);
115 for (int i
= 0; i
< size
; ++i
) {
117 struct igmp_sock
*igmp
= array_get(igmp_sock_array
, i
);
119 zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d", __FILE__
,
120 __PRETTY_FUNCTION__
, i
, size
,
121 inet_ntoa(igmp
->ifaddr
), igmp
->fd
);
126 struct igmp_sock
*pim_igmp_sock_lookup_ifaddr(struct list
*igmp_sock_list
,
127 struct in_addr ifaddr
)
129 struct listnode
*sock_node
;
130 struct igmp_sock
*igmp
;
132 #ifdef IGMP_SOCK_DUMP
133 igmp_sock_dump(igmp_sock_list
);
136 for (ALL_LIST_ELEMENTS_RO(igmp_sock_list
, sock_node
, igmp
))
137 if (ifaddr
.s_addr
== igmp
->ifaddr
.s_addr
)
143 struct igmp_sock
*igmp_sock_lookup_by_fd(struct list
*igmp_sock_list
, int fd
)
145 struct listnode
*sock_node
;
146 struct igmp_sock
*igmp
;
148 for (ALL_LIST_ELEMENTS_RO(igmp_sock_list
, sock_node
, igmp
))
155 static int pim_igmp_other_querier_expire(struct thread
*t
)
157 struct igmp_sock
*igmp
;
159 igmp
= THREAD_ARG(t
);
161 zassert(!igmp
->t_igmp_query_timer
);
163 if (PIM_DEBUG_IGMP_TRACE
) {
164 char ifaddr_str
[INET_ADDRSTRLEN
];
165 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
167 zlog_debug("%s: Querier %s resuming", __PRETTY_FUNCTION__
,
172 We are the current querier, then
173 re-start sending general queries.
174 RFC 2236 - sec 7 Other Querier
175 present timer expired (Send General
176 Query, Set Gen. Query. timer)
178 pim_igmp_general_query(t
);
183 void pim_igmp_other_querier_timer_on(struct igmp_sock
*igmp
)
185 long other_querier_present_interval_msec
;
186 struct pim_interface
*pim_ifp
;
189 zassert(igmp
->interface
);
190 zassert(igmp
->interface
->info
);
192 pim_ifp
= igmp
->interface
->info
;
194 if (igmp
->t_other_querier_timer
) {
196 There is other querier present already,
197 then reset the other-querier-present timer.
200 if (PIM_DEBUG_IGMP_TRACE
) {
201 char ifaddr_str
[INET_ADDRSTRLEN
];
202 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
205 "Querier %s resetting TIMER event for Other-Querier-Present",
208 THREAD_OFF(igmp
->t_other_querier_timer
);
211 We are the current querier, then stop sending general queries:
212 igmp->t_igmp_query_timer = NULL;
214 pim_igmp_general_query_off(igmp
);
218 Since this socket is starting the other-querier-present timer,
219 there should not be periodic query timer for this socket.
221 zassert(!igmp
->t_igmp_query_timer
);
224 RFC 3376: 8.5. Other Querier Present Interval
226 The Other Querier Present Interval is the length of time that must
227 pass before a multicast router decides that there is no longer
228 another multicast router which should be the querier. This value
229 MUST be ((the Robustness Variable) times (the Query Interval)) plus
230 (one half of one Query Response Interval).
232 other_querier_present_interval_msec = \
233 igmp->querier_robustness_variable * \
234 1000 * igmp->querier_query_interval + \
235 100 * (pim_ifp->query_max_response_time_dsec >> 1);
237 other_querier_present_interval_msec
= PIM_IGMP_OQPI_MSEC(
238 igmp
->querier_robustness_variable
, igmp
->querier_query_interval
,
239 pim_ifp
->igmp_query_max_response_time_dsec
);
241 if (PIM_DEBUG_IGMP_TRACE
) {
242 char ifaddr_str
[INET_ADDRSTRLEN
];
243 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
246 "Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
247 ifaddr_str
, other_querier_present_interval_msec
/ 1000,
248 other_querier_present_interval_msec
% 1000);
251 thread_add_timer_msec(master
, pim_igmp_other_querier_expire
, igmp
,
252 other_querier_present_interval_msec
,
253 &igmp
->t_other_querier_timer
);
256 void pim_igmp_other_querier_timer_off(struct igmp_sock
*igmp
)
260 if (PIM_DEBUG_IGMP_TRACE
) {
261 if (igmp
->t_other_querier_timer
) {
262 char ifaddr_str
[INET_ADDRSTRLEN
];
263 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
266 "IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
267 ifaddr_str
, igmp
->fd
, igmp
->interface
->name
);
270 THREAD_OFF(igmp
->t_other_querier_timer
);
273 static int igmp_recv_query(struct igmp_sock
*igmp
, int query_version
,
274 int max_resp_code
, struct in_addr from
,
275 const char *from_str
, char *igmp_msg
,
278 struct interface
*ifp
;
279 struct pim_interface
*pim_ifp
;
280 struct in_addr group_addr
;
281 uint16_t recv_checksum
;
284 memcpy(&group_addr
, igmp_msg
+ 4, sizeof(struct in_addr
));
286 ifp
= igmp
->interface
;
289 recv_checksum
= *(uint16_t *)(igmp_msg
+ IGMP_CHECKSUM_OFFSET
);
291 /* for computing checksum */
292 *(uint16_t *)(igmp_msg
+ IGMP_CHECKSUM_OFFSET
) = 0;
294 checksum
= in_cksum(igmp_msg
, igmp_msg_len
);
295 if (checksum
!= recv_checksum
) {
297 "Recv IGMP query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
298 query_version
, from_str
, ifp
->name
, recv_checksum
,
304 * RFC 3376 defines some guidelines on operating in backwards
305 * compatibility with older versions of IGMP but there are some gaps in
308 * - once we drop from say version 3 to version 2 we will never go back
309 * to version 3 even if the node that TXed an IGMP v2 query upgrades
312 * - The node with the lowest IP is the querier so we will only know to
313 * drop from v3 to v2 if the node that is the querier is also the one
314 * that is running igmp v2. If a non-querier only supports igmp v2
315 * we will have no way of knowing.
317 * For now we will simplify things and inform the user that they need to
318 * configure all PIM routers to use the same version of IGMP.
320 if (query_version
!= pim_ifp
->igmp_version
) {
322 "Recv IGMP query v%d from %s on %s but we are using v%d, please "
323 "configure all PIM routers on this subnet to use the same "
325 query_version
, from_str
, ifp
->name
,
326 pim_ifp
->igmp_version
);
330 if (PIM_DEBUG_IGMP_PACKETS
) {
331 char group_str
[INET_ADDRSTRLEN
];
332 pim_inet4_dump("<group?>", group_addr
, group_str
,
334 zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
335 query_version
, from_str
, ifp
->name
, group_str
);
339 RFC 3376: 6.6.2. Querier Election
341 When a router receives a query with a lower IP address, it sets
342 the Other-Querier-Present timer to Other Querier Present Interval
343 and ceases to send queries on the network if it was the previously
346 if (ntohl(from
.s_addr
) < ntohl(igmp
->ifaddr
.s_addr
)) {
348 if (PIM_DEBUG_IGMP_TRACE
) {
349 char ifaddr_str
[INET_ADDRSTRLEN
];
350 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
353 "%s: local address %s (%u) lost querier election to %s (%u)",
354 ifp
->name
, ifaddr_str
,
355 ntohl(igmp
->ifaddr
.s_addr
), from_str
,
359 pim_igmp_other_querier_timer_on(igmp
);
362 /* IGMP version 3 is the only one where we process the RXed query */
363 if (query_version
== 3) {
364 igmp_v3_recv_query(igmp
, from_str
, igmp_msg
);
370 static void on_trace(const char *label
, struct interface
*ifp
,
373 if (PIM_DEBUG_IGMP_TRACE
) {
374 char from_str
[INET_ADDRSTRLEN
];
375 pim_inet4_dump("<from?>", from
, from_str
, sizeof(from_str
));
376 zlog_debug("%s: from %s on %s", label
, from_str
, ifp
->name
);
380 static int igmp_v1_recv_report(struct igmp_sock
*igmp
, struct in_addr from
,
381 const char *from_str
, char *igmp_msg
,
384 struct interface
*ifp
= igmp
->interface
;
385 struct igmp_group
*group
;
386 struct in_addr group_addr
;
388 on_trace(__PRETTY_FUNCTION__
, igmp
->interface
, from
);
390 if (igmp_msg_len
!= IGMP_V12_MSG_SIZE
) {
392 "Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
393 from_str
, ifp
->name
, igmp_msg_len
, IGMP_V12_MSG_SIZE
);
397 if (PIM_DEBUG_IGMP_TRACE
) {
398 zlog_warn("%s %s: FIXME WRITEME", __FILE__
,
399 __PRETTY_FUNCTION__
);
402 memcpy(&group_addr
, igmp_msg
+ 4, sizeof(struct in_addr
));
404 if (pim_is_group_filtered(ifp
->info
, &group_addr
))
407 /* non-existant group is created as INCLUDE {empty} */
408 group
= igmp_add_group_by_addr(igmp
, group_addr
);
413 group
->last_igmp_v1_report_dsec
= pim_time_monotonic_dsec();
418 int pim_igmp_packet(struct igmp_sock
*igmp
, char *buf
, size_t len
)
421 size_t ip_hlen
; /* ip header length in bytes */
425 char from_str
[INET_ADDRSTRLEN
];
426 char to_str
[INET_ADDRSTRLEN
];
428 if (len
< sizeof(*ip_hdr
)) {
429 zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len
,
434 ip_hdr
= (struct ip
*)buf
;
436 pim_inet4_dump("<src?>", ip_hdr
->ip_src
, from_str
, sizeof(from_str
));
437 pim_inet4_dump("<dst?>", ip_hdr
->ip_dst
, to_str
, sizeof(to_str
));
439 ip_hlen
= ip_hdr
->ip_hl
<< 2; /* ip_hl gives length in 4-byte words */
441 if (PIM_DEBUG_IGMP_PACKETS
) {
443 "Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d",
444 from_str
, to_str
, igmp
->interface
->name
, len
, ip_hlen
,
448 igmp_msg
= buf
+ ip_hlen
;
449 msg_type
= *igmp_msg
;
450 igmp_msg_len
= len
- ip_hlen
;
452 if (PIM_DEBUG_IGMP_PACKETS
) {
454 "Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
455 from_str
, to_str
, igmp
->interface
->name
, ip_hdr
->ip_ttl
,
456 msg_type
, igmp_msg_len
);
459 if (igmp_msg_len
< PIM_IGMP_MIN_LEN
) {
460 zlog_warn("IGMP message size=%d shorter than minimum=%d",
461 igmp_msg_len
, PIM_IGMP_MIN_LEN
);
466 case PIM_IGMP_MEMBERSHIP_QUERY
: {
467 int max_resp_code
= igmp_msg
[1];
471 RFC 3376: 7.1. Query Version Distinctions
472 IGMPv1 Query: length = 8 octets AND Max Resp Code field is
474 IGMPv2 Query: length = 8 octets AND Max Resp Code field is
476 IGMPv3 Query: length >= 12 octets
479 if (igmp_msg_len
== 8) {
480 query_version
= max_resp_code
? 2 : 1;
481 } else if (igmp_msg_len
>= 12) {
484 zlog_warn("Unknown IGMP query version");
488 return igmp_recv_query(igmp
, query_version
, max_resp_code
,
489 ip_hdr
->ip_src
, from_str
, igmp_msg
,
493 case PIM_IGMP_V3_MEMBERSHIP_REPORT
:
494 return igmp_v3_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
495 igmp_msg
, igmp_msg_len
);
497 case PIM_IGMP_V2_MEMBERSHIP_REPORT
:
498 return igmp_v2_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
499 igmp_msg
, igmp_msg_len
);
501 case PIM_IGMP_V1_MEMBERSHIP_REPORT
:
502 return igmp_v1_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
503 igmp_msg
, igmp_msg_len
);
505 case PIM_IGMP_V2_LEAVE_GROUP
:
506 return igmp_v2_recv_leave(igmp
, ip_hdr
->ip_src
, from_str
,
507 igmp_msg
, igmp_msg_len
);
509 case PIM_IGMP_MTRACE_RESPONSE
:
510 return igmp_mtrace_recv_response(igmp
, ip_hdr
, ip_hdr
->ip_src
,
514 case PIM_IGMP_MTRACE_QUERY_REQUEST
:
515 return igmp_mtrace_recv_qry_req(igmp
, ip_hdr
, ip_hdr
->ip_src
,
520 zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type
);
525 void pim_igmp_general_query_on(struct igmp_sock
*igmp
)
527 struct pim_interface
*pim_ifp
;
532 Since this socket is starting as querier,
533 there should not exist a timer for other-querier-present.
535 zassert(!igmp
->t_other_querier_timer
);
536 pim_ifp
= igmp
->interface
->info
;
540 RFC 3376: 8.6. Startup Query Interval
542 The Startup Query Interval is the interval between General Queries
543 sent by a Querier on startup. Default: 1/4 the Query Interval.
544 The first one should be sent out immediately instead of 125/4
547 startup_mode
= igmp
->startup_query_count
> 0;
550 * If this is the first time we are sending a query on a
551 * newly configured igmp interface send it out in 1 second
552 * just to give the entire world a tiny bit of time to settle
553 * else the query interval is:
554 * query_interval = pim_ifp->igmp_default_query_interval >> 2;
556 if (igmp
->startup_query_count
557 == igmp
->querier_robustness_variable
)
560 query_interval
= PIM_IGMP_SQI(
561 pim_ifp
->igmp_default_query_interval
);
563 --igmp
->startup_query_count
;
565 query_interval
= igmp
->querier_query_interval
;
568 if (PIM_DEBUG_IGMP_TRACE
) {
569 char ifaddr_str
[INET_ADDRSTRLEN
];
570 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
573 "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
574 ifaddr_str
, query_interval
,
575 startup_mode
? "startup" : "non-startup", igmp
->fd
);
577 igmp
->t_igmp_query_timer
= NULL
;
578 thread_add_timer(master
, pim_igmp_general_query
, igmp
, query_interval
,
579 &igmp
->t_igmp_query_timer
);
582 void pim_igmp_general_query_off(struct igmp_sock
*igmp
)
586 if (PIM_DEBUG_IGMP_TRACE
) {
587 if (igmp
->t_igmp_query_timer
) {
588 char ifaddr_str
[INET_ADDRSTRLEN
];
589 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
592 "IGMP querier %s fd=%d cancelling query TIMER event on %s",
593 ifaddr_str
, igmp
->fd
, igmp
->interface
->name
);
596 THREAD_OFF(igmp
->t_igmp_query_timer
);
599 /* Issue IGMP general query */
600 static int pim_igmp_general_query(struct thread
*t
)
602 struct igmp_sock
*igmp
;
603 struct in_addr dst_addr
;
604 struct in_addr group_addr
;
605 struct pim_interface
*pim_ifp
;
608 igmp
= THREAD_ARG(t
);
610 zassert(igmp
->interface
);
611 zassert(igmp
->interface
->info
);
613 pim_ifp
= igmp
->interface
->info
;
615 if (pim_ifp
->igmp_version
== 3) {
616 query_buf_size
= PIM_IGMP_BUFSIZE_WRITE
;
618 query_buf_size
= IGMP_V12_MSG_SIZE
;
621 char query_buf
[query_buf_size
];
624 RFC3376: 4.1.12. IP Destination Addresses for Queries
626 In IGMPv3, General Queries are sent with an IP destination address
627 of 224.0.0.1, the all-systems multicast address. Group-Specific
628 and Group-and-Source-Specific Queries are sent with an IP
629 destination address equal to the multicast address of interest.
632 dst_addr
.s_addr
= htonl(INADDR_ALLHOSTS_GROUP
);
633 group_addr
.s_addr
= PIM_NET_INADDR_ANY
;
635 if (PIM_DEBUG_IGMP_TRACE
) {
636 char querier_str
[INET_ADDRSTRLEN
];
637 char dst_str
[INET_ADDRSTRLEN
];
638 pim_inet4_dump("<querier?>", igmp
->ifaddr
, querier_str
,
639 sizeof(querier_str
));
640 pim_inet4_dump("<dst?>", dst_addr
, dst_str
, sizeof(dst_str
));
641 zlog_debug("Querier %s issuing IGMP general query to %s on %s",
642 querier_str
, dst_str
, igmp
->interface
->name
);
645 igmp_send_query(pim_ifp
->igmp_version
, 0 /* igmp_group */, igmp
->fd
,
646 igmp
->interface
->name
, query_buf
, sizeof(query_buf
),
647 0 /* num_sources */, dst_addr
, group_addr
,
648 pim_ifp
->igmp_query_max_response_time_dsec
,
649 1 /* s_flag: always set for general queries */,
650 igmp
->querier_robustness_variable
,
651 igmp
->querier_query_interval
);
653 pim_igmp_general_query_on(igmp
);
658 static void sock_close(struct igmp_sock
*igmp
)
660 pim_igmp_other_querier_timer_off(igmp
);
661 pim_igmp_general_query_off(igmp
);
663 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
664 if (igmp
->t_igmp_read
) {
666 "Cancelling READ event on IGMP socket %s fd=%d on interface %s",
667 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
668 igmp
->interface
->name
);
671 THREAD_OFF(igmp
->t_igmp_read
);
673 if (close(igmp
->fd
)) {
675 "Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
676 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
677 igmp
->interface
->name
, errno
, safe_strerror(errno
));
680 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
681 zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
682 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
683 igmp
->interface
->name
);
687 void igmp_startup_mode_on(struct igmp_sock
*igmp
)
689 struct pim_interface
*pim_ifp
;
691 pim_ifp
= igmp
->interface
->info
;
694 RFC 3376: 8.7. Startup Query Count
696 The Startup Query Count is the number of Queries sent out on
697 startup, separated by the Startup Query Interval. Default: the
700 igmp
->startup_query_count
= igmp
->querier_robustness_variable
;
703 Since we're (re)starting, reset QQI to default Query Interval
705 igmp
->querier_query_interval
= pim_ifp
->igmp_default_query_interval
;
708 static void igmp_group_free(struct igmp_group
*group
)
710 list_delete_and_null(&group
->group_source_list
);
712 XFREE(MTYPE_PIM_IGMP_GROUP
, group
);
715 static void igmp_group_delete(struct igmp_group
*group
)
717 struct listnode
*src_node
;
718 struct listnode
*src_nextnode
;
719 struct igmp_source
*src
;
721 if (PIM_DEBUG_IGMP_TRACE
) {
722 char group_str
[INET_ADDRSTRLEN
];
723 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
725 zlog_debug("Deleting IGMP group %s from socket %d interface %s",
726 group_str
, group
->group_igmp_sock
->fd
,
727 group
->group_igmp_sock
->interface
->name
);
730 for (ALL_LIST_ELEMENTS(group
->group_source_list
, src_node
, src_nextnode
,
732 igmp_source_delete(src
);
735 if (group
->t_group_query_retransmit_timer
) {
736 THREAD_OFF(group
->t_group_query_retransmit_timer
);
739 group_timer_off(group
);
740 listnode_delete(group
->group_igmp_sock
->igmp_group_list
, group
);
741 hash_release(group
->group_igmp_sock
->igmp_group_hash
, group
);
743 igmp_group_free(group
);
746 void igmp_group_delete_empty_include(struct igmp_group
*group
)
748 zassert(!group
->group_filtermode_isexcl
);
749 zassert(!listcount(group
->group_source_list
));
751 igmp_group_delete(group
);
754 void igmp_sock_free(struct igmp_sock
*igmp
)
756 zassert(!igmp
->t_igmp_read
);
757 zassert(!igmp
->t_igmp_query_timer
);
758 zassert(!igmp
->t_other_querier_timer
);
759 zassert(igmp
->igmp_group_list
);
760 zassert(!listcount(igmp
->igmp_group_list
));
762 list_delete_and_null(&igmp
->igmp_group_list
);
763 hash_free(igmp
->igmp_group_hash
);
765 XFREE(MTYPE_PIM_IGMP_SOCKET
, igmp
);
768 void igmp_sock_delete(struct igmp_sock
*igmp
)
770 struct pim_interface
*pim_ifp
;
771 struct listnode
*grp_node
;
772 struct listnode
*grp_nextnode
;
773 struct igmp_group
*grp
;
775 for (ALL_LIST_ELEMENTS(igmp
->igmp_group_list
, grp_node
, grp_nextnode
,
777 igmp_group_delete(grp
);
782 pim_ifp
= igmp
->interface
->info
;
784 listnode_delete(pim_ifp
->igmp_socket_list
, igmp
);
786 igmp_sock_free(igmp
);
789 void igmp_sock_delete_all(struct interface
*ifp
)
791 struct pim_interface
*pim_ifp
;
792 struct listnode
*igmp_node
, *igmp_nextnode
;
793 struct igmp_sock
*igmp
;
797 for (ALL_LIST_ELEMENTS(pim_ifp
->igmp_socket_list
, igmp_node
,
798 igmp_nextnode
, igmp
)) {
799 igmp_sock_delete(igmp
);
803 static unsigned int igmp_group_hash_key(void *arg
)
805 struct igmp_group
*group
= (struct igmp_group
*)arg
;
807 return jhash_1word(group
->group_addr
.s_addr
, 0);
810 static int igmp_group_hash_equal(const void *arg1
, const void *arg2
)
812 const struct igmp_group
*g1
= (const struct igmp_group
*)arg1
;
813 const struct igmp_group
*g2
= (const struct igmp_group
*)arg2
;
815 if (g1
->group_addr
.s_addr
== g2
->group_addr
.s_addr
)
821 static struct igmp_sock
*igmp_sock_new(int fd
, struct in_addr ifaddr
,
822 struct interface
*ifp
)
824 struct pim_interface
*pim_ifp
;
825 struct igmp_sock
*igmp
;
830 if (PIM_DEBUG_IGMP_TRACE
) {
832 "Creating IGMP socket fd=%d for address %s on interface %s",
833 fd
, inet_ntoa(ifaddr
), ifp
->name
);
836 igmp
= XCALLOC(MTYPE_PIM_IGMP_SOCKET
, sizeof(*igmp
));
838 zlog_warn("%s %s: XCALLOC() failure", __FILE__
,
839 __PRETTY_FUNCTION__
);
843 igmp
->igmp_group_list
= list_new();
844 if (!igmp
->igmp_group_list
) {
845 zlog_err("%s %s: failure: igmp_group_list = list_new()",
846 __FILE__
, __PRETTY_FUNCTION__
);
849 igmp
->igmp_group_list
->del
= (void (*)(void *))igmp_group_free
;
851 snprintf(hash_name
, 64, "IGMP %s hash", ifp
->name
);
852 igmp
->igmp_group_hash
= hash_create(igmp_group_hash_key
,
853 igmp_group_hash_equal
,
857 igmp
->interface
= ifp
;
858 igmp
->ifaddr
= ifaddr
;
859 igmp
->t_igmp_read
= NULL
;
860 igmp
->t_igmp_query_timer
= NULL
;
861 igmp
->t_other_querier_timer
= NULL
; /* no other querier present */
862 igmp
->querier_robustness_variable
=
863 pim_ifp
->igmp_default_robustness_variable
;
864 igmp
->sock_creation
= pim_time_monotonic_sec();
867 igmp_startup_mode_on() will reset QQI:
869 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
871 igmp_startup_mode_on(igmp
);
872 pim_igmp_general_query_on(igmp
);
877 static void igmp_read_on(struct igmp_sock
*igmp
);
879 static int pim_igmp_read(struct thread
*t
)
882 struct igmp_sock
*igmp
= (struct igmp_sock
*)THREAD_ARG(t
);
883 struct sockaddr_in from
;
884 struct sockaddr_in to
;
885 socklen_t fromlen
= sizeof(from
);
886 socklen_t tolen
= sizeof(to
);
887 ifindex_t ifindex
= -1;
892 len
= pim_socket_recvfromto(igmp
->fd
, buf
, sizeof(buf
), &from
,
893 &fromlen
, &to
, &tolen
, &ifindex
);
897 if (errno
== EWOULDBLOCK
|| errno
== EAGAIN
)
909 static void igmp_read_on(struct igmp_sock
*igmp
)
912 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
913 zlog_debug("Scheduling READ event on IGMP socket fd=%d",
916 igmp
->t_igmp_read
= NULL
;
917 thread_add_read(master
, pim_igmp_read
, igmp
, igmp
->fd
,
921 struct igmp_sock
*pim_igmp_sock_add(struct list
*igmp_sock_list
,
922 struct in_addr ifaddr
,
923 struct interface
*ifp
)
925 struct pim_interface
*pim_ifp
;
926 struct igmp_sock
*igmp
;
931 fd
= igmp_sock_open(ifaddr
, ifp
, pim_ifp
->options
);
933 zlog_warn("Could not open IGMP socket for %s on %s",
934 inet_ntoa(ifaddr
), ifp
->name
);
938 igmp
= igmp_sock_new(fd
, ifaddr
, ifp
);
940 zlog_err("%s %s: igmp_sock_new() failure", __FILE__
,
941 __PRETTY_FUNCTION__
);
948 listnode_add(igmp_sock_list
, igmp
);
950 #ifdef IGMP_SOCK_DUMP
951 igmp_sock_dump(igmp_sock_array
);
958 RFC 3376: 6.5. Switching Router Filter-Modes
960 When a router's filter-mode for a group is EXCLUDE and the group
961 timer expires, the router filter-mode for the group transitions to
964 A router uses source records with running source timers as its state
965 for the switch to a filter-mode of INCLUDE. If there are any source
966 records with source timers greater than zero (i.e., requested to be
967 forwarded), a router switches to filter-mode of INCLUDE using those
968 source records. Source records whose timers are zero (from the
969 previous EXCLUDE mode) are deleted.
971 static int igmp_group_timer(struct thread
*t
)
973 struct igmp_group
*group
;
975 group
= THREAD_ARG(t
);
977 if (PIM_DEBUG_IGMP_TRACE
) {
978 char group_str
[INET_ADDRSTRLEN
];
979 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
981 zlog_debug("%s: Timer for group %s on interface %s",
982 __PRETTY_FUNCTION__
, group_str
,
983 group
->group_igmp_sock
->interface
->name
);
986 zassert(group
->group_filtermode_isexcl
);
988 group
->group_filtermode_isexcl
= 0;
990 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
991 igmp_anysource_forward_stop(group
);
993 igmp_source_delete_expired(group
->group_source_list
);
995 zassert(!group
->group_filtermode_isexcl
);
998 RFC 3376: 6.2.2. Definition of Group Timers
1000 If there are no more source records for the group, delete group
1003 if (listcount(group
->group_source_list
) < 1) {
1004 igmp_group_delete_empty_include(group
);
1010 static void group_timer_off(struct igmp_group
*group
)
1012 if (!group
->t_group_timer
)
1015 if (PIM_DEBUG_IGMP_TRACE
) {
1016 char group_str
[INET_ADDRSTRLEN
];
1017 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1019 zlog_debug("Cancelling TIMER event for group %s on %s",
1020 group_str
, group
->group_igmp_sock
->interface
->name
);
1022 THREAD_OFF(group
->t_group_timer
);
1025 void igmp_group_timer_on(struct igmp_group
*group
, long interval_msec
,
1028 group_timer_off(group
);
1030 if (PIM_DEBUG_IGMP_EVENTS
) {
1031 char group_str
[INET_ADDRSTRLEN
];
1032 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1035 "Scheduling %ld.%03ld sec TIMER event for group %s on %s",
1036 interval_msec
/ 1000, interval_msec
% 1000, group_str
,
1041 RFC 3376: 6.2.2. Definition of Group Timers
1043 The group timer is only used when a group is in EXCLUDE mode and
1044 it represents the time for the *filter-mode* of the group to
1045 expire and switch to INCLUDE mode.
1047 zassert(group
->group_filtermode_isexcl
);
1049 thread_add_timer_msec(master
, igmp_group_timer
, group
, interval_msec
,
1050 &group
->t_group_timer
);
1053 struct igmp_group
*find_group_by_addr(struct igmp_sock
*igmp
,
1054 struct in_addr group_addr
)
1056 struct igmp_group lookup
;
1058 lookup
.group_addr
.s_addr
= group_addr
.s_addr
;
1060 return hash_lookup(igmp
->igmp_group_hash
, &lookup
);
1063 struct igmp_group
*igmp_add_group_by_addr(struct igmp_sock
*igmp
,
1064 struct in_addr group_addr
)
1066 struct igmp_group
*group
;
1068 group
= find_group_by_addr(igmp
, group_addr
);
1073 if (!pim_is_group_224_4(group_addr
)) {
1074 zlog_warn("%s: Group Specified is not part of 224.0.0.0/4",
1075 __PRETTY_FUNCTION__
);
1079 if (pim_is_group_224_0_0_0_24(group_addr
)) {
1080 zlog_warn("%s: Group specified is part of 224.0.0.0/24",
1081 __PRETTY_FUNCTION__
);
1085 Non-existant group is created as INCLUDE {empty}:
1087 RFC 3376 - 5.1. Action on Change of Interface State
1089 If no interface state existed for that multicast address before
1090 the change (i.e., the change consisted of creating a new
1091 per-interface record), or if no state exists after the change
1092 (i.e., the change consisted of deleting a per-interface record),
1093 then the "non-existent" state is considered to have a filter mode
1094 of INCLUDE and an empty source list.
1097 group
= XCALLOC(MTYPE_PIM_IGMP_GROUP
, sizeof(*group
));
1099 zlog_warn("%s %s: XCALLOC() failure", __FILE__
,
1100 __PRETTY_FUNCTION__
);
1101 return NULL
; /* error, not found, could not create */
1104 group
->group_source_list
= list_new();
1105 if (!group
->group_source_list
) {
1106 zlog_warn("%s %s: list_new() failure", __FILE__
,
1107 __PRETTY_FUNCTION__
);
1108 XFREE(MTYPE_PIM_IGMP_GROUP
, group
); /* discard group */
1109 return NULL
; /* error, not found, could not initialize */
1111 group
->group_source_list
->del
= (void (*)(void *))igmp_source_free
;
1113 group
->t_group_timer
= NULL
;
1114 group
->t_group_query_retransmit_timer
= NULL
;
1115 group
->group_specific_query_retransmit_count
= 0;
1116 group
->group_addr
= group_addr
;
1117 group
->group_igmp_sock
= igmp
;
1118 group
->last_igmp_v1_report_dsec
= -1;
1119 group
->last_igmp_v2_report_dsec
= -1;
1120 group
->group_creation
= pim_time_monotonic_sec();
1121 group
->igmp_version
= IGMP_DEFAULT_VERSION
;
1123 /* initialize new group as INCLUDE {empty} */
1124 group
->group_filtermode_isexcl
= 0; /* 0=INCLUDE, 1=EXCLUDE */
1126 listnode_add(igmp
->igmp_group_list
, group
);
1127 group
= hash_get(igmp
->igmp_group_hash
, group
, hash_alloc_intern
);
1129 if (PIM_DEBUG_IGMP_TRACE
) {
1130 char group_str
[INET_ADDRSTRLEN
];
1131 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1134 "Creating new IGMP group %s on socket %d interface %s",
1135 group_str
, igmp
->fd
, igmp
->interface
->name
);
1139 RFC 3376: 6.2.2. Definition of Group Timers
1141 The group timer is only used when a group is in EXCLUDE mode and
1142 it represents the time for the *filter-mode* of the group to
1143 expire and switch to INCLUDE mode.
1145 zassert(!group
->group_filtermode_isexcl
); /* INCLUDE mode */
1146 zassert(!group
->t_group_timer
); /* group timer == 0 */
1148 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1149 igmp_anysource_forward_stop(group
);
1154 void igmp_send_query(int igmp_version
, struct igmp_group
*group
, int fd
,
1155 const char *ifname
, char *query_buf
, int query_buf_size
,
1156 int num_sources
, struct in_addr dst_addr
,
1157 struct in_addr group_addr
,
1158 int query_max_response_time_dsec
, uint8_t s_flag
,
1159 uint8_t querier_robustness_variable
,
1160 uint16_t querier_query_interval
)
1162 if (igmp_version
== 3) {
1163 igmp_v3_send_query(group
, fd
, ifname
, query_buf
, query_buf_size
,
1164 num_sources
, dst_addr
, group_addr
,
1165 query_max_response_time_dsec
, s_flag
,
1166 querier_robustness_variable
,
1167 querier_query_interval
);
1168 } else if (igmp_version
== 2) {
1169 igmp_v2_send_query(group
, fd
, ifname
, query_buf
, dst_addr
,
1170 group_addr
, query_max_response_time_dsec
);