xref: /openbsd/usr.sbin/dvmrpd/igmp.c (revision 2471dd62)
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