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