1*2471dd62Sflorian /* $OpenBSD: igmp.c,v 1.6 2024/08/21 09:18:47 florian Exp $ */ 2978e5cffSnorby 3978e5cffSnorby /* 4978e5cffSnorby * Copyright (c) 2005, 2006 Esben Norby <norby@openbsd.org> 5978e5cffSnorby * 6978e5cffSnorby * Permission to use, copy, modify, and distribute this software for any 7978e5cffSnorby * purpose with or without fee is hereby granted, provided that the above 8978e5cffSnorby * copyright notice and this permission notice appear in all copies. 9978e5cffSnorby * 10978e5cffSnorby * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11978e5cffSnorby * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12978e5cffSnorby * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13978e5cffSnorby * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14978e5cffSnorby * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15978e5cffSnorby * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16978e5cffSnorby * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17978e5cffSnorby */ 18978e5cffSnorby 19978e5cffSnorby #include <sys/types.h> 20978e5cffSnorby #include <sys/socket.h> 21978e5cffSnorby #include <netinet/in.h> 22978e5cffSnorby #include <arpa/inet.h> 23978e5cffSnorby #include <sys/time.h> 24978e5cffSnorby #include <stdlib.h> 25978e5cffSnorby #include <string.h> 26978e5cffSnorby #include <event.h> 27978e5cffSnorby 28978e5cffSnorby #include "igmp.h" 29978e5cffSnorby #include "dvmrpd.h" 30978e5cffSnorby #include "dvmrp.h" 31978e5cffSnorby #include "log.h" 32978e5cffSnorby #include "dvmrpe.h" 33978e5cffSnorby 34978e5cffSnorby int igmp_chksum(struct igmp_hdr *); 35978e5cffSnorby 36978e5cffSnorby /* IGMP packet handling */ 37978e5cffSnorby int 38978e5cffSnorby send_igmp_query(struct iface *iface, struct group *group) 39978e5cffSnorby { 40978e5cffSnorby struct igmp_hdr igmp_hdr; 41978e5cffSnorby struct sockaddr_in dst; 42e39620e5Snicm struct ibuf *buf; 43978e5cffSnorby int ret = 0; 44978e5cffSnorby 45978e5cffSnorby log_debug("send_igmp_query: interface %s", iface->name); 46978e5cffSnorby 47978e5cffSnorby if (iface->passive) 48978e5cffSnorby return (0); 49978e5cffSnorby 50e39620e5Snicm if ((buf = ibuf_open(iface->mtu - sizeof(struct ip))) == NULL) 51978e5cffSnorby fatal("send_igmp_query"); 52978e5cffSnorby 53978e5cffSnorby /* IGMP header */ 54b206500fSmmcc memset(&igmp_hdr, 0, sizeof(igmp_hdr)); 55978e5cffSnorby igmp_hdr.type = PKT_TYPE_MEMBER_QUERY; 56978e5cffSnorby 57978e5cffSnorby if (group == NULL) { 58978e5cffSnorby /* general query - version is configured */ 59978e5cffSnorby igmp_hdr.grp_addr = 0; 60978e5cffSnorby 61978e5cffSnorby switch (iface->igmp_version) { 62978e5cffSnorby case 1: 63978e5cffSnorby break; 64978e5cffSnorby case 2: 65978e5cffSnorby igmp_hdr.max_resp_time = iface->query_resp_interval; 66978e5cffSnorby break; 67978e5cffSnorby default: 68978e5cffSnorby fatal("send_igmp_query: invalid igmp version"); 69978e5cffSnorby } 70978e5cffSnorby } else { 71978e5cffSnorby /* group specific query - only version 2 */ 72978e5cffSnorby igmp_hdr.grp_addr = group->addr.s_addr; 73978e5cffSnorby igmp_hdr.max_resp_time = iface->last_member_query_interval; 74978e5cffSnorby } 75978e5cffSnorby 76e39620e5Snicm ibuf_add(buf, &igmp_hdr, sizeof(igmp_hdr)); 77978e5cffSnorby 78978e5cffSnorby /* set destination address */ 79978e5cffSnorby dst.sin_family = AF_INET; 80978e5cffSnorby dst.sin_len = sizeof(struct sockaddr_in); 81*2471dd62Sflorian inet_pton(AF_INET, AllSystems, &dst.sin_addr); 82978e5cffSnorby 8335f7e862Sclaudio ret = send_packet(iface, buf, &dst); 84e39620e5Snicm ibuf_free(buf); 85978e5cffSnorby return (ret); 86978e5cffSnorby } 87978e5cffSnorby 88978e5cffSnorby void 89978e5cffSnorby recv_igmp_query(struct iface *iface, struct in_addr src, char *buf, 90978e5cffSnorby u_int16_t len) 91978e5cffSnorby { 92978e5cffSnorby struct igmp_hdr igmp_hdr; 93978e5cffSnorby struct group *group; 94978e5cffSnorby 95978e5cffSnorby log_debug("recv_igmp_query: interface %s", iface->name); 96978e5cffSnorby 97978e5cffSnorby if (len < sizeof(igmp_hdr)) { 98978e5cffSnorby log_debug("recv_igmp_query: invalid IGMP report, interface %s", 99978e5cffSnorby iface->name); 100978e5cffSnorby return; 101978e5cffSnorby } 102978e5cffSnorby 103978e5cffSnorby memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); 104978e5cffSnorby iface->recv_query_resp_interval = igmp_hdr.max_resp_time; 105978e5cffSnorby 106978e5cffSnorby /* verify chksum */ 107978e5cffSnorby if (igmp_chksum(&igmp_hdr) == -1) { 108978e5cffSnorby log_debug("recv_igmp_query: invalid chksum, interface %s", 109978e5cffSnorby iface->name); 110978e5cffSnorby return; 111978e5cffSnorby } 112978e5cffSnorby 113978e5cffSnorby if (src.s_addr < iface->addr.s_addr && igmp_hdr.grp_addr == 0) { 114978e5cffSnorby /* we received a general query and we lost the election */ 115978e5cffSnorby if_fsm(iface, IF_EVT_QRECVD); 116978e5cffSnorby /* remember who is querier */ 117978e5cffSnorby iface->querier = src; 118978e5cffSnorby return; 119978e5cffSnorby } 120978e5cffSnorby 121978e5cffSnorby if (iface->state == IF_STA_NONQUERIER && igmp_hdr.grp_addr != 0) { 122978e5cffSnorby /* validate group id */ 123978e5cffSnorby if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { 124978e5cffSnorby log_debug("recv_igmp_query: invalid group, " 125978e5cffSnorby "interface %s", iface->name); 126978e5cffSnorby return; 127978e5cffSnorby } 128978e5cffSnorby 129978e5cffSnorby if ((group = group_list_add(iface, igmp_hdr.grp_addr)) 130978e5cffSnorby != NULL) 131978e5cffSnorby group_fsm(group, GRP_EVT_QUERY_RCVD); 132978e5cffSnorby } 133978e5cffSnorby } 134978e5cffSnorby 135978e5cffSnorby void 136978e5cffSnorby recv_igmp_report(struct iface *iface, struct in_addr src, char *buf, 137978e5cffSnorby u_int16_t len, u_int8_t type) 138978e5cffSnorby { 139978e5cffSnorby struct igmp_hdr igmp_hdr; 140978e5cffSnorby struct group *group; 141978e5cffSnorby 142978e5cffSnorby log_debug("recv_igmp_report: interface %s", iface->name); 143978e5cffSnorby 144978e5cffSnorby if (len < sizeof(igmp_hdr)) { 145978e5cffSnorby log_debug("recv_igmp_report: invalid IGMP report, interface %s", 146978e5cffSnorby iface->name); 147978e5cffSnorby return; 148978e5cffSnorby } 149978e5cffSnorby 150978e5cffSnorby memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); 151978e5cffSnorby 152978e5cffSnorby /* verify chksum */ 153978e5cffSnorby if (igmp_chksum(&igmp_hdr) == -1) { 154978e5cffSnorby log_debug("recv_igmp_report: invalid chksum, interface %s", 155978e5cffSnorby iface->name); 156978e5cffSnorby return; 157978e5cffSnorby } 158978e5cffSnorby 159978e5cffSnorby /* validate group id */ 160978e5cffSnorby if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { 161978e5cffSnorby log_debug("recv_igmp_report: invalid group, interface %s", 162978e5cffSnorby iface->name); 163978e5cffSnorby return; 164978e5cffSnorby } 165978e5cffSnorby 166978e5cffSnorby if ((group = group_list_add(iface, igmp_hdr.grp_addr)) == NULL) 167978e5cffSnorby return; 168978e5cffSnorby 169978e5cffSnorby if (iface->state == IF_STA_QUERIER) { 170978e5cffSnorby /* querier */ 171978e5cffSnorby switch (type) { 172978e5cffSnorby case PKT_TYPE_MEMBER_REPORTv1: 173978e5cffSnorby group_fsm(group, GRP_EVT_V1_REPORT_RCVD); 174978e5cffSnorby break; 175978e5cffSnorby case PKT_TYPE_MEMBER_REPORTv2: 176978e5cffSnorby group_fsm(group, GRP_EVT_V2_REPORT_RCVD); 177978e5cffSnorby break; 178978e5cffSnorby default: 179978e5cffSnorby fatalx("recv_igmp_report: unknown IGMP report type"); 180978e5cffSnorby } 181978e5cffSnorby } else { 182978e5cffSnorby /* non querier */ 183978e5cffSnorby group_fsm(group, GRP_EVT_REPORT_RCVD); 184978e5cffSnorby } 185978e5cffSnorby } 186978e5cffSnorby 187978e5cffSnorby void 188978e5cffSnorby recv_igmp_leave(struct iface *iface, struct in_addr src, char *buf, 189978e5cffSnorby u_int16_t len) 190978e5cffSnorby { 191978e5cffSnorby struct igmp_hdr igmp_hdr; 192978e5cffSnorby struct group *group; 193978e5cffSnorby 194978e5cffSnorby log_debug("recv_igmp_leave: interface %s", iface->name); 195978e5cffSnorby 196978e5cffSnorby if (iface->state != IF_STA_QUERIER) 197978e5cffSnorby return; 198978e5cffSnorby 199978e5cffSnorby if (len < sizeof(igmp_hdr)) { 200978e5cffSnorby log_debug("recv_igmp_leave: invalid IGMP leave, interface %s", 201978e5cffSnorby iface->name); 202978e5cffSnorby return; 203978e5cffSnorby } 204978e5cffSnorby 205978e5cffSnorby memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); 206978e5cffSnorby 207978e5cffSnorby /* verify chksum */ 208978e5cffSnorby if (igmp_chksum(&igmp_hdr) == -1) { 209978e5cffSnorby log_debug("recv_igmp_leave: invalid chksum, interface %s", 210978e5cffSnorby iface->name); 211978e5cffSnorby return; 212978e5cffSnorby } 213978e5cffSnorby 214978e5cffSnorby /* validate group id */ 215978e5cffSnorby if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { 216978e5cffSnorby log_debug("recv_igmp_leave: invalid group, interface %s", 217978e5cffSnorby iface->name); 218978e5cffSnorby return; 219978e5cffSnorby } 220978e5cffSnorby 221978e5cffSnorby if ((group = group_list_find(iface, igmp_hdr.grp_addr)) != NULL) { 222978e5cffSnorby group_fsm(group, GRP_EVT_LEAVE_RCVD); 223978e5cffSnorby } 224978e5cffSnorby } 225978e5cffSnorby 226978e5cffSnorby int 227978e5cffSnorby igmp_chksum(struct igmp_hdr *igmp_hdr) 228978e5cffSnorby { 229978e5cffSnorby u_int16_t chksum; 230978e5cffSnorby 231978e5cffSnorby chksum = igmp_hdr->chksum; 232978e5cffSnorby igmp_hdr->chksum = 0; 233978e5cffSnorby 234978e5cffSnorby if (chksum != in_cksum(igmp_hdr, sizeof(*igmp_hdr))) 235978e5cffSnorby return (-1); 236978e5cffSnorby 237978e5cffSnorby return (0); 238978e5cffSnorby } 239