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