xref: /freebsd/lib/libc/net/sourcefilter.c (revision 535af610)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2007-2009 Bruce Simpson.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include "namespace.h"
33 
34 #include <sys/param.h>
35 #include <sys/ioctl.h>
36 #include <sys/socket.h>
37 
38 #include <net/if_dl.h>
39 #include <net/if.h>
40 #include <netinet/in.h>
41 #include <netinet/in_systm.h>
42 #include <netinet/ip.h>
43 #include <netinet/ip_var.h>
44 
45 #include <assert.h>
46 #include <errno.h>
47 #include <ifaddrs.h>
48 #include <stdlib.h>
49 #include <string.h>
50 
51 #include "un-namespace.h"
52 
53 /*
54  * Advanced (Full-state) multicast group membership APIs [RFC3678]
55  * Currently this module assumes IPv4 support (INET) in the base system.
56  */
57 #ifndef INET
58 #define INET
59 #endif
60 
61 union sockunion {
62 	struct sockaddr_storage	ss;
63 	struct sockaddr		sa;
64 	struct sockaddr_dl	sdl;
65 #ifdef INET
66 	struct sockaddr_in	sin;
67 #endif
68 #ifdef INET6
69 	struct sockaddr_in6	sin6;
70 #endif
71 };
72 typedef union sockunion sockunion_t;
73 
74 #ifndef MIN
75 #define	MIN(a, b)	((a) < (b) ? (a) : (b))
76 #endif
77 
78 /*
79  * Internal: Map an IPv4 unicast address to an interface index.
80  * This is quite inefficient so it is recommended applications use
81  * the newer, more portable, protocol independent API.
82  */
83 static uint32_t
84 __inaddr_to_index(in_addr_t ifaddr)
85 {
86 	struct ifaddrs	*ifa;
87 	struct ifaddrs	*ifaddrs;
88 	char		*ifname;
89 	int		 ifindex;
90 	sockunion_t	*psu;
91 
92 	if (getifaddrs(&ifaddrs) < 0)
93 		return (0);
94 
95 	ifindex = 0;
96 	ifname = NULL;
97 
98 	/*
99 	 * Pass #1: Find the ifaddr entry corresponding to the
100 	 * supplied IPv4 address. We should really use the ifindex
101 	 * consistently for matches, however it is not available to
102 	 * us on this pass.
103 	 */
104 	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
105 		psu = (sockunion_t *)ifa->ifa_addr;
106 		if (psu && psu->ss.ss_family == AF_INET &&
107 		    psu->sin.sin_addr.s_addr == ifaddr) {
108 			ifname = ifa->ifa_name;
109 			break;
110 		}
111 	}
112 	if (ifname == NULL)
113 		goto out;
114 
115 	/*
116 	 * Pass #2: Find the index of the interface matching the name
117 	 * we obtained from looking up the IPv4 ifaddr in pass #1.
118 	 * There must be a better way of doing this.
119 	 */
120 	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
121 		psu = (sockunion_t *)ifa->ifa_addr;
122 		if (psu && psu->ss.ss_family == AF_LINK &&
123 		    strcmp(ifa->ifa_name, ifname) == 0) {
124 			ifindex = LLINDEX(&psu->sdl);
125 			break;
126 		}
127 	}
128 	assert(ifindex != 0);
129 
130 out:
131 	freeifaddrs(ifaddrs);
132 	return (ifindex);
133 }
134 
135 /*
136  * Set IPv4 source filter list in use on socket.
137  *
138  * Stubbed to setsourcefilter(). Performs conversion of structures which
139  * may be inefficient; applications are encouraged to use the
140  * protocol-independent API.
141  */
142 int
143 setipv4sourcefilter(int s, struct in_addr interface, struct in_addr group,
144     uint32_t fmode, uint32_t numsrc, struct in_addr *slist)
145 {
146 #ifdef INET
147 	sockunion_t	 tmpgroup;
148 	struct in_addr	*pina;
149 	sockunion_t	*psu, *tmpslist;
150 	int		 err;
151 	size_t		 i;
152 	uint32_t	 ifindex;
153 
154 	assert(s != -1);
155 
156 	tmpslist = NULL;
157 
158 	if (!IN_MULTICAST(ntohl(group.s_addr)) ||
159 	    (fmode != MCAST_INCLUDE && fmode != MCAST_EXCLUDE)) {
160 		errno = EINVAL;
161 		return (-1);
162 	}
163 
164 	ifindex = __inaddr_to_index(interface.s_addr);
165 	if (ifindex == 0) {
166 		errno = EADDRNOTAVAIL;
167 		return (-1);
168 	}
169 
170 	memset(&tmpgroup, 0, sizeof(sockunion_t));
171 	tmpgroup.sin.sin_family = AF_INET;
172 	tmpgroup.sin.sin_len = sizeof(struct sockaddr_in);
173 	tmpgroup.sin.sin_addr = group;
174 
175 	if (numsrc != 0 || slist != NULL) {
176 		tmpslist = calloc(numsrc, sizeof(sockunion_t));
177 		if (tmpslist == NULL) {
178 			errno = ENOMEM;
179 			return (-1);
180 		}
181 
182 		pina = slist;
183 		psu = tmpslist;
184 		for (i = 0; i < numsrc; i++, pina++, psu++) {
185 			psu->sin.sin_family = AF_INET;
186 			psu->sin.sin_len = sizeof(struct sockaddr_in);
187 			psu->sin.sin_addr = *pina;
188 		}
189 	}
190 
191 	err = setsourcefilter(s, ifindex, (struct sockaddr *)&tmpgroup,
192 	    sizeof(struct sockaddr_in), fmode, numsrc,
193 	    (struct sockaddr_storage *)tmpslist);
194 
195 	if (tmpslist != NULL)
196 		free(tmpslist);
197 
198 	return (err);
199 #else /* !INET */
200 	return (EAFNOSUPPORT);
201 #endif /* INET */
202 }
203 
204 /*
205  * Get IPv4 source filter list in use on socket.
206  *
207  * Stubbed to getsourcefilter(). Performs conversion of structures which
208  * may be inefficient; applications are encouraged to use the
209  * protocol-independent API.
210  * An slist of NULL may be used for guessing the required buffer size.
211  */
212 int
213 getipv4sourcefilter(int s, struct in_addr interface, struct in_addr group,
214     uint32_t *fmode, uint32_t *numsrc, struct in_addr *slist)
215 {
216 	sockunion_t	*psu, *tmpslist;
217 	sockunion_t	 tmpgroup;
218 	struct in_addr	*pina;
219 	int		 err;
220 	size_t		 i;
221 	uint32_t	 ifindex, onumsrc;
222 
223 	assert(s != -1);
224 	assert(fmode != NULL);
225 	assert(numsrc != NULL);
226 
227 	onumsrc = *numsrc;
228 	*numsrc = 0;
229 	tmpslist = NULL;
230 
231 	if (!IN_MULTICAST(ntohl(group.s_addr)) ||
232 	    (onumsrc != 0 && slist == NULL)) {
233 		errno = EINVAL;
234 		return (-1);
235 	}
236 
237 	ifindex = __inaddr_to_index(interface.s_addr);
238 	if (ifindex == 0) {
239 		errno = EADDRNOTAVAIL;
240 		return (-1);
241 	}
242 
243 	memset(&tmpgroup, 0, sizeof(sockunion_t));
244 	tmpgroup.sin.sin_family = AF_INET;
245 	tmpgroup.sin.sin_len = sizeof(struct sockaddr_in);
246 	tmpgroup.sin.sin_addr = group;
247 
248 	if (onumsrc != 0 || slist != NULL) {
249 		tmpslist = calloc(onumsrc, sizeof(sockunion_t));
250 		if (tmpslist == NULL) {
251 			errno = ENOMEM;
252 			return (-1);
253 		}
254 	}
255 
256 	err = getsourcefilter(s, ifindex, (struct sockaddr *)&tmpgroup,
257 	    sizeof(struct sockaddr_in), fmode, numsrc,
258 	    (struct sockaddr_storage *)tmpslist);
259 
260 	if (tmpslist != NULL && *numsrc != 0) {
261 		pina = slist;
262 		psu = tmpslist;
263 		for (i = 0; i < MIN(onumsrc, *numsrc); i++, psu++) {
264 			if (psu->ss.ss_family != AF_INET)
265 				continue;
266 			*pina++ = psu->sin.sin_addr;
267 		}
268 		free(tmpslist);
269 	}
270 
271 	return (err);
272 }
273 
274 /*
275  * Set protocol-independent source filter list in use on socket.
276  */
277 int
278 setsourcefilter(int s, uint32_t interface, struct sockaddr *group,
279     socklen_t grouplen, uint32_t fmode, uint32_t numsrc,
280     struct sockaddr_storage *slist)
281 {
282 	struct __msfilterreq	 msfr;
283 	sockunion_t		*psu;
284 	int			 level, optname;
285 
286 	if (fmode != MCAST_INCLUDE && fmode != MCAST_EXCLUDE) {
287 		errno = EINVAL;
288 		return (-1);
289 	}
290 
291 	psu = (sockunion_t *)group;
292 	switch (psu->ss.ss_family) {
293 #ifdef INET
294 	case AF_INET:
295 		if ((grouplen != sizeof(struct sockaddr_in) ||
296 		    !IN_MULTICAST(ntohl(psu->sin.sin_addr.s_addr)))) {
297 			errno = EINVAL;
298 			return (-1);
299 		}
300 		level = IPPROTO_IP;
301 		optname = IP_MSFILTER;
302 		break;
303 #endif
304 #ifdef INET6
305 	case AF_INET6:
306 		if (grouplen != sizeof(struct sockaddr_in6) ||
307 		    !IN6_IS_ADDR_MULTICAST(&psu->sin6.sin6_addr)) {
308 			errno = EINVAL;
309 			return (-1);
310 		}
311 		level = IPPROTO_IPV6;
312 		optname = IPV6_MSFILTER;
313 		break;
314 #endif
315 	default:
316 		errno = EAFNOSUPPORT;
317 		return (-1);
318 	}
319 
320 	memset(&msfr, 0, sizeof(msfr));
321 	msfr.msfr_ifindex = interface;
322 	msfr.msfr_fmode = fmode;
323 	msfr.msfr_nsrcs = numsrc;
324 	memcpy(&msfr.msfr_group, &psu->ss, psu->ss.ss_len);
325 	msfr.msfr_srcs = slist;		/* pointer */
326 
327 	return (_setsockopt(s, level, optname, &msfr, sizeof(msfr)));
328 }
329 
330 /*
331  * Get protocol-independent source filter list in use on socket.
332  * An slist of NULL may be used for guessing the required buffer size.
333  */
334 int
335 getsourcefilter(int s, uint32_t interface, struct sockaddr *group,
336     socklen_t grouplen, uint32_t *fmode, uint32_t *numsrc,
337     struct sockaddr_storage *slist)
338 {
339 	struct __msfilterreq	 msfr;
340 	sockunion_t		*psu;
341 	socklen_t		 optlen;
342 	int			 err, level, nsrcs, optname;
343 
344 	if (interface == 0 || group == NULL || numsrc == NULL ||
345 	    fmode == NULL) {
346 		errno = EINVAL;
347 		return (-1);
348 	}
349 
350 	nsrcs = *numsrc;
351 	*numsrc = 0;
352 	*fmode = 0;
353 
354 	psu = (sockunion_t *)group;
355 	switch (psu->ss.ss_family) {
356 #ifdef INET
357 	case AF_INET:
358 		if ((grouplen != sizeof(struct sockaddr_in) ||
359 		    !IN_MULTICAST(ntohl(psu->sin.sin_addr.s_addr)))) {
360 			errno = EINVAL;
361 			return (-1);
362 		}
363 		level = IPPROTO_IP;
364 		optname = IP_MSFILTER;
365 		break;
366 #endif
367 #ifdef INET6
368 	case AF_INET6:
369 		if (grouplen != sizeof(struct sockaddr_in6) ||
370 		    !IN6_IS_ADDR_MULTICAST(&psu->sin6.sin6_addr)) {
371 			errno = EINVAL;
372 			return (-1);
373 		}
374 		level = IPPROTO_IPV6;
375 		optname = IPV6_MSFILTER;
376 		break;
377 #endif
378 	default:
379 		errno = EAFNOSUPPORT;
380 		return (-1);
381 		break;
382 	}
383 
384 	optlen = sizeof(struct __msfilterreq);
385 	memset(&msfr, 0, optlen);
386 	msfr.msfr_ifindex = interface;
387 	msfr.msfr_fmode = 0;
388 	msfr.msfr_nsrcs = nsrcs;
389 	memcpy(&msfr.msfr_group, &psu->ss, psu->ss.ss_len);
390 
391 	/*
392 	 * msfr_srcs is a pointer to a vector of sockaddr_storage. It
393 	 * may be NULL. The kernel will always return the total number
394 	 * of filter entries for the group in msfr.msfr_nsrcs.
395 	 */
396 	msfr.msfr_srcs = slist;
397 	err = _getsockopt(s, level, optname, &msfr, &optlen);
398 	if (err == 0) {
399 		*numsrc = msfr.msfr_nsrcs;
400 		*fmode = msfr.msfr_fmode;
401 	}
402 
403 	return (err);
404 }
405