OpenBSD-4.6/usr.sbin/dvmrpd/igmp.c

Compare this file to the similar file:
Show the results in this format:

/*	$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);
}