xref: /openbsd/usr.sbin/dvmrpd/igmp.c (revision 274d7c50)
1 /*	$OpenBSD: igmp.c,v 1.4 2015/12/07 19:14:49 mmcc 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 	/* update chksum */
77 	igmp_hdr.chksum = in_cksum(&igmp_hdr, sizeof(igmp_hdr));
78 
79 	ibuf_add(buf, &igmp_hdr, sizeof(igmp_hdr));
80 
81 	/* set destination address */
82 	dst.sin_family = AF_INET;
83 	dst.sin_len = sizeof(struct sockaddr_in);
84 	inet_aton(AllSystems, &dst.sin_addr);
85 
86 	ret = send_packet(iface, buf->buf, buf->wpos, &dst);
87 	ibuf_free(buf);
88 	return (ret);
89 }
90 
91 void
92 recv_igmp_query(struct iface *iface, struct in_addr src, char *buf,
93     u_int16_t len)
94 {
95 	struct igmp_hdr	 igmp_hdr;
96 	struct group	*group;
97 
98 	log_debug("recv_igmp_query: interface %s", iface->name);
99 
100 	if (len < sizeof(igmp_hdr)) {
101 		log_debug("recv_igmp_query: invalid IGMP report, interface %s",
102 		    iface->name);
103 		return;
104 	}
105 
106 	memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
107 	iface->recv_query_resp_interval = igmp_hdr.max_resp_time;
108 
109 	/* verify chksum */
110 	if (igmp_chksum(&igmp_hdr) == -1) {
111 		log_debug("recv_igmp_query: invalid chksum, interface %s",
112 		    iface->name);
113 		return;
114 	}
115 
116 	if (src.s_addr < iface->addr.s_addr && igmp_hdr.grp_addr == 0) {
117 		/* we received a general query and we lost the election */
118 		if_fsm(iface, IF_EVT_QRECVD);
119 		/* remember who is querier */
120 		iface->querier = src;
121 		return;
122 	}
123 
124 	if (iface->state == IF_STA_NONQUERIER && igmp_hdr.grp_addr != 0) {
125 		/* validate group id */
126 		if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
127 			log_debug("recv_igmp_query: invalid group, "
128 			    "interface %s", iface->name);
129 			return;
130 		}
131 
132 		if ((group = group_list_add(iface, igmp_hdr.grp_addr))
133 		    != NULL)
134 			group_fsm(group, GRP_EVT_QUERY_RCVD);
135 	}
136 }
137 
138 void
139 recv_igmp_report(struct iface *iface, struct in_addr src, char *buf,
140     u_int16_t len, u_int8_t type)
141 {
142 	struct igmp_hdr	 igmp_hdr;
143 	struct group	*group;
144 
145 	log_debug("recv_igmp_report: interface %s", iface->name);
146 
147 	if (len < sizeof(igmp_hdr)) {
148 		log_debug("recv_igmp_report: invalid IGMP report, interface %s",
149 		    iface->name);
150 		return;
151 	}
152 
153 	memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
154 
155 	/* verify chksum */
156 	if (igmp_chksum(&igmp_hdr) == -1) {
157 		log_debug("recv_igmp_report: invalid chksum, interface %s",
158 		    iface->name);
159 		return;
160 	}
161 
162 	/* validate group id */
163 	if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
164 		log_debug("recv_igmp_report: invalid group, interface %s",
165 		    iface->name);
166 		return;
167 	}
168 
169 	if ((group = group_list_add(iface, igmp_hdr.grp_addr)) == NULL)
170 		return;
171 
172 	if (iface->state == IF_STA_QUERIER) {
173 		/* querier */
174 		switch (type) {
175 		case PKT_TYPE_MEMBER_REPORTv1:
176 			group_fsm(group, GRP_EVT_V1_REPORT_RCVD);
177 			break;
178 		case PKT_TYPE_MEMBER_REPORTv2:
179 			group_fsm(group, GRP_EVT_V2_REPORT_RCVD);
180 			break;
181 		default:
182 			fatalx("recv_igmp_report: unknown IGMP report type");
183 		}
184 	} else {
185 		/* non querier */
186 		group_fsm(group, GRP_EVT_REPORT_RCVD);
187 	}
188 }
189 
190 void
191 recv_igmp_leave(struct iface *iface, struct in_addr src, char *buf,
192     u_int16_t len)
193 {
194 	struct igmp_hdr	 igmp_hdr;
195 	struct group	*group;
196 
197 	log_debug("recv_igmp_leave: interface %s", iface->name);
198 
199 	if (iface->state != IF_STA_QUERIER)
200 		return;
201 
202 	if (len < sizeof(igmp_hdr)) {
203 		log_debug("recv_igmp_leave: invalid IGMP leave, interface %s",
204 		    iface->name);
205 		return;
206 	}
207 
208 	memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
209 
210 	/* verify chksum */
211 	if (igmp_chksum(&igmp_hdr) == -1) {
212 		log_debug("recv_igmp_leave: invalid chksum, interface %s",
213 		    iface->name);
214 		return;
215 	}
216 
217 	/* validate group id */
218 	if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
219 		log_debug("recv_igmp_leave: invalid group, interface %s",
220 		    iface->name);
221 		return;
222 	}
223 
224 	if ((group = group_list_find(iface, igmp_hdr.grp_addr)) != NULL) {
225 		group_fsm(group, GRP_EVT_LEAVE_RCVD);
226 	}
227 }
228 
229 int
230 igmp_chksum(struct igmp_hdr *igmp_hdr)
231 {
232 	u_int16_t	chksum;
233 
234 	chksum = igmp_hdr->chksum;
235 	igmp_hdr->chksum = 0;
236 
237 	if (chksum != in_cksum(igmp_hdr, sizeof(*igmp_hdr)))
238 		return (-1);
239 
240 	return (0);
241 }
242