1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apr_arch_networkio.h"
18#include "apr_network_io.h"
19#include "apr_support.h"
20#include "apr_portable.h"
21#include "apr_arch_inherit.h"
22
23#ifdef HAVE_GETIFADDRS
24#include <net/if.h>
25#include <ifaddrs.h>
26#endif
27
28#ifdef HAVE_STRUCT_IPMREQ
29static void fill_mip_v4(struct ip_mreq *mip, apr_sockaddr_t *mcast,
30                        apr_sockaddr_t *iface)
31{
32    mip->imr_multiaddr = mcast->sa.sin.sin_addr;
33    if (iface == NULL) {
34        mip->imr_interface.s_addr = INADDR_ANY;
35    }
36    else {
37        mip->imr_interface = iface->sa.sin.sin_addr;
38    }
39}
40
41/* This function is only interested in AF_INET6 sockets, so a noop
42 * "return 0" implementation for the !APR_HAVE_IPV6 build is
43 * sufficient. */
44static unsigned int find_if_index(const apr_sockaddr_t *iface)
45{
46    unsigned int index = 0;
47#if defined(HAVE_GETIFADDRS) && APR_HAVE_IPV6
48    struct ifaddrs *ifp, *ifs;
49
50    /**
51     * TODO: getifaddrs is only portable to *BSD and OS X. Using ioctl
52     *       and SIOCGIFCONF is needed for Linux/Solaris support.
53     *
54     *       There is a wrapper that takes the messy ioctl interface into
55     *       getifaddrs. The license is acceptable, but, It is a fairly large
56     *       chunk of code.
57     */
58    if (getifaddrs(&ifs) != 0) {
59        return 0;
60    }
61
62    for (ifp = ifs; ifp; ifp = ifp->ifa_next) {
63        if (ifp->ifa_addr != NULL && ifp->ifa_addr->sa_family == AF_INET6) {
64            if (memcmp(&iface->sa.sin6.sin6_addr,
65                       &((struct sockaddr_in6*)ifp->ifa_addr)->sin6_addr,
66                       sizeof(iface->sa.sin6.sin6_addr)) == 0) {
67                index = if_nametoindex(ifp->ifa_name);
68                break;
69            }
70        }
71    }
72
73    freeifaddrs(ifs);
74#endif
75    return index;
76}
77
78#if APR_HAVE_IPV6
79static void fill_mip_v6(struct ipv6_mreq *mip, const apr_sockaddr_t *mcast,
80                        const apr_sockaddr_t *iface)
81{
82    memcpy(&mip->ipv6mr_multiaddr, mcast->ipaddr_ptr,
83           sizeof(mip->ipv6mr_multiaddr));
84
85    if (iface == NULL) {
86        mip->ipv6mr_interface = 0;
87    }
88    else {
89        mip->ipv6mr_interface = find_if_index(iface);
90    }
91}
92
93#endif
94
95static int sock_is_ipv4(apr_socket_t *sock)
96{
97    if (sock->local_addr->family == APR_INET)
98        return 1;
99    return 0;
100}
101
102#if APR_HAVE_IPV6
103static int sock_is_ipv6(apr_socket_t *sock)
104{
105    if (sock->local_addr->family == APR_INET6)
106        return 1;
107    return 0;
108}
109#endif
110
111static apr_status_t do_mcast(int type, apr_socket_t *sock,
112                             apr_sockaddr_t *mcast, apr_sockaddr_t *iface,
113                             apr_sockaddr_t *source)
114{
115    struct ip_mreq mip4;
116    apr_status_t rv = APR_SUCCESS;
117#if APR_HAVE_IPV6
118    struct ipv6_mreq mip6;
119#endif
120#ifdef GROUP_FILTER_SIZE
121    struct group_source_req mip;
122    int ip_proto;
123#endif
124
125    if (source != NULL) {
126#ifdef GROUP_FILTER_SIZE
127        if (sock_is_ipv4(sock)) {
128            ip_proto = IPPROTO_IP;
129        }
130#if APR_HAVE_IPV6
131        else if (sock_is_ipv6(sock)) {
132            ip_proto = IPPROTO_IPV6;
133        }
134#endif
135        else {
136            return APR_ENOTIMPL;
137        }
138
139        if (type == IP_ADD_MEMBERSHIP)
140            type = MCAST_JOIN_SOURCE_GROUP;
141        else if (type == IP_DROP_MEMBERSHIP)
142            type = MCAST_LEAVE_SOURCE_GROUP;
143        else
144            return APR_ENOTIMPL;
145
146        mip.gsr_interface = find_if_index(iface);
147        memcpy(&mip.gsr_group, mcast->ipaddr_ptr, sizeof(mip.gsr_group));
148        memcpy(&mip.gsr_source, source->ipaddr_ptr, sizeof(mip.gsr_source));
149
150        if (setsockopt(sock->socketdes, ip_proto, type, (const void *) &mip,
151                       sizeof(mip)) == -1) {
152            rv = errno;
153        }
154#else
155        /* We do not support Source-Specific Multicast. */
156        return APR_ENOTIMPL;
157#endif
158    }
159    else {
160        if (sock_is_ipv4(sock)) {
161
162            fill_mip_v4(&mip4, mcast, iface);
163
164            if (setsockopt(sock->socketdes, IPPROTO_IP, type,
165                           (const void *) &mip4, sizeof(mip4)) == -1) {
166                rv = errno;
167            }
168        }
169#if APR_HAVE_IPV6 && defined(IPV6_JOIN_GROUP) && defined(IPV6_LEAVE_GROUP)
170        else if (sock_is_ipv6(sock)) {
171            if (type == IP_ADD_MEMBERSHIP) {
172                type = IPV6_JOIN_GROUP;
173            }
174            else if (type == IP_DROP_MEMBERSHIP) {
175                type = IPV6_LEAVE_GROUP;
176            }
177            else {
178                return APR_ENOTIMPL;
179            }
180
181            fill_mip_v6(&mip6, mcast, iface);
182
183            if (setsockopt(sock->socketdes, IPPROTO_IPV6, type,
184                           (const void *) &mip6, sizeof(mip6)) == -1) {
185                rv = errno;
186            }
187        }
188#endif
189        else {
190            rv = APR_ENOTIMPL;
191        }
192    }
193    return rv;
194}
195
196/* Set the IP_MULTICAST_TTL or IP_MULTICAST_LOOP option, or IPv6
197 * equivalents, for the socket, to the given value.  Note that this
198 * function *only works* for those particular option types. */
199static apr_status_t do_mcast_opt(int type, apr_socket_t *sock,
200                                 apr_byte_t value)
201{
202    apr_status_t rv = APR_SUCCESS;
203
204    if (sock_is_ipv4(sock)) {
205        /* For the IP_MULTICAST_* options, this must be a (char *)
206         * pointer. */
207        if (setsockopt(sock->socketdes, IPPROTO_IP, type,
208                       (const void *) &value, sizeof(value)) == -1) {
209            rv = errno;
210        }
211    }
212#if APR_HAVE_IPV6
213    else if (sock_is_ipv6(sock)) {
214        /* For the IPV6_* options, an (int *) pointer must be used. */
215        int ivalue = value;
216
217        if (type == IP_MULTICAST_TTL) {
218            type = IPV6_MULTICAST_HOPS;
219        }
220        else if (type == IP_MULTICAST_LOOP) {
221            type = IPV6_MULTICAST_LOOP;
222        }
223        else {
224            return APR_ENOTIMPL;
225        }
226
227        if (setsockopt(sock->socketdes, IPPROTO_IPV6, type,
228                       (const void *) &ivalue, sizeof(ivalue)) == -1) {
229            rv = errno;
230        }
231    }
232#endif
233    else {
234        rv = APR_ENOTIMPL;
235    }
236
237    return rv;
238}
239#endif
240
241APR_DECLARE(apr_status_t) apr_mcast_join(apr_socket_t *sock,
242                                         apr_sockaddr_t *join,
243                                         apr_sockaddr_t *iface,
244                                         apr_sockaddr_t *source)
245{
246#if defined(IP_ADD_MEMBERSHIP) && defined(HAVE_STRUCT_IPMREQ)
247    return do_mcast(IP_ADD_MEMBERSHIP, sock, join, iface, source);
248#else
249    return APR_ENOTIMPL;
250#endif
251}
252
253APR_DECLARE(apr_status_t) apr_mcast_leave(apr_socket_t *sock,
254                                          apr_sockaddr_t *addr,
255                                          apr_sockaddr_t *iface,
256                                          apr_sockaddr_t *source)
257{
258#if defined(IP_DROP_MEMBERSHIP) && defined(HAVE_STRUCT_IPMREQ)
259    return do_mcast(IP_DROP_MEMBERSHIP, sock, addr, iface, source);
260#else
261    return APR_ENOTIMPL;
262#endif
263}
264
265APR_DECLARE(apr_status_t) apr_mcast_hops(apr_socket_t *sock, apr_byte_t ttl)
266{
267#if defined(IP_MULTICAST_TTL) && defined(HAVE_STRUCT_IPMREQ)
268    return do_mcast_opt(IP_MULTICAST_TTL, sock, ttl);
269#else
270    return APR_ENOTIMPL;
271#endif
272}
273
274APR_DECLARE(apr_status_t) apr_mcast_loopback(apr_socket_t *sock,
275                                             apr_byte_t opt)
276{
277#if defined(IP_MULTICAST_LOOP) && defined(HAVE_STRUCT_IPMREQ)
278    return do_mcast_opt(IP_MULTICAST_LOOP, sock, opt);
279#else
280    return APR_ENOTIMPL;
281#endif
282}
283
284APR_DECLARE(apr_status_t) apr_mcast_interface(apr_socket_t *sock,
285                                              apr_sockaddr_t *iface)
286{
287#if defined(IP_MULTICAST_IF) && defined(HAVE_STRUCT_IPMREQ)
288    apr_status_t rv = APR_SUCCESS;
289
290    if (sock_is_ipv4(sock)) {
291        if (setsockopt(sock->socketdes, IPPROTO_IP, IP_MULTICAST_IF,
292                       (const void *) &iface->sa.sin.sin_addr,
293                       sizeof(iface->sa.sin.sin_addr)) == -1) {
294            rv = errno;
295        }
296    }
297#if APR_HAVE_IPV6
298    else if (sock_is_ipv6(sock)) {
299        unsigned int idx = find_if_index(iface);
300        if (setsockopt(sock->socketdes, IPPROTO_IPV6, IPV6_MULTICAST_IF,
301                       (const void *) &idx, sizeof(idx)) == -1) {
302            rv = errno;
303        }
304    }
305#endif
306    else {
307        rv = APR_ENOTIMPL;
308    }
309    return rv;
310#else
311    return APR_ENOTIMPL;
312#endif
313}
314