summaryrefslogtreecommitdiffstats
path: root/pimd/pim_igmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_igmp.c')
-rw-r--r--pimd/pim_igmp.c1550
1 files changed, 1550 insertions, 0 deletions
diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c
new file mode 100644
index 0000000..308cba0
--- /dev/null
+++ b/pimd/pim_igmp.c
@@ -0,0 +1,1550 @@
+/*
+ * PIM for Quagga
+ * Copyright (C) 2008 Everton da Silva Marques
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "memory.h"
+#include "prefix.h"
+#include "if.h"
+#include "hash.h"
+#include "jhash.h"
+#include "lib_errors.h"
+
+#include "pimd.h"
+#include "pim_instance.h"
+#include "pim_igmp.h"
+#include "pim_igmpv2.h"
+#include "pim_igmpv3.h"
+#include "pim_igmp_mtrace.h"
+#include "pim_iface.h"
+#include "pim_sock.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_time.h"
+#include "pim_ssm.h"
+#include "pim_tib.h"
+
+static void group_timer_off(struct gm_group *group);
+static void pim_igmp_general_query(struct thread *t);
+
+void igmp_anysource_forward_start(struct pim_instance *pim,
+ struct gm_group *group)
+{
+ struct gm_source *source;
+ struct in_addr src_addr = {.s_addr = 0};
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ assert(group->group_filtermode_isexcl);
+ assert(listcount(group->group_source_list) < 1);
+
+ source = igmp_get_source_by_addr(group, src_addr, NULL);
+ if (!source) {
+ zlog_warn("%s: Failure to create * source", __func__);
+ return;
+ }
+
+ igmp_source_forward_start(pim, source);
+}
+
+void igmp_anysource_forward_stop(struct gm_group *group)
+{
+ struct gm_source *source;
+ struct in_addr star = {.s_addr = 0};
+
+ source = igmp_find_source_by_addr(group, star);
+ if (source)
+ igmp_source_forward_stop(source);
+}
+
+static void igmp_source_forward_reevaluate_one(struct pim_instance *pim,
+ struct gm_source *source,
+ int is_grp_ssm)
+{
+ pim_sgaddr sg;
+ struct gm_group *group = source->source_group;
+
+ memset(&sg, 0, sizeof(sg));
+ sg.src = source->source_addr;
+ sg.grp = group->group_addr;
+
+ /** if there is no PIM state **/
+ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+ if (pim_addr_is_any(source->source_addr)) {
+ if (is_grp_ssm) {
+ if (PIM_DEBUG_PIM_EVENTS)
+ zlog_debug(
+ "local membership del for %pSG as G is now SSM",
+ &sg);
+ igmp_source_forward_stop(source);
+ }
+ } else {
+ if (!is_grp_ssm) {
+ if (PIM_DEBUG_PIM_EVENTS)
+ zlog_debug(
+ "local membership del for %pSG as G is now ASM",
+ &sg);
+ igmp_source_forward_stop(source);
+ }
+ }
+ } else {
+ if (!pim_addr_is_any(source->source_addr) && (is_grp_ssm)) {
+ if (PIM_DEBUG_PIM_EVENTS)
+ zlog_debug(
+ "local membership add for %pSG as G is now SSM",
+ &sg);
+ igmp_source_forward_start(pim, source);
+ }
+ }
+}
+
+void igmp_source_forward_reevaluate_all(struct pim_instance *pim)
+{
+ struct interface *ifp;
+
+ FOR_ALL_INTERFACES (pim->vrf, ifp) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *grpnode, *grp_nextnode;
+ struct gm_group *grp;
+ struct pim_ifchannel *ch, *ch_temp;
+
+ if (!pim_ifp)
+ continue;
+
+ /* scan igmp groups */
+ for (ALL_LIST_ELEMENTS(pim_ifp->gm_group_list, grpnode,
+ grp_nextnode, grp)) {
+ struct listnode *srcnode;
+ struct gm_source *src;
+ int is_grp_ssm;
+
+ /*
+ * RFC 4604
+ * section 2.2.1
+ * EXCLUDE mode does not apply to SSM addresses,
+ * and an SSM-aware router will ignore
+ * MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE
+ * requests in the SSM range.
+ */
+ is_grp_ssm = pim_is_grp_ssm(pim, grp->group_addr);
+ if (is_grp_ssm && grp->group_filtermode_isexcl) {
+ igmp_group_delete(grp);
+ } else {
+ /* scan group sources */
+ for (ALL_LIST_ELEMENTS_RO(
+ grp->group_source_list, srcnode,
+ src)) {
+ igmp_source_forward_reevaluate_one(
+ pim, src, is_grp_ssm);
+ } /* scan group sources */
+ }
+ } /* scan igmp groups */
+
+ RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb,
+ ch_temp) {
+ if (pim_is_grp_ssm(pim, ch->sg.grp)) {
+ if (pim_addr_is_any(ch->sg.src))
+ pim_ifchannel_delete(ch);
+ }
+ }
+ } /* scan interfaces */
+}
+
+void igmp_source_forward_start(struct pim_instance *pim,
+ struct gm_source *source)
+{
+ struct gm_group *group;
+ pim_sgaddr sg;
+
+ memset(&sg, 0, sizeof(sg));
+ sg.src = source->source_addr;
+ sg.grp = source->source_group->group_addr;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg,
+ source->source_group->interface->name,
+ IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+ }
+
+ /*
+ * PIM state should not be allowed for ASM group with valid source
+ * address.
+ */
+ if ((!pim_is_grp_ssm(pim, source->source_group->group_addr)) &&
+ !pim_addr_is_any(source->source_addr)) {
+ zlog_warn(
+ "%s: (S,G)=%pSG ASM range having source address, not allowed to create PIM state",
+ __func__, &sg);
+ return;
+ }
+
+ /* Prevent IGMP interface from installing multicast route multiple
+ times */
+ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+ return;
+ }
+
+ group = source->source_group;
+
+ if (tib_sg_gm_join(pim, sg, group->interface,
+ &source->source_channel_oil))
+ IGMP_SOURCE_DO_FORWARDING(source->source_flags);
+}
+
+/*
+ igmp_source_forward_stop: stop forwarding, but keep the source
+ igmp_source_delete: stop forwarding, and delete the source
+ */
+void igmp_source_forward_stop(struct gm_source *source)
+{
+ struct pim_interface *pim_oif;
+ struct gm_group *group;
+ pim_sgaddr sg;
+
+ memset(&sg, 0, sizeof(sg));
+ sg.src = source->source_addr;
+ sg.grp = source->source_group->group_addr;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg,
+ source->source_group->interface->name,
+ IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+ }
+
+ /* Prevent IGMP interface from removing multicast route multiple
+ times */
+ if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+ return;
+ }
+
+ group = source->source_group;
+ pim_oif = group->interface->info;
+
+ tib_sg_gm_prune(pim_oif->pim, sg, group->interface,
+ &source->source_channel_oil);
+ IGMP_SOURCE_DONT_FORWARDING(source->source_flags);
+}
+
+/* This socket is used for TXing IGMP packets only, IGMP RX happens
+ * in pim_mroute_msg()
+ */
+static int igmp_sock_open(struct in_addr ifaddr, struct interface *ifp)
+{
+ int fd;
+ int join = 0;
+ struct in_addr group;
+ struct pim_interface *pim_ifp = ifp->info;
+
+ fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifp, 1);
+
+ if (fd < 0)
+ return -1;
+
+ if (inet_aton(PIM_ALL_ROUTERS, &group)) {
+ if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex, pim_ifp))
+ ++join;
+ } else {
+ zlog_warn(
+ "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s",
+ __FILE__, __func__, fd, &ifaddr, PIM_ALL_ROUTERS, errno,
+ safe_strerror(errno));
+ }
+
+ /*
+ IGMP routers periodically send IGMP general queries to
+ AllSystems=224.0.0.1
+ IGMP routers must receive general queries for querier election.
+ */
+ if (inet_aton(PIM_ALL_SYSTEMS, &group)) {
+ if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex, pim_ifp))
+ ++join;
+ } else {
+ zlog_warn(
+ "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s",
+ __FILE__, __func__, fd, &ifaddr,
+ PIM_ALL_SYSTEMS, errno, safe_strerror(errno));
+ }
+
+ if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
+ if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex,
+ pim_ifp)) {
+ ++join;
+ }
+ } else {
+ zlog_warn(
+ "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s",
+ __FILE__, __func__, fd, &ifaddr,
+ PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno));
+ }
+
+ if (!join) {
+ flog_err_sys(
+ EC_LIB_SOCKET,
+ "IGMP socket fd=%d could not join any group on interface address %pI4",
+ fd, &ifaddr);
+ close(fd);
+ fd = -1;
+ }
+
+ return fd;
+}
+
+#undef IGMP_SOCK_DUMP
+
+#ifdef IGMP_SOCK_DUMP
+static void igmp_sock_dump(array_t *igmp_sock_array)
+{
+ int size = array_size(igmp_sock_array);
+ for (int i = 0; i < size; ++i) {
+
+ struct gm_sock *igmp = array_get(igmp_sock_array, i);
+
+ zlog_debug("%s %s: [%d/%d] igmp_addr=%pI4 fd=%d", __FILE__,
+ __func__, i, size, &igmp->ifaddr,
+ igmp->fd);
+ }
+}
+#endif
+
+struct gm_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
+ struct in_addr ifaddr)
+{
+ struct listnode *sock_node;
+ struct gm_sock *igmp;
+
+#ifdef IGMP_SOCK_DUMP
+ igmp_sock_dump(igmp_sock_list);
+#endif
+
+ for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
+ if (ifaddr.s_addr == igmp->ifaddr.s_addr)
+ return igmp;
+
+ return NULL;
+}
+
+static void pim_igmp_other_querier_expire(struct thread *t)
+{
+ struct gm_sock *igmp;
+
+ igmp = THREAD_ARG(t);
+
+ assert(!igmp->t_igmp_query_timer);
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug("%s: Querier %s resuming", __func__, ifaddr_str);
+ }
+ /* Mark the interface address as querier address */
+ igmp->querier_addr = igmp->ifaddr;
+
+ /*
+ We are the current querier, then
+ re-start sending general queries.
+ RFC 2236 - sec 7 Other Querier
+ present timer expired (Send General
+ Query, Set Gen. Query. timer)
+ */
+ pim_igmp_general_query(t);
+}
+
+void pim_igmp_other_querier_timer_on(struct gm_sock *igmp)
+{
+ long other_querier_present_interval_msec;
+ struct pim_interface *pim_ifp;
+
+ assert(igmp);
+ assert(igmp->interface);
+ assert(igmp->interface->info);
+
+ pim_ifp = igmp->interface->info;
+
+ if (igmp->t_other_querier_timer) {
+ /*
+ There is other querier present already,
+ then reset the other-querier-present timer.
+ */
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug(
+ "Querier %s resetting TIMER event for Other-Querier-Present",
+ ifaddr_str);
+ }
+ THREAD_OFF(igmp->t_other_querier_timer);
+ } else {
+ /*
+ We are the current querier, then stop sending general queries:
+ igmp->t_igmp_query_timer = NULL;
+ */
+ pim_igmp_general_query_off(igmp);
+ }
+
+ /*
+ Since this socket is starting the other-querier-present timer,
+ there should not be periodic query timer for this socket.
+ */
+ assert(!igmp->t_igmp_query_timer);
+
+ /*
+ RFC 3376: 8.5. Other Querier Present Interval
+
+ The Other Querier Present Interval is the length of time that must
+ pass before a multicast router decides that there is no longer
+ another multicast router which should be the querier. This value
+ MUST be ((the Robustness Variable) times (the Query Interval)) plus
+ (one half of one Query Response Interval).
+
+ other_querier_present_interval_msec = \
+ igmp->querier_robustness_variable * \
+ 1000 * igmp->querier_query_interval + \
+ 100 * (pim_ifp->query_max_response_time_dsec >> 1);
+ */
+ other_querier_present_interval_msec = PIM_IGMP_OQPI_MSEC(
+ igmp->querier_robustness_variable, igmp->querier_query_interval,
+ pim_ifp->gm_query_max_response_time_dsec);
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug(
+ "Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
+ ifaddr_str, other_querier_present_interval_msec / 1000,
+ other_querier_present_interval_msec % 1000);
+ }
+
+ thread_add_timer_msec(router->master, pim_igmp_other_querier_expire,
+ igmp, other_querier_present_interval_msec,
+ &igmp->t_other_querier_timer);
+}
+
+void pim_igmp_other_querier_timer_off(struct gm_sock *igmp)
+{
+ assert(igmp);
+
+ if (PIM_DEBUG_GM_TRACE) {
+ if (igmp->t_other_querier_timer) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug(
+ "IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
+ ifaddr_str, igmp->fd, igmp->interface->name);
+ }
+ }
+ THREAD_OFF(igmp->t_other_querier_timer);
+}
+
+int igmp_validate_checksum(char *igmp_msg, int igmp_msg_len)
+{
+ uint16_t recv_checksum;
+ uint16_t checksum;
+
+ IGMP_GET_INT16((unsigned char *)(igmp_msg + IGMP_CHECKSUM_OFFSET),
+ recv_checksum);
+
+ /* Clear the checksum field */
+ memset(igmp_msg + IGMP_CHECKSUM_OFFSET, 0, 2);
+
+ checksum = in_cksum(igmp_msg, igmp_msg_len);
+ if (ntohs(checksum) != recv_checksum) {
+ zlog_warn("Invalid checksum received %x, calculated %x",
+ recv_checksum, ntohs(checksum));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int igmp_recv_query(struct gm_sock *igmp, int query_version,
+ int max_resp_code, struct in_addr from,
+ const char *from_str, char *igmp_msg,
+ int igmp_msg_len)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct in_addr group_addr;
+
+ if (igmp->mtrace_only)
+ return 0;
+
+ memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+ ifp = igmp->interface;
+ pim_ifp = ifp->info;
+
+ if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
+ zlog_warn(
+ "Recv IGMP query v%d from %s on %s with invalid checksum",
+ query_version, from_str, ifp->name);
+ return -1;
+ }
+
+ if (!pim_if_connected_to_source(ifp, from)) {
+ if (PIM_DEBUG_GM_PACKETS)
+ zlog_debug("Recv IGMP query on interface: %s from a non-connected source: %s",
+ ifp->name, from_str);
+ return 0;
+ }
+
+ if (if_address_is_local(&from, AF_INET, ifp->vrf->vrf_id)) {
+ if (PIM_DEBUG_GM_PACKETS)
+ zlog_debug("Recv IGMP query on interface: %s from ourself %s",
+ ifp->name, from_str);
+ return 0;
+ }
+
+ /* Collecting IGMP Rx stats */
+ switch (query_version) {
+ case 1:
+ igmp->igmp_stats.query_v1++;
+ break;
+ case 2:
+ igmp->igmp_stats.query_v2++;
+ break;
+ case 3:
+ igmp->igmp_stats.query_v3++;
+ break;
+ default:
+ igmp->igmp_stats.unsupported++;
+ }
+
+ /*
+ * RFC 3376 defines some guidelines on operating in backwards
+ * compatibility with older versions of IGMP but there are some gaps in
+ * the logic:
+ *
+ * - once we drop from say version 3 to version 2 we will never go back
+ * to version 3 even if the node that TXed an IGMP v2 query upgrades
+ * to v3
+ *
+ * - The node with the lowest IP is the querier so we will only know to
+ * drop from v3 to v2 if the node that is the querier is also the one
+ * that is running igmp v2. If a non-querier only supports igmp v2
+ * we will have no way of knowing.
+ *
+ * For now we will simplify things and inform the user that they need to
+ * configure all PIM routers to use the same version of IGMP.
+ */
+ if (query_version != pim_ifp->igmp_version) {
+ zlog_warn(
+ "Recv IGMP query v%d from %s on %s but we are using v%d, please configure all PIM routers on this subnet to use the same IGMP version",
+ query_version, from_str, ifp->name,
+ pim_ifp->igmp_version);
+ return 0;
+ }
+
+ if (PIM_DEBUG_GM_PACKETS) {
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<group?>", group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("Recv IGMP query v%d from %s on %s for group %s",
+ query_version, from_str, ifp->name, group_str);
+ }
+
+ /*
+ RFC 3376: 6.6.2. Querier Election
+
+ When a router receives a query with a lower IP address, it sets
+ the Other-Querier-Present timer to Other Querier Present Interval
+ and ceases to send queries on the network if it was the previously
+ elected querier.
+ */
+ if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
+
+ /* As per RFC 2236 section 3:
+ * When a Querier receives a Leave Group message for a group
+ * that has group members on the reception interface, it sends
+ * [Last Member Query Count] Group-Specific Queries every [Last
+ * Member Query Interval] to the group being left. These
+ * Group-Specific Queries have their Max Response time set to
+ * [Last Member Query Interval]. If no Reports are received
+ * after the response time of the last query expires, the
+ * routers assume that the group has no local members, as above.
+ * Any Querier to non-Querier transition is ignored during this
+ * time; the same router keeps sending the Group-Specific
+ * Queries.
+ */
+ const struct gm_group *group;
+ const struct listnode *grpnode;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode,
+ group)) {
+ if (!group->t_group_query_retransmit_timer)
+ continue;
+
+ if (PIM_DEBUG_GM_TRACE)
+ zlog_debug(
+ "%s: lower address query packet from %s is ignored when last member query interval timer is running",
+ ifp->name, from_str);
+ return 0;
+ }
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug(
+ "%s: local address %s (%u) lost querier election to %s (%u)",
+ ifp->name, ifaddr_str,
+ ntohl(igmp->ifaddr.s_addr), from_str,
+ ntohl(from.s_addr));
+ }
+ /* Reset the other querier timer only if query is received from
+ * the previously elected querier or a better new querier
+ * This will make sure that non-querier elects the new querier
+ * whose ip address is higher than the old querier
+ * in case the old querier goes down via other querier present
+ * timer expiry
+ */
+ if (ntohl(from.s_addr) <= ntohl(igmp->querier_addr.s_addr)) {
+ igmp->querier_addr.s_addr = from.s_addr;
+ pim_igmp_other_querier_timer_on(igmp);
+ }
+ }
+
+ /* IGMP version 3 is the only one where we process the RXed query */
+ if (query_version == 3) {
+ igmp_v3_recv_query(igmp, from_str, igmp_msg);
+ }
+
+ return 0;
+}
+
+static void on_trace(const char *label, struct interface *ifp,
+ struct in_addr from)
+{
+ if (PIM_DEBUG_GM_TRACE) {
+ char from_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
+ zlog_debug("%s: from %s on %s", label, from_str, ifp->name);
+ }
+}
+
+static int igmp_v1_recv_report(struct gm_sock *igmp, struct in_addr from,
+ const char *from_str, char *igmp_msg,
+ int igmp_msg_len)
+{
+ struct interface *ifp = igmp->interface;
+ struct gm_group *group;
+ struct in_addr group_addr;
+
+ on_trace(__func__, igmp->interface, from);
+
+ if (igmp->mtrace_only)
+ return 0;
+
+ if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+ zlog_warn(
+ "Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
+ from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
+ return -1;
+ }
+
+ if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
+ zlog_warn(
+ "Recv IGMP report v1 from %s on %s with invalid checksum",
+ from_str, ifp->name);
+ return -1;
+ }
+
+ /* Collecting IGMP Rx stats */
+ igmp->igmp_stats.report_v1++;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ zlog_warn("%s %s: FIXME WRITEME", __FILE__, __func__);
+ }
+
+ memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+ if (pim_is_group_filtered(ifp->info, &group_addr))
+ return -1;
+
+ /* non-existent group is created as INCLUDE {empty} */
+ group = igmp_add_group_by_addr(igmp, group_addr);
+ if (!group) {
+ return -1;
+ }
+
+ group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec();
+
+ return 0;
+}
+
+bool pim_igmp_verify_header(struct ip *ip_hdr, size_t len, size_t *hlen)
+{
+ char *igmp_msg;
+ int igmp_msg_len;
+ int msg_type;
+ size_t ip_hlen; /* ip header length in bytes */
+
+ if (len < sizeof(*ip_hdr)) {
+ zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len,
+ sizeof(*ip_hdr));
+ return false;
+ }
+
+ ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
+ *hlen = ip_hlen;
+
+ if (ip_hlen > len) {
+ zlog_warn(
+ "IGMP packet header claims size %zu, but we only have %zu bytes",
+ ip_hlen, len);
+ return false;
+ }
+
+ igmp_msg = (char *)ip_hdr + ip_hlen;
+ igmp_msg_len = len - ip_hlen;
+ msg_type = *igmp_msg;
+
+ if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
+ zlog_warn("IGMP message size=%d shorter than minimum=%d",
+ igmp_msg_len, PIM_IGMP_MIN_LEN);
+ return false;
+ }
+
+ if ((msg_type != PIM_IGMP_MTRACE_RESPONSE)
+ && (msg_type != PIM_IGMP_MTRACE_QUERY_REQUEST)) {
+ if (ip_hdr->ip_ttl != 1) {
+ zlog_warn(
+ "Recv IGMP packet with invalid ttl=%u, discarding the packet",
+ ip_hdr->ip_ttl);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int pim_igmp_packet(struct gm_sock *igmp, char *buf, size_t len)
+{
+ struct ip *ip_hdr = (struct ip *)buf;
+ size_t ip_hlen; /* ip header length in bytes */
+ char *igmp_msg;
+ int igmp_msg_len;
+ int msg_type;
+ char from_str[INET_ADDRSTRLEN];
+ char to_str[INET_ADDRSTRLEN];
+
+ if (!pim_igmp_verify_header(ip_hdr, len, &ip_hlen))
+ return -1;
+
+ igmp_msg = buf + ip_hlen;
+ igmp_msg_len = len - ip_hlen;
+ msg_type = *igmp_msg;
+
+ pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str, sizeof(from_str));
+ pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str, sizeof(to_str));
+
+ if (PIM_DEBUG_GM_PACKETS) {
+ zlog_debug(
+ "Recv IGMP packet from %s to %s on %s: size=%zu ttl=%d msg_type=%d msg_size=%d",
+ from_str, to_str, igmp->interface->name, len, ip_hdr->ip_ttl,
+ msg_type, igmp_msg_len);
+ }
+
+ switch (msg_type) {
+ case PIM_IGMP_MEMBERSHIP_QUERY: {
+ int max_resp_code = igmp_msg[1];
+ int query_version;
+
+ /*
+ RFC 3376: 7.1. Query Version Distinctions
+ IGMPv1 Query: length = 8 octets AND Max Resp Code field is
+ zero
+ IGMPv2 Query: length = 8 octets AND Max Resp Code field is
+ non-zero
+ IGMPv3 Query: length >= 12 octets
+ */
+
+ if (igmp_msg_len == 8) {
+ query_version = max_resp_code ? 2 : 1;
+ } else if (igmp_msg_len >= 12) {
+ query_version = 3;
+ } else {
+ zlog_warn("Unknown IGMP query version");
+ return -1;
+ }
+
+ return igmp_recv_query(igmp, query_version, max_resp_code,
+ ip_hdr->ip_src, from_str, igmp_msg,
+ igmp_msg_len);
+ }
+
+ case PIM_IGMP_V3_MEMBERSHIP_REPORT:
+ return igmp_v3_recv_report(igmp, ip_hdr->ip_src, from_str,
+ igmp_msg, igmp_msg_len);
+
+ case PIM_IGMP_V2_MEMBERSHIP_REPORT:
+ return igmp_v2_recv_report(igmp, ip_hdr->ip_src, from_str,
+ igmp_msg, igmp_msg_len);
+
+ case PIM_IGMP_V1_MEMBERSHIP_REPORT:
+ return igmp_v1_recv_report(igmp, ip_hdr->ip_src, from_str,
+ igmp_msg, igmp_msg_len);
+
+ case PIM_IGMP_V2_LEAVE_GROUP:
+ return igmp_v2_recv_leave(igmp, ip_hdr, from_str, igmp_msg,
+ igmp_msg_len);
+
+ case PIM_IGMP_MTRACE_RESPONSE:
+ return igmp_mtrace_recv_response(igmp, ip_hdr, ip_hdr->ip_src,
+ from_str, igmp_msg,
+ igmp_msg_len);
+ case PIM_IGMP_MTRACE_QUERY_REQUEST:
+ return igmp_mtrace_recv_qry_req(igmp, ip_hdr, ip_hdr->ip_src,
+ from_str, igmp_msg,
+ igmp_msg_len);
+ }
+
+ zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
+
+ /* Collecting IGMP Rx stats */
+ igmp->igmp_stats.unsupported++;
+
+ return -1;
+}
+
+void pim_igmp_general_query_on(struct gm_sock *igmp)
+{
+ struct pim_interface *pim_ifp;
+ int startup_mode;
+ int query_interval;
+
+ /*
+ Since this socket is starting as querier,
+ there should not exist a timer for other-querier-present.
+ */
+ assert(!igmp->t_other_querier_timer);
+ pim_ifp = igmp->interface->info;
+ assert(pim_ifp);
+
+ /*
+ RFC 3376: 8.6. Startup Query Interval
+
+ The Startup Query Interval is the interval between General Queries
+ sent by a Querier on startup. Default: 1/4 the Query Interval.
+ The first one should be sent out immediately instead of 125/4
+ seconds from now.
+ */
+ startup_mode = igmp->startup_query_count > 0;
+ if (startup_mode) {
+ /*
+ * If this is the first time we are sending a query on a
+ * newly configured igmp interface send it out in 1 second
+ * just to give the entire world a tiny bit of time to settle
+ * else the query interval is:
+ * query_interval = pim_ifp->gm_default_query_interval >> 2;
+ */
+ if (igmp->startup_query_count ==
+ igmp->querier_robustness_variable)
+ query_interval = 1;
+ else
+ query_interval = PIM_IGMP_SQI(
+ pim_ifp->gm_default_query_interval);
+
+ --igmp->startup_query_count;
+ } else {
+ query_interval = igmp->querier_query_interval;
+ }
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug(
+ "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
+ ifaddr_str, query_interval,
+ startup_mode ? "startup" : "non-startup", igmp->fd);
+ }
+ thread_add_timer(router->master, pim_igmp_general_query, igmp,
+ query_interval, &igmp->t_igmp_query_timer);
+}
+
+void pim_igmp_general_query_off(struct gm_sock *igmp)
+{
+ assert(igmp);
+
+ if (PIM_DEBUG_GM_TRACE) {
+ if (igmp->t_igmp_query_timer) {
+ char ifaddr_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str,
+ sizeof(ifaddr_str));
+ zlog_debug(
+ "IGMP querier %s fd=%d cancelling query TIMER event on %s",
+ ifaddr_str, igmp->fd, igmp->interface->name);
+ }
+ }
+ THREAD_OFF(igmp->t_igmp_query_timer);
+}
+
+/* Issue IGMP general query */
+static void pim_igmp_general_query(struct thread *t)
+{
+ struct gm_sock *igmp;
+ struct in_addr dst_addr;
+ struct in_addr group_addr;
+ struct pim_interface *pim_ifp;
+ int query_buf_size;
+
+ igmp = THREAD_ARG(t);
+
+ assert(igmp->interface);
+ assert(igmp->interface->info);
+
+ pim_ifp = igmp->interface->info;
+
+ if (pim_ifp->igmp_version == 3) {
+ query_buf_size = PIM_IGMP_BUFSIZE_WRITE;
+ } else {
+ query_buf_size = IGMP_V12_MSG_SIZE;
+ }
+
+ char query_buf[query_buf_size];
+
+ /*
+ RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+ In IGMPv3, General Queries are sent with an IP destination address
+ of 224.0.0.1, the all-systems multicast address. Group-Specific
+ and Group-and-Source-Specific Queries are sent with an IP
+ destination address equal to the multicast address of interest.
+ */
+
+ dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
+ group_addr.s_addr = PIM_NET_INADDR_ANY;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char querier_str[INET_ADDRSTRLEN];
+ char dst_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
+ sizeof(querier_str));
+ pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+ zlog_debug("Querier %s issuing IGMP general query to %s on %s",
+ querier_str, dst_str, igmp->interface->name);
+ }
+
+ igmp_send_query(pim_ifp->igmp_version, 0 /* igmp_group */, query_buf,
+ sizeof(query_buf), 0 /* num_sources */, dst_addr,
+ group_addr, pim_ifp->gm_query_max_response_time_dsec,
+ 1 /* s_flag: always set for general queries */, igmp);
+
+ pim_igmp_general_query_on(igmp);
+}
+
+static void sock_close(struct gm_sock *igmp)
+{
+ pim_igmp_other_querier_timer_off(igmp);
+ pim_igmp_general_query_off(igmp);
+
+ if (PIM_DEBUG_GM_TRACE_DETAIL) {
+ if (igmp->t_igmp_read) {
+ zlog_debug(
+ "Cancelling READ event on IGMP socket %pI4 fd=%d on interface %s",
+ &igmp->ifaddr, igmp->fd,
+ igmp->interface->name);
+ }
+ }
+ THREAD_OFF(igmp->t_igmp_read);
+
+ if (close(igmp->fd)) {
+ flog_err(
+ EC_LIB_SOCKET,
+ "Failure closing IGMP socket %pI4 fd=%d on interface %s: errno=%d: %s",
+ &igmp->ifaddr, igmp->fd,
+ igmp->interface->name, errno, safe_strerror(errno));
+ }
+
+ if (PIM_DEBUG_GM_TRACE_DETAIL) {
+ zlog_debug("Deleted IGMP socket %pI4 fd=%d on interface %s",
+ &igmp->ifaddr, igmp->fd,
+ igmp->interface->name);
+ }
+}
+
+void igmp_startup_mode_on(struct gm_sock *igmp)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = igmp->interface->info;
+
+ /*
+ RFC 3376: 8.7. Startup Query Count
+
+ The Startup Query Count is the number of Queries sent out on
+ startup, separated by the Startup Query Interval. Default: the
+ Robustness Variable.
+ */
+ igmp->startup_query_count = igmp->querier_robustness_variable;
+
+ /*
+ Since we're (re)starting, reset QQI to default Query Interval
+ */
+ igmp->querier_query_interval = pim_ifp->gm_default_query_interval;
+}
+
+static void igmp_group_free(struct gm_group *group)
+{
+ list_delete(&group->group_source_list);
+
+ XFREE(MTYPE_PIM_IGMP_GROUP, group);
+}
+
+static void igmp_group_count_incr(struct pim_interface *pim_ifp)
+{
+ uint32_t group_count = listcount(pim_ifp->gm_group_list);
+
+ ++pim_ifp->pim->gm_group_count;
+ if (pim_ifp->pim->gm_group_count == pim_ifp->pim->gm_watermark_limit) {
+ zlog_warn(
+ "IGMP group count reached watermark limit: %u(vrf: %s)",
+ pim_ifp->pim->gm_group_count,
+ VRF_LOGNAME(pim_ifp->pim->vrf));
+ }
+
+ if (pim_ifp->igmp_peak_group_count < group_count)
+ pim_ifp->igmp_peak_group_count = group_count;
+}
+
+static void igmp_group_count_decr(struct pim_interface *pim_ifp)
+{
+ if (pim_ifp->pim->gm_group_count == 0) {
+ zlog_warn("Cannot decrement igmp group count below 0(vrf: %s)",
+ VRF_LOGNAME(pim_ifp->pim->vrf));
+ return;
+ }
+
+ --pim_ifp->pim->gm_group_count;
+}
+
+void igmp_group_delete(struct gm_group *group)
+{
+ struct listnode *src_node;
+ struct listnode *src_nextnode;
+ struct gm_source *src;
+ struct pim_interface *pim_ifp = group->interface->info;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<group?>", group->group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("Deleting IGMP group %s from interface %s",
+ group_str, group->interface->name);
+ }
+
+ for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode,
+ src)) {
+ igmp_source_delete(src);
+ }
+
+ THREAD_OFF(group->t_group_query_retransmit_timer);
+
+ group_timer_off(group);
+ igmp_group_count_decr(pim_ifp);
+ listnode_delete(pim_ifp->gm_group_list, group);
+ hash_release(pim_ifp->gm_group_hash, group);
+
+ igmp_group_free(group);
+}
+
+void igmp_group_delete_empty_include(struct gm_group *group)
+{
+ assert(!group->group_filtermode_isexcl);
+ assert(!listcount(group->group_source_list));
+
+ igmp_group_delete(group);
+}
+
+void igmp_sock_free(struct gm_sock *igmp)
+{
+ assert(!igmp->t_igmp_read);
+ assert(!igmp->t_igmp_query_timer);
+ assert(!igmp->t_other_querier_timer);
+
+ XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
+}
+
+void igmp_sock_delete(struct gm_sock *igmp)
+{
+ struct pim_interface *pim_ifp;
+
+ sock_close(igmp);
+
+ pim_ifp = igmp->interface->info;
+
+ listnode_delete(pim_ifp->gm_socket_list, igmp);
+
+ igmp_sock_free(igmp);
+
+ if (!listcount(pim_ifp->gm_socket_list))
+ pim_igmp_if_reset(pim_ifp);
+}
+
+void igmp_sock_delete_all(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *igmp_node, *igmp_nextnode;
+ struct gm_sock *igmp;
+
+ pim_ifp = ifp->info;
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->gm_socket_list, igmp_node,
+ igmp_nextnode, igmp)) {
+ igmp_sock_delete(igmp);
+ }
+}
+
+static unsigned int igmp_group_hash_key(const void *arg)
+{
+ const struct gm_group *group = arg;
+
+ return jhash_1word(group->group_addr.s_addr, 0);
+}
+
+static bool igmp_group_hash_equal(const void *arg1, const void *arg2)
+{
+ const struct gm_group *g1 = (const struct gm_group *)arg1;
+ const struct gm_group *g2 = (const struct gm_group *)arg2;
+
+ if (g1->group_addr.s_addr == g2->group_addr.s_addr)
+ return true;
+
+ return false;
+}
+
+void pim_igmp_if_init(struct pim_interface *pim_ifp, struct interface *ifp)
+{
+ char hash_name[64];
+
+ pim_ifp->gm_socket_list = list_new();
+ pim_ifp->gm_socket_list->del = (void (*)(void *))igmp_sock_free;
+
+ pim_ifp->gm_group_list = list_new();
+ pim_ifp->gm_group_list->del = (void (*)(void *))igmp_group_free;
+
+ snprintf(hash_name, sizeof(hash_name), "IGMP %s hash", ifp->name);
+ pim_ifp->gm_group_hash = hash_create(igmp_group_hash_key,
+ igmp_group_hash_equal, hash_name);
+}
+
+void pim_igmp_if_reset(struct pim_interface *pim_ifp)
+{
+ struct listnode *grp_node, *grp_nextnode;
+ struct gm_group *grp;
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->gm_group_list, grp_node, grp_nextnode,
+ grp)) {
+ igmp_group_delete(grp);
+ }
+}
+
+void pim_igmp_if_fini(struct pim_interface *pim_ifp)
+{
+ pim_igmp_if_reset(pim_ifp);
+
+ assert(pim_ifp->gm_group_list);
+ assert(!listcount(pim_ifp->gm_group_list));
+
+ list_delete(&pim_ifp->gm_group_list);
+ hash_free(pim_ifp->gm_group_hash);
+
+ list_delete(&pim_ifp->gm_socket_list);
+}
+
+static struct gm_sock *igmp_sock_new(int fd, struct in_addr ifaddr,
+ struct interface *ifp, int mtrace_only)
+{
+ struct pim_interface *pim_ifp;
+ struct gm_sock *igmp;
+
+ pim_ifp = ifp->info;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ zlog_debug(
+ "Creating IGMP socket fd=%d for address %pI4 on interface %s",
+ fd, &ifaddr, ifp->name);
+ }
+
+ igmp = XCALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
+
+ igmp->fd = fd;
+ igmp->interface = ifp;
+ igmp->ifaddr = ifaddr;
+ igmp->querier_addr = ifaddr;
+ igmp->t_igmp_read = NULL;
+ igmp->t_igmp_query_timer = NULL;
+ igmp->t_other_querier_timer = NULL; /* no other querier present */
+ igmp->querier_robustness_variable =
+ pim_ifp->gm_default_robustness_variable;
+ igmp->sock_creation = pim_time_monotonic_sec();
+
+ igmp_stats_init(&igmp->igmp_stats);
+
+ if (mtrace_only) {
+ igmp->mtrace_only = mtrace_only;
+ return igmp;
+ }
+
+ igmp->mtrace_only = false;
+
+ /*
+ igmp_startup_mode_on() will reset QQI:
+
+ igmp->querier_query_interval = pim_ifp->gm_default_query_interval;
+ */
+ igmp_startup_mode_on(igmp);
+ pim_igmp_general_query_on(igmp);
+
+ return igmp;
+}
+
+static void igmp_read_on(struct gm_sock *igmp);
+
+static void pim_igmp_read(struct thread *t)
+{
+ uint8_t buf[10000];
+ struct gm_sock *igmp = (struct gm_sock *)THREAD_ARG(t);
+ struct sockaddr_storage from;
+ struct sockaddr_storage to;
+ socklen_t fromlen = sizeof(from);
+ socklen_t tolen = sizeof(to);
+ ifindex_t ifindex = -1;
+ int len;
+
+ while (1) {
+ len = pim_socket_recvfromto(igmp->fd, buf, sizeof(buf), &from,
+ &fromlen, &to, &tolen, &ifindex);
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ break;
+
+ goto done;
+ }
+ }
+
+done:
+ igmp_read_on(igmp);
+}
+
+static void igmp_read_on(struct gm_sock *igmp)
+{
+
+ if (PIM_DEBUG_GM_TRACE_DETAIL) {
+ zlog_debug("Scheduling READ event on IGMP socket fd=%d",
+ igmp->fd);
+ }
+ thread_add_read(router->master, pim_igmp_read, igmp, igmp->fd,
+ &igmp->t_igmp_read);
+}
+
+struct gm_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
+ struct in_addr ifaddr, struct interface *ifp,
+ bool mtrace_only)
+{
+ struct gm_sock *igmp;
+ struct sockaddr_in sin;
+ int fd;
+
+ fd = igmp_sock_open(ifaddr, ifp);
+ if (fd < 0) {
+ zlog_warn("Could not open IGMP socket for %pI4 on %s",
+ &ifaddr, ifp->name);
+ return NULL;
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr = ifaddr;
+ sin.sin_port = 0;
+ if (bind(fd, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
+ zlog_warn("Could not bind IGMP socket for %pI4 on %s: %s(%d)",
+ &ifaddr, ifp->name, strerror(errno), errno);
+ close(fd);
+
+ return NULL;
+ }
+
+ igmp = igmp_sock_new(fd, ifaddr, ifp, mtrace_only);
+
+ igmp_read_on(igmp);
+
+ listnode_add(igmp_sock_list, igmp);
+
+#ifdef IGMP_SOCK_DUMP
+ igmp_sock_dump(igmp_sock_array);
+#endif
+
+ return igmp;
+}
+
+/*
+ RFC 3376: 6.5. Switching Router Filter-Modes
+
+ When a router's filter-mode for a group is EXCLUDE and the group
+ timer expires, the router filter-mode for the group transitions to
+ INCLUDE.
+
+ A router uses source records with running source timers as its state
+ for the switch to a filter-mode of INCLUDE. If there are any source
+ records with source timers greater than zero (i.e., requested to be
+ forwarded), a router switches to filter-mode of INCLUDE using those
+ source records. Source records whose timers are zero (from the
+ previous EXCLUDE mode) are deleted.
+ */
+static void igmp_group_timer(struct thread *t)
+{
+ struct gm_group *group;
+
+ group = THREAD_ARG(t);
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<group?>", group->group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("%s: Timer for group %s on interface %s", __func__,
+ group_str, group->interface->name);
+ }
+
+ assert(group->group_filtermode_isexcl);
+
+ group->group_filtermode_isexcl = 0;
+
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ igmp_anysource_forward_stop(group);
+
+ igmp_source_delete_expired(group->group_source_list);
+
+ assert(!group->group_filtermode_isexcl);
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ If there are no more source records for the group, delete group
+ record.
+ */
+ if (listcount(group->group_source_list) < 1) {
+ igmp_group_delete_empty_include(group);
+ }
+}
+
+static void group_timer_off(struct gm_group *group)
+{
+ if (!group->t_group_timer)
+ return;
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<group?>", group->group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("Cancelling TIMER event for group %s on %s",
+ group_str, group->interface->name);
+ }
+ THREAD_OFF(group->t_group_timer);
+}
+
+void igmp_group_timer_on(struct gm_group *group, long interval_msec,
+ const char *ifname)
+{
+ group_timer_off(group);
+
+ if (PIM_DEBUG_GM_EVENTS) {
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<group?>", group->group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug(
+ "Scheduling %ld.%03ld sec TIMER event for group %s on %s",
+ interval_msec / 1000, interval_msec % 1000, group_str,
+ ifname);
+ }
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and
+ it represents the time for the *filter-mode* of the group to
+ expire and switch to INCLUDE mode.
+ */
+ assert(group->group_filtermode_isexcl);
+
+ thread_add_timer_msec(router->master, igmp_group_timer, group,
+ interval_msec, &group->t_group_timer);
+}
+
+struct gm_group *find_group_by_addr(struct gm_sock *igmp,
+ struct in_addr group_addr)
+{
+ struct gm_group lookup;
+ struct pim_interface *pim_ifp = igmp->interface->info;
+
+ lookup.group_addr.s_addr = group_addr.s_addr;
+
+ return hash_lookup(pim_ifp->gm_group_hash, &lookup);
+}
+
+struct gm_group *igmp_add_group_by_addr(struct gm_sock *igmp,
+ struct in_addr group_addr)
+{
+ struct gm_group *group;
+ struct pim_interface *pim_ifp = igmp->interface->info;
+
+ group = find_group_by_addr(igmp, group_addr);
+ if (group) {
+ return group;
+ }
+
+ if (!pim_is_group_224_4(group_addr)) {
+ zlog_warn("%s: Group Specified is not part of 224.0.0.0/4",
+ __func__);
+ return NULL;
+ }
+
+ if (pim_is_group_224_0_0_0_24(group_addr)) {
+ if (PIM_DEBUG_GM_TRACE)
+ zlog_debug(
+ "%s: Group specified %pI4 is part of 224.0.0.0/24",
+ __func__, &group_addr);
+ return NULL;
+ }
+ /*
+ Non-existant group is created as INCLUDE {empty}:
+
+ RFC 3376 - 5.1. Action on Change of Interface State
+
+ If no interface state existed for that multicast address before
+ the change (i.e., the change consisted of creating a new
+ per-interface record), or if no state exists after the change
+ (i.e., the change consisted of deleting a per-interface record),
+ then the "non-existent" state is considered to have a filter mode
+ of INCLUDE and an empty source list.
+ */
+
+ group = XCALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
+
+ group->group_source_list = list_new();
+ group->group_source_list->del = (void (*)(void *))igmp_source_free;
+
+ group->t_group_timer = NULL;
+ group->t_group_query_retransmit_timer = NULL;
+ group->group_specific_query_retransmit_count = 0;
+ group->group_addr = group_addr;
+ group->interface = igmp->interface;
+ group->last_igmp_v1_report_dsec = -1;
+ group->last_igmp_v2_report_dsec = -1;
+ group->group_creation = pim_time_monotonic_sec();
+ group->igmp_version = IGMP_DEFAULT_VERSION;
+
+ /* initialize new group as INCLUDE {empty} */
+ group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
+
+ listnode_add(pim_ifp->gm_group_list, group);
+ group = hash_get(pim_ifp->gm_group_hash, group, hash_alloc_intern);
+
+ if (PIM_DEBUG_GM_TRACE) {
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<group?>", group->group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug(
+ "Creating new IGMP group %s on socket %d interface %s",
+ group_str, igmp->fd, igmp->interface->name);
+ }
+
+ igmp_group_count_incr(pim_ifp);
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and
+ it represents the time for the *filter-mode* of the group to
+ expire and switch to INCLUDE mode.
+ */
+ assert(!group->group_filtermode_isexcl); /* INCLUDE mode */
+ assert(!group->t_group_timer); /* group timer == 0 */
+
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ igmp_anysource_forward_stop(group);
+
+ return group;
+}
+
+void igmp_send_query(int igmp_version, struct gm_group *group, char *query_buf,
+ int query_buf_size, int num_sources,
+ struct in_addr dst_addr, struct in_addr group_addr,
+ int query_max_response_time_dsec, uint8_t s_flag,
+ struct gm_sock *igmp)
+{
+ if (pim_addr_is_any(group_addr) &&
+ ntohl(dst_addr.s_addr) == INADDR_ALLHOSTS_GROUP)
+ igmp->igmp_stats.general_queries_sent++;
+ else if (group)
+ igmp->igmp_stats.group_queries_sent++;
+
+ if (igmp_version == 3) {
+ igmp_v3_send_query(group, igmp->fd, igmp->interface->name,
+ query_buf, query_buf_size, num_sources,
+ dst_addr, group_addr,
+ query_max_response_time_dsec, s_flag,
+ igmp->querier_robustness_variable,
+ igmp->querier_query_interval);
+ } else if (igmp_version == 2) {
+ igmp_v2_send_query(group, igmp->fd, igmp->interface->name,
+ query_buf, dst_addr, group_addr,
+ query_max_response_time_dsec);
+ }
+}
+
+void igmp_send_query_on_intf(struct interface *ifp, int igmp_ver)
+{
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node = NULL;
+ struct gm_sock *igmp = NULL;
+ struct in_addr dst_addr;
+ struct in_addr group_addr;
+ int query_buf_size;
+
+ if (!igmp_ver)
+ igmp_ver = 2;
+
+ if (igmp_ver == 3)
+ query_buf_size = PIM_IGMP_BUFSIZE_WRITE;
+ else
+ query_buf_size = IGMP_V12_MSG_SIZE;
+
+ dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
+ group_addr.s_addr = PIM_NET_INADDR_ANY;
+
+ if (PIM_DEBUG_GM_TRACE)
+ zlog_debug("Issuing general query on request on %s", ifp->name);
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) {
+
+ char query_buf[query_buf_size];
+
+ igmp_send_query(
+ igmp_ver, 0 /* igmp_group */, query_buf,
+ sizeof(query_buf), 0 /* num_sources */, dst_addr,
+ group_addr, pim_ifp->gm_query_max_response_time_dsec,
+ 1 /* s_flag: always set for general queries */, igmp);
+ }
+}