diff options
Diffstat (limited to 'usr.sbin/dvmrpd/igmp.c')
-rw-r--r-- | usr.sbin/dvmrpd/igmp.c | 242 |
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); +} |