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