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