summaryrefslogtreecommitdiff
path: root/usr.sbin/dvmrpd/igmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/dvmrpd/igmp.c')
-rw-r--r--usr.sbin/dvmrpd/igmp.c242
1 files changed, 242 insertions, 0 deletions
diff --git a/usr.sbin/dvmrpd/igmp.c b/usr.sbin/dvmrpd/igmp.c
new file mode 100644
index 00000000000..da0f3e3d0b7
--- /dev/null
+++ b/usr.sbin/dvmrpd/igmp.c
@@ -0,0 +1,242 @@
+/* $OpenBSD: igmp.c,v 1.1 2006/06/01 14:12:20 norby Exp $ */
+
+/*
+ * Copyright (c) 2005, 2006 Esben Norby <norby@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+#include <event.h>
+
+#include "igmp.h"
+#include "dvmrpd.h"
+#include "dvmrp.h"
+#include "log.h"
+#include "dvmrpe.h"
+
+int igmp_chksum(struct igmp_hdr *);
+
+/* IGMP packet handling */
+int
+send_igmp_query(struct iface *iface, struct group *group)
+{
+ struct igmp_hdr igmp_hdr;
+ struct sockaddr_in dst;
+ struct buf *buf;
+ int ret = 0;
+
+ log_debug("send_igmp_query: interface %s", iface->name);
+
+ if (iface->passive)
+ return (0);
+
+ if ((buf = buf_open(iface->mtu - sizeof(struct ip))) == NULL)
+ fatal("send_igmp_query");
+
+ /* IGMP header */
+ bzero(&igmp_hdr, sizeof(igmp_hdr));
+ igmp_hdr.type = PKT_TYPE_MEMBER_QUERY;
+
+ if (group == NULL) {
+ /* general query - version is configured */
+ igmp_hdr.grp_addr = 0;
+
+ switch (iface->igmp_version) {
+ case 1:
+ break;
+ case 2:
+ igmp_hdr.max_resp_time = iface->query_resp_interval;
+ break;
+ default:
+ fatal("send_igmp_query: invalid igmp version");
+ }
+ } else {
+ /* group specific query - only version 2 */
+ igmp_hdr.grp_addr = group->addr.s_addr;
+ igmp_hdr.max_resp_time = iface->last_member_query_interval;
+ }
+
+ /* update chksum */
+ igmp_hdr.chksum = in_cksum(&igmp_hdr, sizeof(igmp_hdr));
+
+ buf_add(buf, &igmp_hdr, sizeof(igmp_hdr));
+
+ /* set destination address */
+ dst.sin_family = AF_INET;
+ dst.sin_len = sizeof(struct sockaddr_in);
+ inet_aton(AllSystems, &dst.sin_addr);
+
+ ret = send_packet(iface, buf->buf, buf->wpos, &dst);
+ buf_free(buf);
+ return (ret);
+}
+
+void
+recv_igmp_query(struct iface *iface, struct in_addr src, char *buf,
+ u_int16_t len)
+{
+ struct igmp_hdr igmp_hdr;
+ struct group *group;
+
+ log_debug("recv_igmp_query: interface %s", iface->name);
+
+ if (len < sizeof(igmp_hdr)) {
+ log_debug("recv_igmp_query: invalid IGMP report, interface %s",
+ iface->name);
+ return;
+ }
+
+ memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
+ iface->recv_query_resp_interval = igmp_hdr.max_resp_time;
+
+ /* verify chksum */
+ if (igmp_chksum(&igmp_hdr) == -1) {
+ log_debug("recv_igmp_query: invalid chksum, interface %s",
+ iface->name);
+ return;
+ }
+
+ if (src.s_addr < iface->addr.s_addr && igmp_hdr.grp_addr == 0) {
+ /* we received a general query and we lost the election */
+ if_fsm(iface, IF_EVT_QRECVD);
+ /* remember who is querier */
+ iface->querier = src;
+ return;
+ }
+
+ if (iface->state == IF_STA_NONQUERIER && igmp_hdr.grp_addr != 0) {
+ /* validate group id */
+ if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
+ log_debug("recv_igmp_query: invalid group, "
+ "interface %s", iface->name);
+ return;
+ }
+
+ if ((group = group_list_add(iface, igmp_hdr.grp_addr))
+ != NULL)
+ group_fsm(group, GRP_EVT_QUERY_RCVD);
+ }
+}
+
+void
+recv_igmp_report(struct iface *iface, struct in_addr src, char *buf,
+ u_int16_t len, u_int8_t type)
+{
+ struct igmp_hdr igmp_hdr;
+ struct group *group;
+
+ log_debug("recv_igmp_report: interface %s", iface->name);
+
+ if (len < sizeof(igmp_hdr)) {
+ log_debug("recv_igmp_report: invalid IGMP report, interface %s",
+ iface->name);
+ return;
+ }
+
+ memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
+
+ /* verify chksum */
+ if (igmp_chksum(&igmp_hdr) == -1) {
+ log_debug("recv_igmp_report: invalid chksum, interface %s",
+ iface->name);
+ return;
+ }
+
+ /* validate group id */
+ if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
+ log_debug("recv_igmp_report: invalid group, interface %s",
+ iface->name);
+ return;
+ }
+
+ if ((group = group_list_add(iface, igmp_hdr.grp_addr)) == NULL)
+ return;
+
+ if (iface->state == IF_STA_QUERIER) {
+ /* querier */
+ switch (type) {
+ case PKT_TYPE_MEMBER_REPORTv1:
+ group_fsm(group, GRP_EVT_V1_REPORT_RCVD);
+ break;
+ case PKT_TYPE_MEMBER_REPORTv2:
+ group_fsm(group, GRP_EVT_V2_REPORT_RCVD);
+ break;
+ default:
+ fatalx("recv_igmp_report: unknown IGMP report type");
+ }
+ } else {
+ /* non querier */
+ group_fsm(group, GRP_EVT_REPORT_RCVD);
+ }
+}
+
+void
+recv_igmp_leave(struct iface *iface, struct in_addr src, char *buf,
+ u_int16_t len)
+{
+ struct igmp_hdr igmp_hdr;
+ struct group *group;
+
+ log_debug("recv_igmp_leave: interface %s", iface->name);
+
+ if (iface->state != IF_STA_QUERIER)
+ return;
+
+ if (len < sizeof(igmp_hdr)) {
+ log_debug("recv_igmp_leave: invalid IGMP leave, interface %s",
+ iface->name);
+ return;
+ }
+
+ memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
+
+ /* verify chksum */
+ if (igmp_chksum(&igmp_hdr) == -1) {
+ log_debug("recv_igmp_leave: invalid chksum, interface %s",
+ iface->name);
+ return;
+ }
+
+ /* validate group id */
+ if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
+ log_debug("recv_igmp_leave: invalid group, interface %s",
+ iface->name);
+ return;
+ }
+
+ if ((group = group_list_find(iface, igmp_hdr.grp_addr)) != NULL) {
+ group_fsm(group, GRP_EVT_LEAVE_RCVD);
+ }
+}
+
+int
+igmp_chksum(struct igmp_hdr *igmp_hdr)
+{
+ u_int16_t chksum;
+
+ chksum = igmp_hdr->chksum;
+ igmp_hdr->chksum = 0;
+
+ if (chksum != in_cksum(igmp_hdr, sizeof(*igmp_hdr)))
+ return (-1);
+
+ return (0);
+}