summaryrefslogtreecommitdiff
path: root/network_io/unix/multicast.c
diff options
context:
space:
mode:
Diffstat (limited to 'network_io/unix/multicast.c')
-rw-r--r--network_io/unix/multicast.c295
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
+}