diff options
-rw-r--r-- | CHANGES | 2 | ||||
-rw-r--r-- | configure.in | 3 | ||||
-rw-r--r-- | include/apr_network_io.h | 57 | ||||
-rw-r--r-- | network_io/unix/multicast.c | 295 |
4 files changed, 357 insertions, 0 deletions
@@ -1,5 +1,7 @@ Changes for APR 1.1.0 + *) Add APR Multicast Functions [Paul Querna] + *) Add a build script to create a solaris package. [Graham Leggett] *) Add support for APR_TCP_DEFER_ACCEPT. diff --git a/configure.in b/configure.in index b120ebe28..1326b645e 100644 --- a/configure.in +++ b/configure.in @@ -1898,6 +1898,9 @@ AC_SUBST(have_sctp) AC_CHECK_FUNCS(set_h_errno) +dnl Used in the Multicast Code +AC_CHECK_FUNCS(getifaddrs) + echo "${nl}Checking for IPv6 Networking support..." dnl Start of checking for IPv6 support... diff --git a/include/apr_network_io.h b/include/apr_network_io.h index 97c18330d..cf3679b23 100644 --- a/include/apr_network_io.h +++ b/include/apr_network_io.h @@ -747,6 +747,63 @@ APR_DECLARE_INHERIT_SET(socket); */ APR_DECLARE_INHERIT_UNSET(socket); +/** + * @defgroup apr_mcast IP Multicast + * @{ + */ + +/** + * Join a Multicast Group + * @param sock The socket to join a multicast group + * @param join The address of the multicast group to join + * @param iface Address of the interface to use. If NULL is passed, the + * default multicast interface will be used. (OS Dependent) + */ +APR_DECLARE(apr_status_t) apr_mcast_join(apr_socket_t *sock, + apr_sockaddr_t *join, + apr_sockaddr_t *iface); + +/** + * Leave a Multicast Group. All arguments must be the same as + * apr_mcast_join. + * @param sock The socket to leave a multicast group + * @param leave The address of the multicast group to leave + * @param iface Address of the interface to use. If NULL is passed, the + * default multicast interface will be used. (OS Dependent) + */ +APR_DECLARE(apr_status_t) apr_mcast_leave(apr_socket_t *sock, + apr_sockaddr_t *leave, + apr_sockaddr_t *iface); + +/** + * Set the Multicast Time to Live (ttl) for a multicast transmission. + * @param sock The socket to set the multicast ttl + * @param ttl Time to live to Assign. 0-255, default=1 + * @remark If the TTL is 0, packets will only be seen by sockets on + * the local machine, and only when multicast loopback is enabled. + */ +APR_DECLARE(apr_status_t) apr_mcast_hops(apr_socket_t *sock, + apr_byte_t ttl); + +/** + * Toggle IP Multicast Loopback + * @param sock The socket to set multicast loopback + * @param opt 0=disable, 1=enable + */ +APR_DECLARE(apr_status_t) apr_mcast_loopback(apr_socket_t *sock, + apr_byte_t opt); + + +/** + * Set the Interface to be used for outgoing Multicast Transmissions. + * @param sock The socket to set the multicast interface on + * @param iface Address of the interface to use for Multicast + */ +APR_DECLARE(apr_status_t) apr_mcast_interface(apr_socket_t *sock, + apr_sockaddr_t *iface); + +/** @} */ + /** @} */ #ifdef __cplusplus 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 +} |