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 if (if_lookup_address(&from
, AF_INET
, ifp
->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
);
322 /* Collecting IGMP Rx stats */
323 switch (query_version
) {
325 igmp
->rx_stats
.query_v1
++;
328 igmp
->rx_stats
.query_v2
++;
331 igmp
->rx_stats
.query_v3
++;
334 igmp
->rx_stats
.unsupported
++;
338 * RFC 3376 defines some guidelines on operating in backwards
339 * compatibility with older versions of IGMP but there are some gaps in
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
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.
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.
354 if (query_version
!= pim_ifp
->igmp_version
) {
356 "Recv IGMP query v%d from %s on %s but we are using v%d, please "
357 "configure all PIM routers on this subnet to use the same "
359 query_version
, from_str
, ifp
->name
,
360 pim_ifp
->igmp_version
);
364 if (PIM_DEBUG_IGMP_PACKETS
) {
365 char group_str
[INET_ADDRSTRLEN
];
366 pim_inet4_dump("<group?>", group_addr
, group_str
,
368 zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
369 query_version
, from_str
, ifp
->name
, group_str
);
373 RFC 3376: 6.6.2. Querier Election
375 When a router receives a query with a lower IP address, it sets
376 the Other-Querier-Present timer to Other Querier Present Interval
377 and ceases to send queries on the network if it was the previously
380 if (ntohl(from
.s_addr
) < ntohl(igmp
->ifaddr
.s_addr
)) {
382 if (PIM_DEBUG_IGMP_TRACE
) {
383 char ifaddr_str
[INET_ADDRSTRLEN
];
384 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
387 "%s: local address %s (%u) lost querier election to %s (%u)",
388 ifp
->name
, ifaddr_str
,
389 ntohl(igmp
->ifaddr
.s_addr
), from_str
,
393 pim_igmp_other_querier_timer_on(igmp
);
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
);
404 static void on_trace(const char *label
, struct interface
*ifp
,
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
);
414 static int igmp_v1_recv_report(struct igmp_sock
*igmp
, struct in_addr from
,
415 const char *from_str
, char *igmp_msg
,
418 struct interface
*ifp
= igmp
->interface
;
419 struct igmp_group
*group
;
420 struct in_addr group_addr
;
422 on_trace(__PRETTY_FUNCTION__
, igmp
->interface
, from
);
424 if (igmp
->mtrace_only
)
427 if (igmp_msg_len
!= IGMP_V12_MSG_SIZE
) {
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
);
434 /* Collecting IGMP Rx stats */
435 igmp
->rx_stats
.report_v1
++;
437 if (PIM_DEBUG_IGMP_TRACE
) {
438 zlog_warn("%s %s: FIXME WRITEME", __FILE__
,
439 __PRETTY_FUNCTION__
);
442 memcpy(&group_addr
, igmp_msg
+ 4, sizeof(struct in_addr
));
444 if (pim_is_group_filtered(ifp
->info
, &group_addr
))
447 /* non-existant group is created as INCLUDE {empty} */
448 group
= igmp_add_group_by_addr(igmp
, group_addr
);
453 group
->last_igmp_v1_report_dsec
= pim_time_monotonic_dsec();
458 int pim_igmp_packet(struct igmp_sock
*igmp
, char *buf
, size_t len
)
461 size_t ip_hlen
; /* ip header length in bytes */
465 char from_str
[INET_ADDRSTRLEN
];
466 char to_str
[INET_ADDRSTRLEN
];
468 if (len
< sizeof(*ip_hdr
)) {
469 zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len
,
474 ip_hdr
= (struct ip
*)buf
;
476 pim_inet4_dump("<src?>", ip_hdr
->ip_src
, from_str
, sizeof(from_str
));
477 pim_inet4_dump("<dst?>", ip_hdr
->ip_dst
, to_str
, sizeof(to_str
));
479 ip_hlen
= ip_hdr
->ip_hl
<< 2; /* ip_hl gives length in 4-byte words */
483 "IGMP packet header claims size %zu, but we only have %zu bytes",
488 igmp_msg
= buf
+ ip_hlen
;
489 igmp_msg_len
= len
- ip_hlen
;
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
);
497 msg_type
= *igmp_msg
;
499 if (PIM_DEBUG_IGMP_PACKETS
) {
501 "Recv IGMP packet from %s to %s on %s: size=%zu ttl=%d msg_type=%d msg_size=%d",
502 from_str
, to_str
, igmp
->interface
->name
, len
, ip_hdr
->ip_ttl
,
503 msg_type
, igmp_msg_len
);
507 case PIM_IGMP_MEMBERSHIP_QUERY
: {
508 int max_resp_code
= igmp_msg
[1];
512 RFC 3376: 7.1. Query Version Distinctions
513 IGMPv1 Query: length = 8 octets AND Max Resp Code field is
515 IGMPv2 Query: length = 8 octets AND Max Resp Code field is
517 IGMPv3 Query: length >= 12 octets
520 if (igmp_msg_len
== 8) {
521 query_version
= max_resp_code
? 2 : 1;
522 } else if (igmp_msg_len
>= 12) {
525 zlog_warn("Unknown IGMP query version");
529 return igmp_recv_query(igmp
, query_version
, max_resp_code
,
530 ip_hdr
->ip_src
, from_str
, igmp_msg
,
534 case PIM_IGMP_V3_MEMBERSHIP_REPORT
:
535 return igmp_v3_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
536 igmp_msg
, igmp_msg_len
);
538 case PIM_IGMP_V2_MEMBERSHIP_REPORT
:
539 return igmp_v2_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
540 igmp_msg
, igmp_msg_len
);
542 case PIM_IGMP_V1_MEMBERSHIP_REPORT
:
543 return igmp_v1_recv_report(igmp
, ip_hdr
->ip_src
, from_str
,
544 igmp_msg
, igmp_msg_len
);
546 case PIM_IGMP_V2_LEAVE_GROUP
:
547 return igmp_v2_recv_leave(igmp
, ip_hdr
->ip_src
, from_str
,
548 igmp_msg
, igmp_msg_len
);
550 case PIM_IGMP_MTRACE_RESPONSE
:
551 return igmp_mtrace_recv_response(igmp
, ip_hdr
, ip_hdr
->ip_src
,
554 case PIM_IGMP_MTRACE_QUERY_REQUEST
:
555 return igmp_mtrace_recv_qry_req(igmp
, ip_hdr
, ip_hdr
->ip_src
,
560 zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type
);
562 /* Collecting IGMP Rx stats */
563 igmp
->rx_stats
.unsupported
++;
568 void pim_igmp_general_query_on(struct igmp_sock
*igmp
)
570 struct pim_interface
*pim_ifp
;
575 Since this socket is starting as querier,
576 there should not exist a timer for other-querier-present.
578 zassert(!igmp
->t_other_querier_timer
);
579 pim_ifp
= igmp
->interface
->info
;
583 RFC 3376: 8.6. Startup Query Interval
585 The Startup Query Interval is the interval between General Queries
586 sent by a Querier on startup. Default: 1/4 the Query Interval.
587 The first one should be sent out immediately instead of 125/4
590 startup_mode
= igmp
->startup_query_count
> 0;
593 * If this is the first time we are sending a query on a
594 * newly configured igmp interface send it out in 1 second
595 * just to give the entire world a tiny bit of time to settle
596 * else the query interval is:
597 * query_interval = pim_ifp->igmp_default_query_interval >> 2;
599 if (igmp
->startup_query_count
600 == igmp
->querier_robustness_variable
)
603 query_interval
= PIM_IGMP_SQI(
604 pim_ifp
->igmp_default_query_interval
);
606 --igmp
->startup_query_count
;
608 query_interval
= igmp
->querier_query_interval
;
611 if (PIM_DEBUG_IGMP_TRACE
) {
612 char ifaddr_str
[INET_ADDRSTRLEN
];
613 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
616 "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
617 ifaddr_str
, query_interval
,
618 startup_mode
? "startup" : "non-startup", igmp
->fd
);
620 igmp
->t_igmp_query_timer
= NULL
;
621 thread_add_timer(router
->master
, pim_igmp_general_query
, igmp
,
622 query_interval
, &igmp
->t_igmp_query_timer
);
625 void pim_igmp_general_query_off(struct igmp_sock
*igmp
)
629 if (PIM_DEBUG_IGMP_TRACE
) {
630 if (igmp
->t_igmp_query_timer
) {
631 char ifaddr_str
[INET_ADDRSTRLEN
];
632 pim_inet4_dump("<ifaddr?>", igmp
->ifaddr
, ifaddr_str
,
635 "IGMP querier %s fd=%d cancelling query TIMER event on %s",
636 ifaddr_str
, igmp
->fd
, igmp
->interface
->name
);
639 THREAD_OFF(igmp
->t_igmp_query_timer
);
642 /* Issue IGMP general query */
643 static int pim_igmp_general_query(struct thread
*t
)
645 struct igmp_sock
*igmp
;
646 struct in_addr dst_addr
;
647 struct in_addr group_addr
;
648 struct pim_interface
*pim_ifp
;
651 igmp
= THREAD_ARG(t
);
653 zassert(igmp
->interface
);
654 zassert(igmp
->interface
->info
);
656 pim_ifp
= igmp
->interface
->info
;
658 if (pim_ifp
->igmp_version
== 3) {
659 query_buf_size
= PIM_IGMP_BUFSIZE_WRITE
;
661 query_buf_size
= IGMP_V12_MSG_SIZE
;
664 char query_buf
[query_buf_size
];
667 RFC3376: 4.1.12. IP Destination Addresses for Queries
669 In IGMPv3, General Queries are sent with an IP destination address
670 of 224.0.0.1, the all-systems multicast address. Group-Specific
671 and Group-and-Source-Specific Queries are sent with an IP
672 destination address equal to the multicast address of interest.
675 dst_addr
.s_addr
= htonl(INADDR_ALLHOSTS_GROUP
);
676 group_addr
.s_addr
= PIM_NET_INADDR_ANY
;
678 if (PIM_DEBUG_IGMP_TRACE
) {
679 char querier_str
[INET_ADDRSTRLEN
];
680 char dst_str
[INET_ADDRSTRLEN
];
681 pim_inet4_dump("<querier?>", igmp
->ifaddr
, querier_str
,
682 sizeof(querier_str
));
683 pim_inet4_dump("<dst?>", dst_addr
, dst_str
, sizeof(dst_str
));
684 zlog_debug("Querier %s issuing IGMP general query to %s on %s",
685 querier_str
, dst_str
, igmp
->interface
->name
);
688 igmp_send_query(pim_ifp
->igmp_version
, 0 /* igmp_group */, igmp
->fd
,
689 igmp
->interface
->name
, query_buf
, sizeof(query_buf
),
690 0 /* num_sources */, dst_addr
, group_addr
,
691 pim_ifp
->igmp_query_max_response_time_dsec
,
692 1 /* s_flag: always set for general queries */,
693 igmp
->querier_robustness_variable
,
694 igmp
->querier_query_interval
);
696 pim_igmp_general_query_on(igmp
);
701 static void sock_close(struct igmp_sock
*igmp
)
703 pim_igmp_other_querier_timer_off(igmp
);
704 pim_igmp_general_query_off(igmp
);
706 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
707 if (igmp
->t_igmp_read
) {
709 "Cancelling READ event on IGMP socket %s fd=%d on interface %s",
710 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
711 igmp
->interface
->name
);
714 THREAD_OFF(igmp
->t_igmp_read
);
716 if (close(igmp
->fd
)) {
719 "Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
720 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
721 igmp
->interface
->name
, errno
, safe_strerror(errno
));
724 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
725 zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
726 inet_ntoa(igmp
->ifaddr
), igmp
->fd
,
727 igmp
->interface
->name
);
731 void igmp_startup_mode_on(struct igmp_sock
*igmp
)
733 struct pim_interface
*pim_ifp
;
735 pim_ifp
= igmp
->interface
->info
;
738 RFC 3376: 8.7. Startup Query Count
740 The Startup Query Count is the number of Queries sent out on
741 startup, separated by the Startup Query Interval. Default: the
744 igmp
->startup_query_count
= igmp
->querier_robustness_variable
;
747 Since we're (re)starting, reset QQI to default Query Interval
749 igmp
->querier_query_interval
= pim_ifp
->igmp_default_query_interval
;
752 static void igmp_group_free(struct igmp_group
*group
)
754 list_delete(&group
->group_source_list
);
756 XFREE(MTYPE_PIM_IGMP_GROUP
, group
);
759 void igmp_group_delete(struct igmp_group
*group
)
761 struct listnode
*src_node
;
762 struct listnode
*src_nextnode
;
763 struct igmp_source
*src
;
765 if (PIM_DEBUG_IGMP_TRACE
) {
766 char group_str
[INET_ADDRSTRLEN
];
767 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
769 zlog_debug("Deleting IGMP group %s from socket %d interface %s",
770 group_str
, group
->group_igmp_sock
->fd
,
771 group
->group_igmp_sock
->interface
->name
);
774 for (ALL_LIST_ELEMENTS(group
->group_source_list
, src_node
, src_nextnode
,
776 igmp_source_delete(src
);
779 if (group
->t_group_query_retransmit_timer
) {
780 THREAD_OFF(group
->t_group_query_retransmit_timer
);
783 group_timer_off(group
);
784 listnode_delete(group
->group_igmp_sock
->igmp_group_list
, group
);
785 hash_release(group
->group_igmp_sock
->igmp_group_hash
, group
);
787 igmp_group_free(group
);
790 void igmp_group_delete_empty_include(struct igmp_group
*group
)
792 zassert(!group
->group_filtermode_isexcl
);
793 zassert(!listcount(group
->group_source_list
));
795 igmp_group_delete(group
);
798 void igmp_sock_free(struct igmp_sock
*igmp
)
800 zassert(!igmp
->t_igmp_read
);
801 zassert(!igmp
->t_igmp_query_timer
);
802 zassert(!igmp
->t_other_querier_timer
);
803 zassert(igmp
->igmp_group_list
);
804 zassert(!listcount(igmp
->igmp_group_list
));
806 list_delete(&igmp
->igmp_group_list
);
807 hash_free(igmp
->igmp_group_hash
);
809 XFREE(MTYPE_PIM_IGMP_SOCKET
, igmp
);
812 void igmp_sock_delete(struct igmp_sock
*igmp
)
814 struct pim_interface
*pim_ifp
;
815 struct listnode
*grp_node
;
816 struct listnode
*grp_nextnode
;
817 struct igmp_group
*grp
;
819 for (ALL_LIST_ELEMENTS(igmp
->igmp_group_list
, grp_node
, grp_nextnode
,
821 igmp_group_delete(grp
);
826 pim_ifp
= igmp
->interface
->info
;
828 listnode_delete(pim_ifp
->igmp_socket_list
, igmp
);
830 igmp_sock_free(igmp
);
833 void igmp_sock_delete_all(struct interface
*ifp
)
835 struct pim_interface
*pim_ifp
;
836 struct listnode
*igmp_node
, *igmp_nextnode
;
837 struct igmp_sock
*igmp
;
841 for (ALL_LIST_ELEMENTS(pim_ifp
->igmp_socket_list
, igmp_node
,
842 igmp_nextnode
, igmp
)) {
843 igmp_sock_delete(igmp
);
847 static unsigned int igmp_group_hash_key(const void *arg
)
849 const struct igmp_group
*group
= arg
;
851 return jhash_1word(group
->group_addr
.s_addr
, 0);
854 static bool igmp_group_hash_equal(const void *arg1
, const void *arg2
)
856 const struct igmp_group
*g1
= (const struct igmp_group
*)arg1
;
857 const struct igmp_group
*g2
= (const struct igmp_group
*)arg2
;
859 if (g1
->group_addr
.s_addr
== g2
->group_addr
.s_addr
)
865 static struct igmp_sock
*igmp_sock_new(int fd
, struct in_addr ifaddr
,
866 struct interface
*ifp
, int mtrace_only
)
868 struct pim_interface
*pim_ifp
;
869 struct igmp_sock
*igmp
;
874 if (PIM_DEBUG_IGMP_TRACE
) {
876 "Creating IGMP socket fd=%d for address %s on interface %s",
877 fd
, inet_ntoa(ifaddr
), ifp
->name
);
880 igmp
= XCALLOC(MTYPE_PIM_IGMP_SOCKET
, sizeof(*igmp
));
882 igmp
->igmp_group_list
= list_new();
883 igmp
->igmp_group_list
->del
= (void (*)(void *))igmp_group_free
;
885 snprintf(hash_name
, 64, "IGMP %s hash", ifp
->name
);
886 igmp
->igmp_group_hash
= hash_create(igmp_group_hash_key
,
887 igmp_group_hash_equal
, hash_name
);
890 igmp
->interface
= ifp
;
891 igmp
->ifaddr
= ifaddr
;
892 igmp
->t_igmp_read
= NULL
;
893 igmp
->t_igmp_query_timer
= NULL
;
894 igmp
->t_other_querier_timer
= NULL
; /* no other querier present */
895 igmp
->querier_robustness_variable
=
896 pim_ifp
->igmp_default_robustness_variable
;
897 igmp
->sock_creation
= pim_time_monotonic_sec();
899 igmp_stats_init(&igmp
->rx_stats
);
902 igmp
->mtrace_only
= mtrace_only
;
906 igmp
->mtrace_only
= false;
909 igmp_startup_mode_on() will reset QQI:
911 igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
913 igmp_startup_mode_on(igmp
);
914 pim_igmp_general_query_on(igmp
);
919 static void igmp_read_on(struct igmp_sock
*igmp
);
921 static int pim_igmp_read(struct thread
*t
)
924 struct igmp_sock
*igmp
= (struct igmp_sock
*)THREAD_ARG(t
);
925 struct sockaddr_in from
;
926 struct sockaddr_in to
;
927 socklen_t fromlen
= sizeof(from
);
928 socklen_t tolen
= sizeof(to
);
929 ifindex_t ifindex
= -1;
933 len
= pim_socket_recvfromto(igmp
->fd
, buf
, sizeof(buf
), &from
,
934 &fromlen
, &to
, &tolen
, &ifindex
);
938 if (errno
== EWOULDBLOCK
|| errno
== EAGAIN
)
950 static void igmp_read_on(struct igmp_sock
*igmp
)
953 if (PIM_DEBUG_IGMP_TRACE_DETAIL
) {
954 zlog_debug("Scheduling READ event on IGMP socket fd=%d",
957 igmp
->t_igmp_read
= NULL
;
958 thread_add_read(router
->master
, pim_igmp_read
, igmp
, igmp
->fd
,
962 struct igmp_sock
*pim_igmp_sock_add(struct list
*igmp_sock_list
,
963 struct in_addr ifaddr
,
964 struct interface
*ifp
,
967 struct pim_interface
*pim_ifp
;
968 struct igmp_sock
*igmp
;
973 fd
= igmp_sock_open(ifaddr
, ifp
, pim_ifp
->options
);
975 zlog_warn("Could not open IGMP socket for %s on %s",
976 inet_ntoa(ifaddr
), ifp
->name
);
980 igmp
= igmp_sock_new(fd
, ifaddr
, ifp
, mtrace_only
);
984 listnode_add(igmp_sock_list
, igmp
);
986 #ifdef IGMP_SOCK_DUMP
987 igmp_sock_dump(igmp_sock_array
);
994 RFC 3376: 6.5. Switching Router Filter-Modes
996 When a router's filter-mode for a group is EXCLUDE and the group
997 timer expires, the router filter-mode for the group transitions to
1000 A router uses source records with running source timers as its state
1001 for the switch to a filter-mode of INCLUDE. If there are any source
1002 records with source timers greater than zero (i.e., requested to be
1003 forwarded), a router switches to filter-mode of INCLUDE using those
1004 source records. Source records whose timers are zero (from the
1005 previous EXCLUDE mode) are deleted.
1007 static int igmp_group_timer(struct thread
*t
)
1009 struct igmp_group
*group
;
1011 group
= THREAD_ARG(t
);
1013 if (PIM_DEBUG_IGMP_TRACE
) {
1014 char group_str
[INET_ADDRSTRLEN
];
1015 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1017 zlog_debug("%s: Timer for group %s on interface %s",
1018 __PRETTY_FUNCTION__
, group_str
,
1019 group
->group_igmp_sock
->interface
->name
);
1022 zassert(group
->group_filtermode_isexcl
);
1024 group
->group_filtermode_isexcl
= 0;
1026 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1027 igmp_anysource_forward_stop(group
);
1029 igmp_source_delete_expired(group
->group_source_list
);
1031 zassert(!group
->group_filtermode_isexcl
);
1034 RFC 3376: 6.2.2. Definition of Group Timers
1036 If there are no more source records for the group, delete group
1039 if (listcount(group
->group_source_list
) < 1) {
1040 igmp_group_delete_empty_include(group
);
1046 static void group_timer_off(struct igmp_group
*group
)
1048 if (!group
->t_group_timer
)
1051 if (PIM_DEBUG_IGMP_TRACE
) {
1052 char group_str
[INET_ADDRSTRLEN
];
1053 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1055 zlog_debug("Cancelling TIMER event for group %s on %s",
1056 group_str
, group
->group_igmp_sock
->interface
->name
);
1058 THREAD_OFF(group
->t_group_timer
);
1061 void igmp_group_timer_on(struct igmp_group
*group
, long interval_msec
,
1064 group_timer_off(group
);
1066 if (PIM_DEBUG_IGMP_EVENTS
) {
1067 char group_str
[INET_ADDRSTRLEN
];
1068 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1071 "Scheduling %ld.%03ld sec TIMER event for group %s on %s",
1072 interval_msec
/ 1000, interval_msec
% 1000, group_str
,
1077 RFC 3376: 6.2.2. Definition of Group Timers
1079 The group timer is only used when a group is in EXCLUDE mode and
1080 it represents the time for the *filter-mode* of the group to
1081 expire and switch to INCLUDE mode.
1083 zassert(group
->group_filtermode_isexcl
);
1085 thread_add_timer_msec(router
->master
, igmp_group_timer
, group
,
1086 interval_msec
, &group
->t_group_timer
);
1089 struct igmp_group
*find_group_by_addr(struct igmp_sock
*igmp
,
1090 struct in_addr group_addr
)
1092 struct igmp_group lookup
;
1094 lookup
.group_addr
.s_addr
= group_addr
.s_addr
;
1096 return hash_lookup(igmp
->igmp_group_hash
, &lookup
);
1099 struct igmp_group
*igmp_add_group_by_addr(struct igmp_sock
*igmp
,
1100 struct in_addr group_addr
)
1102 struct igmp_group
*group
;
1104 group
= find_group_by_addr(igmp
, group_addr
);
1109 if (!pim_is_group_224_4(group_addr
)) {
1110 zlog_warn("%s: Group Specified is not part of 224.0.0.0/4",
1111 __PRETTY_FUNCTION__
);
1115 if (pim_is_group_224_0_0_0_24(group_addr
)) {
1116 if (PIM_DEBUG_IGMP_TRACE
)
1118 "%s: Group specified %s is part of 224.0.0.0/24",
1119 __PRETTY_FUNCTION__
, inet_ntoa(group_addr
));
1123 Non-existant group is created as INCLUDE {empty}:
1125 RFC 3376 - 5.1. Action on Change of Interface State
1127 If no interface state existed for that multicast address before
1128 the change (i.e., the change consisted of creating a new
1129 per-interface record), or if no state exists after the change
1130 (i.e., the change consisted of deleting a per-interface record),
1131 then the "non-existent" state is considered to have a filter mode
1132 of INCLUDE and an empty source list.
1135 group
= XCALLOC(MTYPE_PIM_IGMP_GROUP
, sizeof(*group
));
1137 group
->group_source_list
= list_new();
1138 group
->group_source_list
->del
= (void (*)(void *))igmp_source_free
;
1140 group
->t_group_timer
= NULL
;
1141 group
->t_group_query_retransmit_timer
= NULL
;
1142 group
->group_specific_query_retransmit_count
= 0;
1143 group
->group_addr
= group_addr
;
1144 group
->group_igmp_sock
= igmp
;
1145 group
->last_igmp_v1_report_dsec
= -1;
1146 group
->last_igmp_v2_report_dsec
= -1;
1147 group
->group_creation
= pim_time_monotonic_sec();
1148 group
->igmp_version
= IGMP_DEFAULT_VERSION
;
1150 /* initialize new group as INCLUDE {empty} */
1151 group
->group_filtermode_isexcl
= 0; /* 0=INCLUDE, 1=EXCLUDE */
1153 listnode_add(igmp
->igmp_group_list
, group
);
1154 group
= hash_get(igmp
->igmp_group_hash
, group
, hash_alloc_intern
);
1156 if (PIM_DEBUG_IGMP_TRACE
) {
1157 char group_str
[INET_ADDRSTRLEN
];
1158 pim_inet4_dump("<group?>", group
->group_addr
, group_str
,
1161 "Creating new IGMP group %s on socket %d interface %s",
1162 group_str
, igmp
->fd
, igmp
->interface
->name
);
1166 RFC 3376: 6.2.2. Definition of Group Timers
1168 The group timer is only used when a group is in EXCLUDE mode and
1169 it represents the time for the *filter-mode* of the group to
1170 expire and switch to INCLUDE mode.
1172 zassert(!group
->group_filtermode_isexcl
); /* INCLUDE mode */
1173 zassert(!group
->t_group_timer
); /* group timer == 0 */
1175 /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
1176 igmp_anysource_forward_stop(group
);
1181 void igmp_send_query(int igmp_version
, struct igmp_group
*group
, int fd
,
1182 const char *ifname
, char *query_buf
, int query_buf_size
,
1183 int num_sources
, struct in_addr dst_addr
,
1184 struct in_addr group_addr
,
1185 int query_max_response_time_dsec
, uint8_t s_flag
,
1186 uint8_t querier_robustness_variable
,
1187 uint16_t querier_query_interval
)
1189 if (igmp_version
== 3) {
1190 igmp_v3_send_query(group
, fd
, ifname
, query_buf
, query_buf_size
,
1191 num_sources
, dst_addr
, group_addr
,
1192 query_max_response_time_dsec
, s_flag
,
1193 querier_robustness_variable
,
1194 querier_query_interval
);
1195 } else if (igmp_version
== 2) {
1196 igmp_v2_send_query(group
, fd
, ifname
, query_buf
, dst_addr
,
1197 group_addr
, query_max_response_time_dsec
);
1201 void igmp_send_query_on_intf(struct interface
*ifp
, int igmp_ver
)
1203 struct pim_interface
*pim_ifp
= ifp
->info
;
1204 struct listnode
*sock_node
= NULL
;
1205 struct igmp_sock
*igmp
= NULL
;
1206 struct in_addr dst_addr
;
1207 struct in_addr group_addr
;
1214 query_buf_size
= PIM_IGMP_BUFSIZE_WRITE
;
1216 query_buf_size
= IGMP_V12_MSG_SIZE
;
1218 dst_addr
.s_addr
= htonl(INADDR_ALLHOSTS_GROUP
);
1219 group_addr
.s_addr
= PIM_NET_INADDR_ANY
;
1221 if (PIM_DEBUG_IGMP_TRACE
)
1222 zlog_debug("Issuing general query on request on %s",
1225 for (ALL_LIST_ELEMENTS_RO(pim_ifp
->igmp_socket_list
, sock_node
, igmp
)) {
1227 char query_buf
[query_buf_size
];
1229 igmp_send_query(igmp_ver
, 0 /* igmp_group */, igmp
->fd
,
1230 igmp
->interface
->name
, query_buf
,
1231 sizeof(query_buf
), 0 /* num_sources */,
1232 dst_addr
, group_addr
,
1233 pim_ifp
->igmp_query_max_response_time_dsec
,
1234 1 /* s_flag: always set for general queries */,
1235 igmp
->querier_robustness_variable
,
1236 igmp
->querier_query_interval
);