]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - lib/libc/net/sourcefilter.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / lib / libc / net / sourcefilter.c
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
60 union 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 };
71 typedef 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  */
82 static 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
129 out:
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  */
141 int
142 setipv4sourcefilter(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  */
211 int
212 getipv4sourcefilter(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  */
276 int
277 setsourcefilter(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  */
333 int
334 getsourcefilter(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         int                      err, level, nsrcs, optlen, 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 }