1 /* $OpenBSD: igmp.c,v 1.5 2023/06/26 10:08:56 claudio Exp $ */ 2 3 /* 4 * Copyright (c) 2005, 2006 Esben Norby <norby@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/socket.h> 21 #include <netinet/in.h> 22 #include <arpa/inet.h> 23 #include <sys/time.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <event.h> 27 28 #include "igmp.h" 29 #include "dvmrpd.h" 30 #include "dvmrp.h" 31 #include "log.h" 32 #include "dvmrpe.h" 33 34 int igmp_chksum(struct igmp_hdr *); 35 36 /* IGMP packet handling */ 37 int 38 send_igmp_query(struct iface *iface, struct group *group) 39 { 40 struct igmp_hdr igmp_hdr; 41 struct sockaddr_in dst; 42 struct ibuf *buf; 43 int ret = 0; 44 45 log_debug("send_igmp_query: interface %s", iface->name); 46 47 if (iface->passive) 48 return (0); 49 50 if ((buf = ibuf_open(iface->mtu - sizeof(struct ip))) == NULL) 51 fatal("send_igmp_query"); 52 53 /* IGMP header */ 54 memset(&igmp_hdr, 0, sizeof(igmp_hdr)); 55 igmp_hdr.type = PKT_TYPE_MEMBER_QUERY; 56 57 if (group == NULL) { 58 /* general query - version is configured */ 59 igmp_hdr.grp_addr = 0; 60 61 switch (iface->igmp_version) { 62 case 1: 63 break; 64 case 2: 65 igmp_hdr.max_resp_time = iface->query_resp_interval; 66 break; 67 default: 68 fatal("send_igmp_query: invalid igmp version"); 69 } 70 } else { 71 /* group specific query - only version 2 */ 72 igmp_hdr.grp_addr = group->addr.s_addr; 73 igmp_hdr.max_resp_time = iface->last_member_query_interval; 74 } 75 76 ibuf_add(buf, &igmp_hdr, sizeof(igmp_hdr)); 77 78 /* set destination address */ 79 dst.sin_family = AF_INET; 80 dst.sin_len = sizeof(struct sockaddr_in); 81 inet_aton(AllSystems, &dst.sin_addr); 82 83 ret = send_packet(iface, buf, &dst); 84 ibuf_free(buf); 85 return (ret); 86 } 87 88 void 89 recv_igmp_query(struct iface *iface, struct in_addr src, char *buf, 90 u_int16_t len) 91 { 92 struct igmp_hdr igmp_hdr; 93 struct group *group; 94 95 log_debug("recv_igmp_query: interface %s", iface->name); 96 97 if (len < sizeof(igmp_hdr)) { 98 log_debug("recv_igmp_query: invalid IGMP report, interface %s", 99 iface->name); 100 return; 101 } 102 103 memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); 104 iface->recv_query_resp_interval = igmp_hdr.max_resp_time; 105 106 /* verify chksum */ 107 if (igmp_chksum(&igmp_hdr) == -1) { 108 log_debug("recv_igmp_query: invalid chksum, interface %s", 109 iface->name); 110 return; 111 } 112 113 if (src.s_addr < iface->addr.s_addr && igmp_hdr.grp_addr == 0) { 114 /* we received a general query and we lost the election */ 115 if_fsm(iface, IF_EVT_QRECVD); 116 /* remember who is querier */ 117 iface->querier = src; 118 return; 119 } 120 121 if (iface->state == IF_STA_NONQUERIER && igmp_hdr.grp_addr != 0) { 122 /* validate group id */ 123 if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { 124 log_debug("recv_igmp_query: invalid group, " 125 "interface %s", iface->name); 126 return; 127 } 128 129 if ((group = group_list_add(iface, igmp_hdr.grp_addr)) 130 != NULL) 131 group_fsm(group, GRP_EVT_QUERY_RCVD); 132 } 133 } 134 135 void 136 recv_igmp_report(struct iface *iface, struct in_addr src, char *buf, 137 u_int16_t len, u_int8_t type) 138 { 139 struct igmp_hdr igmp_hdr; 140 struct group *group; 141 142 log_debug("recv_igmp_report: interface %s", iface->name); 143 144 if (len < sizeof(igmp_hdr)) { 145 log_debug("recv_igmp_report: invalid IGMP report, interface %s", 146 iface->name); 147 return; 148 } 149 150 memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); 151 152 /* verify chksum */ 153 if (igmp_chksum(&igmp_hdr) == -1) { 154 log_debug("recv_igmp_report: invalid chksum, interface %s", 155 iface->name); 156 return; 157 } 158 159 /* validate group id */ 160 if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { 161 log_debug("recv_igmp_report: invalid group, interface %s", 162 iface->name); 163 return; 164 } 165 166 if ((group = group_list_add(iface, igmp_hdr.grp_addr)) == NULL) 167 return; 168 169 if (iface->state == IF_STA_QUERIER) { 170 /* querier */ 171 switch (type) { 172 case PKT_TYPE_MEMBER_REPORTv1: 173 group_fsm(group, GRP_EVT_V1_REPORT_RCVD); 174 break; 175 case PKT_TYPE_MEMBER_REPORTv2: 176 group_fsm(group, GRP_EVT_V2_REPORT_RCVD); 177 break; 178 default: 179 fatalx("recv_igmp_report: unknown IGMP report type"); 180 } 181 } else { 182 /* non querier */ 183 group_fsm(group, GRP_EVT_REPORT_RCVD); 184 } 185 } 186 187 void 188 recv_igmp_leave(struct iface *iface, struct in_addr src, char *buf, 189 u_int16_t len) 190 { 191 struct igmp_hdr igmp_hdr; 192 struct group *group; 193 194 log_debug("recv_igmp_leave: interface %s", iface->name); 195 196 if (iface->state != IF_STA_QUERIER) 197 return; 198 199 if (len < sizeof(igmp_hdr)) { 200 log_debug("recv_igmp_leave: invalid IGMP leave, interface %s", 201 iface->name); 202 return; 203 } 204 205 memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); 206 207 /* verify chksum */ 208 if (igmp_chksum(&igmp_hdr) == -1) { 209 log_debug("recv_igmp_leave: invalid chksum, interface %s", 210 iface->name); 211 return; 212 } 213 214 /* validate group id */ 215 if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { 216 log_debug("recv_igmp_leave: invalid group, interface %s", 217 iface->name); 218 return; 219 } 220 221 if ((group = group_list_find(iface, igmp_hdr.grp_addr)) != NULL) { 222 group_fsm(group, GRP_EVT_LEAVE_RCVD); 223 } 224 } 225 226 int 227 igmp_chksum(struct igmp_hdr *igmp_hdr) 228 { 229 u_int16_t chksum; 230 231 chksum = igmp_hdr->chksum; 232 igmp_hdr->chksum = 0; 233 234 if (chksum != in_cksum(igmp_hdr, sizeof(*igmp_hdr))) 235 return (-1); 236 237 return (0); 238 } 239