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