summaryrefslogtreecommitdiffstats
path: root/pimd/pim_igmpv2.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_igmpv2.c')
-rw-r--r--pimd/pim_igmpv2.c265
1 files changed, 265 insertions, 0 deletions
diff --git a/pimd/pim_igmpv2.c b/pimd/pim_igmpv2.c
new file mode 100644
index 0000000..188c1bf
--- /dev/null
+++ b/pimd/pim_igmpv2.c
@@ -0,0 +1,265 @@
+/*
+ * PIM for Quagga
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Daniel Walton
+ *
+ * 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 "pimd.h"
+#include "pim_instance.h"
+#include "pim_igmp.h"
+#include "pim_igmpv2.h"
+#include "pim_igmpv3.h"
+#include "pim_ssm.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_util.h"
+
+
+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);
+ }
+}
+
+void igmp_v2_send_query(struct gm_group *group, int fd, const char *ifname,
+ char *query_buf, struct in_addr dst_addr,
+ struct in_addr group_addr,
+ int query_max_response_time_dsec)
+{
+ ssize_t msg_size = 8;
+ uint8_t max_resp_code;
+ ssize_t sent;
+ struct sockaddr_in to;
+ socklen_t tolen;
+ uint16_t checksum;
+
+ /* max_resp_code must be non-zero else this will look like an IGMP v1
+ * query */
+ /* RFC 2236: 2.2. , v2's is equal to it */
+ max_resp_code = query_max_response_time_dsec;
+ assert(max_resp_code > 0);
+
+ query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY;
+ query_buf[1] = max_resp_code;
+ *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) =
+ 0; /* for computing checksum */
+ memcpy(query_buf + 4, &group_addr, sizeof(struct in_addr));
+
+ checksum = in_cksum(query_buf, msg_size);
+ *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum;
+
+ if (PIM_DEBUG_GM_PACKETS) {
+ char dst_str[INET_ADDRSTRLEN];
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+ pim_inet4_dump("<group?>", group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("Send IGMPv2 QUERY to %s on %s for group %s",
+ dst_str, ifname, group_str);
+ }
+
+ memset(&to, 0, sizeof(to));
+ to.sin_family = AF_INET;
+ to.sin_addr = dst_addr;
+ tolen = sizeof(to);
+
+ sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT,
+ (struct sockaddr *)&to, tolen);
+ if (sent != (ssize_t)msg_size) {
+ char dst_str[INET_ADDRSTRLEN];
+ char group_str[INET_ADDRSTRLEN];
+ pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+ pim_inet4_dump("<group?>", group_addr, group_str,
+ sizeof(group_str));
+ if (sent < 0) {
+ zlog_warn(
+ "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s",
+ dst_str, ifname, group_str, msg_size, errno,
+ safe_strerror(errno));
+ } else {
+ zlog_warn(
+ "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: sent=%zd",
+ dst_str, ifname, group_str, msg_size, sent);
+ }
+ return;
+ }
+}
+
+int igmp_v2_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 in_addr group_addr;
+ struct pim_interface *pim_ifp;
+ char group_str[INET_ADDRSTRLEN];
+
+ on_trace(__func__, igmp->interface, from);
+
+ pim_ifp = ifp->info;
+
+ if (igmp->mtrace_only)
+ return 0;
+
+ if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+ if (PIM_DEBUG_GM_PACKETS)
+ zlog_debug(
+ "Recv IGMPv2 REPORT from %s on %s: size=%d other than correct=%d",
+ from_str, ifp->name, igmp_msg_len,
+ IGMP_V12_MSG_SIZE);
+ }
+
+ if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
+ zlog_warn(
+ "Recv IGMPv2 REPORT from %s on %s: size=%d with invalid checksum",
+ from_str, ifp->name, igmp_msg_len);
+ return -1;
+ }
+
+ /* Collecting IGMP Rx stats */
+ igmp->igmp_stats.report_v2++;
+
+ memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+ if (PIM_DEBUG_GM_PACKETS) {
+ pim_inet4_dump("<dst?>", group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("Recv IGMPv2 REPORT from %s on %s for %s", from_str,
+ ifp->name, group_str);
+ }
+
+ /*
+ * 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.
+ */
+ if (pim_is_grp_ssm(pim_ifp->pim, group_addr)) {
+ if (PIM_DEBUG_GM_PACKETS) {
+ zlog_debug(
+ "Ignoring IGMPv2 group record %pI4 from %s on %s exclude mode in SSM range",
+ &group_addr.s_addr, from_str, ifp->name);
+ }
+ return -1;
+ }
+
+
+ /*
+ * RFC 3376
+ * 7.3.2. In the Presence of Older Version Group Members
+ *
+ * When Group Compatibility Mode is IGMPv2, a router internally
+ * translates the following IGMPv2 messages for that group to their
+ * IGMPv3 equivalents:
+ *
+ * IGMPv2 Message IGMPv3 Equivalent
+ * -------------- -----------------
+ * Report IS_EX( {} )
+ * Leave TO_IN( {} )
+ */
+ igmpv3_report_isex(igmp, from, group_addr, 0, NULL, 1);
+
+ return 0;
+}
+
+int igmp_v2_recv_leave(struct gm_sock *igmp, struct ip *ip_hdr,
+ const char *from_str, char *igmp_msg, int igmp_msg_len)
+{
+ struct interface *ifp = igmp->interface;
+ struct in_addr group_addr;
+ char group_str[INET_ADDRSTRLEN];
+ struct in_addr from = ip_hdr->ip_src;
+
+ on_trace(__func__, igmp->interface, from);
+
+ if (igmp->mtrace_only)
+ return 0;
+
+ if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+ if (PIM_DEBUG_GM_PACKETS)
+ zlog_debug(
+ "Recv IGMPv2 LEAVE from %s on %s: size=%d other than correct=%d",
+ from_str, ifp->name, igmp_msg_len,
+ IGMP_V12_MSG_SIZE);
+ }
+
+ if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
+ zlog_warn(
+ "Recv IGMPv2 LEAVE from %s on %s with invalid checksum",
+ from_str, ifp->name);
+ return -1;
+ }
+
+
+ memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
+
+ if (PIM_DEBUG_GM_PACKETS) {
+ pim_inet4_dump("<dst?>", group_addr, group_str,
+ sizeof(group_str));
+ zlog_debug("Recv IGMPv2 LEAVE from %s on %s for %s", from_str,
+ ifp->name, group_str);
+ }
+ /*
+ * As per RFC 2236, section 9:
+ Message Type Destination Group
+ ------------ -----------------
+ General Query ALL-SYSTEMS (224.0.0.1)
+ Group-Specific Query The group being queried
+ Membership Report The group being reported
+ Leave Message ALL-ROUTERS (224.0.0.2)
+
+ Note: in older (i.e., non-standard and now obsolete) versions of
+ IGMPv2, hosts send Leave Messages to the group being left. A
+ router SHOULD accept Leave Messages addressed to the group being
+ left in the interests of backwards compatibility with such hosts.
+ In all cases, however, hosts MUST send to the ALL-ROUTERS address
+ to be compliant with this specification.
+ */
+ if ((ntohl(ip_hdr->ip_dst.s_addr) != INADDR_ALLRTRS_GROUP)
+ && (ip_hdr->ip_dst.s_addr != group_addr.s_addr)) {
+ if (PIM_DEBUG_GM_EVENTS)
+ zlog_debug(
+ "IGMPv2 Leave message is ignored since received on address other than ALL-ROUTERS or Group-address");
+ return -1;
+ }
+
+ /* Collecting IGMP Rx stats */
+ igmp->igmp_stats.leave_v2++;
+
+ /*
+ * RFC 3376
+ * 7.3.2. In the Presence of Older Version Group Members
+ *
+ * When Group Compatibility Mode is IGMPv2, a router internally
+ * translates the following IGMPv2 messages for that group to their
+ * IGMPv3 equivalents:
+ *
+ * IGMPv2 Message IGMPv3 Equivalent
+ * -------------- -----------------
+ * Report IS_EX( {} )
+ * Leave TO_IN( {} )
+ */
+ igmpv3_report_toin(igmp, from, group_addr, 0, NULL);
+
+ return 0;
+}