diff options
Diffstat (limited to 'network_io/unix/multicast.c')
-rw-r--r-- | network_io/unix/multicast.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/network_io/unix/multicast.c b/network_io/unix/multicast.c new file mode 100644 index 000000000..ea4fe6487 --- /dev/null +++ b/network_io/unix/multicast.c @@ -0,0 +1,295 @@ +/* Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_arch_networkio.h" +#include "apr_network_io.h" +#include "apr_support.h" +#include "apr_portable.h" +#include "apr_arch_inherit.h" + +#if HAVE_GETIFADDRS +#include <net/if.h> +#include <ifaddrs.h> +#endif + +/* Only UDP and Raw Sockets can be used for Multicast */ +static apr_status_t mcast_check_type(apr_socket_t* sock) +{ + int type; + apr_status_t rv; + + rv = apr_socket_type_get(sock, &type); + + if (rv != APR_SUCCESS) { + return rv; + } + else if (type == SOCK_DGRAM || type == SOCK_RAW) { + return APR_SUCCESS; + } + else { + return APR_ENOTIMPL; + } +} + +static void fill_mip_v4(struct ip_mreq *mip, apr_sockaddr_t *mcast, + apr_sockaddr_t *iface) +{ + mip->imr_multiaddr = mcast->sa.sin.sin_addr; + if (iface == NULL) { + mip->imr_interface.s_addr = INADDR_ANY; + } + else { + mip->imr_interface = iface->sa.sin.sin_addr; + } +} + +#if APR_HAVE_IPV6 +static unsigned int find_if_index(const apr_sockaddr_t *iface) +{ + unsigned int index = 0; + struct ifaddrs *ifp, *ifs; + + /** + * TODO: getifaddrs is only portable to *BSD and OS X. Using ioctl + * and SIOCGIFCONF is needed for Linux/Solaris support. + * + * There is a wrapper that takes the messy ioctl interface into + * getifaddrs. The license is acceptable, but, It is a fairly large + * chunk of code. + */ +#if HAVE_GETIFADDRS + if (getifaddrs(&ifs) != 0) { + return 0; + } + + for (ifp = ifs; ifp; ifp = ifp->ifa_next) { + if (ifp->ifa_addr != NULL && + ifp->ifa_addr->sa_family == AF_INET6) { + /* TODO: Is this correct? */ + if (memcmp(&iface->sa.sin6.sin6_addr, + &ifp->ifa_addr->sa_data[0], + sizeof(ifp->ifa_addr)) == 0) { + index = if_nametoindex(ifp->ifa_name); + break; + } + } + } + + freeifaddrs(ifs); +#endif + return index; +} + +static void fill_mip_v6(struct ipv6_mreq *mip, const apr_sockaddr_t *mcast, + const apr_sockaddr_t *iface) +{ + memcpy(&mip->ipv6mr_multiaddr, mcast->ipaddr_ptr, + sizeof(mip->ipv6mr_multiaddr)); + + if (iface == NULL) { + mip->ipv6mr_interface = 0; + } + else { + mip->ipv6mr_interface = find_if_index(iface); + } +} + +#endif + +static int sock_is_ipv4(apr_socket_t* sock) +{ + if (sock->local_addr->family == APR_INET) + return 1; + return 0; +} + +#if APR_HAVE_IPV6 +static int sock_is_ipv6(apr_socket_t* sock) +{ + if (sock->local_addr->family == APR_INET6) + return 1; + return 0; +} +#endif + +static apr_status_t do_mcast(int type, apr_socket_t *sock, + apr_sockaddr_t *mcast, apr_sockaddr_t *iface) +{ + struct ip_mreq mip4; + apr_status_t rv = APR_SUCCESS; +#if APR_HAVE_IPV6 + struct ipv6_mreq mip6; +#endif + + rv = mcast_check_type(sock); + + if (rv != APR_SUCCESS) { + return rv; + } + + if (sock_is_ipv4(sock)) { + + fill_mip_v4(&mip4, mcast, iface); + + if (setsockopt(sock->socketdes, IPPROTO_IP, type, + &mip4, sizeof(mip4)) == -1) { + rv = errno; + } + } +#if APR_HAVE_IPV6 + else if (sock_is_ipv6(sock)) { + if (type == IP_ADD_MEMBERSHIP) { + type = IPV6_JOIN_GROUP; + } + else if (type == IP_DROP_MEMBERSHIP) { + type = IPV6_LEAVE_GROUP; + } + else { + return APR_ENOTIMPL; + } + + fill_mip_v6(&mip6, mcast, iface); + + if (setsockopt(sock->socketdes, IPPROTO_IPV6, type, + &mip6, sizeof(mip6)) == -1) { + rv = errno; + } + } +#endif + else { + rv = APR_ENOTIMPL; + } + return rv; +} + +static apr_status_t do_mcast_opt(int type, apr_socket_t *sock, + apr_byte_t value) +{ + apr_status_t rv = APR_SUCCESS; + + rv = mcast_check_type(sock); + + if (rv != APR_SUCCESS) { + return rv; + } + + if (sock_is_ipv4(sock)) { + if (setsockopt(sock->socketdes, IPPROTO_IP, type, + &value, sizeof(value)) == -1) { + rv = errno; + } + } +#if APR_HAVE_IPV6 + else if (sock_is_ipv6(sock) && type == IP_MULTICAST_LOOP) { + unsigned int loopopt = value; + type = IPV6_MULTICAST_LOOP; + if (setsockopt(sock->socketdes, IPPROTO_IPV6, type, + &loopopt, sizeof(loopopt)) == -1) { + rv = errno; + } + } + else if (sock_is_ipv6(sock)) { + if (type == IP_MULTICAST_TTL) { + type = IPV6_MULTICAST_HOPS; + } + else { + return APR_ENOTIMPL; + } + + if (setsockopt(sock->socketdes, IPPROTO_IPV6, type, + &value, sizeof(value)) == -1) { + rv = errno; + } + } +#endif + else { + rv = APR_ENOTIMPL; + } + + return rv; +} + +APR_DECLARE(apr_status_t) apr_mcast_join(apr_socket_t *sock, + apr_sockaddr_t *join, + apr_sockaddr_t *iface) +{ +#ifdef IP_ADD_MEMBERSHIP + return do_mcast(IP_ADD_MEMBERSHIP, sock, join, iface); +#else + return APR_ENOTIMPL; +#endif +} + +APR_DECLARE(apr_status_t) apr_mcast_leave(apr_socket_t *sock, + apr_sockaddr_t *leave, + apr_sockaddr_t *iface) +{ +#ifdef IP_DROP_MEMBERSHIP + return do_mcast(IP_DROP_MEMBERSHIP, sock, leave, iface); +#else + return APR_ENOTIMPL; +#endif +} + +APR_DECLARE(apr_status_t) apr_mcast_hops(apr_socket_t *sock, + apr_byte_t ttl) +{ +#ifdef IP_MULTICAST_TTL + return do_mcast_opt(IP_MULTICAST_TTL, sock, ttl); +#else + return APR_ENOTIMPL; +#endif +} + +APR_DECLARE(apr_status_t) apr_mcast_loopback(apr_socket_t *sock, + apr_byte_t opt) +{ +#ifdef IP_MULTICAST_LOOP + return do_mcast_opt(IP_MULTICAST_LOOP, sock, opt); +#else + return APR_ENOTIMPL; +#endif +} + +APR_DECLARE(apr_status_t) apr_mcast_interface(apr_socket_t *sock, + apr_sockaddr_t *iface) +{ +#ifdef IP_MULTICAST_IF + apr_status_t rv = APR_SUCCESS; + + if (sock_is_ipv4(sock)) { + if (setsockopt(sock->socketdes, IPPROTO_IP, IP_MULTICAST_IF, + &iface->sa.sin.sin_addr, + sizeof(iface->sa.sin.sin_addr)) == -1) { + rv = errno; + } + } +#if APR_HAVE_IPV6 + else if (sock_is_ipv6(sock)) { + unsigned int idx = find_if_index(iface); + if (setsockopt(sock->socketdes, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &idx, sizeof(idx)) == -1) { + rv = errno; + } + } +#endif + else { + rv = APR_ENOTIMPL; + } + return rv; +#else + return APR_ENOTIMPL; +#endif +} |