1 /* $OpenBSD: igmp.c,v 1.6 2024/08/21 09:18:47 florian 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
send_igmp_query(struct iface * iface,struct group * group)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_pton(AF_INET, AllSystems, &dst.sin_addr);
82
83 ret = send_packet(iface, buf, &dst);
84 ibuf_free(buf);
85 return (ret);
86 }
87
88 void
recv_igmp_query(struct iface * iface,struct in_addr src,char * buf,u_int16_t len)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
recv_igmp_report(struct iface * iface,struct in_addr src,char * buf,u_int16_t len,u_int8_t type)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
recv_igmp_leave(struct iface * iface,struct in_addr src,char * buf,u_int16_t len)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
igmp_chksum(struct igmp_hdr * igmp_hdr)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