/* $OpenBSD: igmp.c,v 1.2 2010/05/26 13:56:07 nicm Exp $ */ /* * Copyright (c) 2005, 2006 Esben Norby * * 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 #include #include #include #include #include #include #include #include #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 ibuf *buf; int ret = 0; log_debug("send_igmp_query: interface %s", iface->name); if (iface->passive) return (0); if ((buf = ibuf_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)); ibuf_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); ibuf_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); }