diff --git a/include/uv.h b/include/uv.h index 4eeade74c4..f0ab40548f 100644 --- a/include/uv.h +++ b/include/uv.h @@ -951,6 +951,23 @@ UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership); +/* +* Set membership for a multicast IPV6 address +* +* Arguments: +* handle UDP handle. Should have been initialized with +* `uv_udp_init`. +* multicast_addr multicast address to set membership for +* interface_addr interface address +* membership Should be UV_JOIN_GROUP or UV_LEAVE_GROUP +* +* Returns: +* 0 on success, or an error code < 0 on failure. +*/ +UV_EXTERN int uv_udp_set_membership6(uv_udp_t* handle, + const char* multicast_addr, const char* interface_addr, + uv_membership membership); + /* * Set IP multicast loop flag. Makes multicast packets loop back to * local sockets. @@ -1599,6 +1616,7 @@ struct uv_interface_address_s { char* name; char phys_addr[6]; int is_internal; + uint32_t if_index; union { struct sockaddr_in address4; struct sockaddr_in6 address6; diff --git a/src/unix/internal.h b/src/unix/internal.h index 0ea82b51a0..aa5dd17e74 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -125,7 +125,8 @@ enum { UV_STREAM_READ_EOF = 0x200, /* read(2) read EOF. */ UV_TCP_NODELAY = 0x400, /* Disable Nagle. */ UV_TCP_KEEPALIVE = 0x800, /* Turn on keep-alive. */ - UV_TCP_SINGLE_ACCEPT = 0x1000 /* Only accept() when idle. */ + UV_TCP_SINGLE_ACCEPT = 0x1000, /* Only accept() when idle. */ + UV_HANDLE_IPV6 = 0x01000000 /* Indicate socket is IPv6. */ }; typedef enum { diff --git a/src/unix/linux-core.c b/src/unix/linux-core.c index 78b234364e..29cbeaebd2 100644 --- a/src/unix/linux-core.c +++ b/src/unix/linux-core.c @@ -751,6 +751,8 @@ int uv_interface_addresses(uv_interface_address_t** addresses, address->name = strdup(ent->ifa_name); + address->if_index = if_nametoindex(address->name); + if (ent->ifa_addr->sa_family == AF_INET6) { address->address.address6 = *((struct sockaddr_in6*) ent->ifa_addr); } else { diff --git a/src/unix/udp.c b/src/unix/udp.c index a2b3dc3298..ab95c10e57 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -35,6 +35,10 @@ static void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents); static void uv__udp_recvmsg(uv_loop_t* loop, uv__io_t* w, unsigned int revents); static void uv__udp_sendmsg(uv_loop_t* loop, uv__io_t* w, unsigned int revents); static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, int domain); +static int uv__udp_set_membership6(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + uv_membership membership); void uv__udp_close(uv_udp_t* handle) { @@ -455,6 +459,11 @@ int uv_udp_set_membership(uv_udp_t* handle, uv_membership membership) { struct ip_mreq mreq; int optname; + struct sockaddr_in6 addr6; + + if (uv_ip6_addr(multicast_addr, 0, &addr6) == 0) { + return uv__udp_set_membership6(handle, multicast_addr, interface_addr, membership); + } memset(&mreq, 0, sizeof mreq); @@ -488,6 +497,73 @@ int uv_udp_set_membership(uv_udp_t* handle, return 0; } +static int uv__udp_set_membership6(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + uv_membership membership) { + + int optname; + int interfaces_count; + int i; + struct ipv6_mreq mreq; + struct in6_addr multicast_addr_n; + struct in6_addr interface_addr_n; + uv_interface_address_t* interfaces; + + memset(&mreq, 0, sizeof mreq); + memset(&multicast_addr_n, 0, sizeof multicast_addr_n); + memset(&interface_addr_n, 0, sizeof interface_addr_n); + + if (interface_addr != NULL) { + uv_inet_pton(AF_INET6, interface_addr, &interface_addr_n); + if (uv_interface_addresses(&interfaces, &interfaces_count) != 0) + return -1; + /*return uv__set_sys_error(handle->loop, UV_EINVAL);*/ + + for (i = 0; i < interfaces_count; i++) { + if (interfaces[i].address.address6.sin6_family == AF_INET6) { + if (memcmp(&interfaces[i].address.address6.sin6_addr, + &interface_addr_n, + sizeof interface_addr_n) == 0) { + mreq.ipv6mr_interface = interfaces[i].if_index; + break; + } + } + } + + if (mreq.ipv6mr_interface == 0) { + /*uv__set_artificial_error(handle->loop, UV_EINVAL);*/ + return -1; + } + } + + if (uv_inet_pton(AF_INET6, multicast_addr, &multicast_addr_n) != 0) { + /*uv__set_artificial_error(handle->loop, UV_EINVAL);*/ + return -1; + } + + mreq.ipv6mr_multiaddr = multicast_addr_n; + + switch (membership) { + case UV_JOIN_GROUP: + optname = IPV6_ADD_MEMBERSHIP; + break; + case UV_LEAVE_GROUP: + optname = IPV6_DROP_MEMBERSHIP; + break; + default: + /*uv__set_artificial_error(handle->loop, UV_EINVAL);*/ + return -1; + } + + if (setsockopt(handle->io_watcher.fd, IPPROTO_IPV6, optname, &mreq, sizeof mreq) != 0) { + /*uv__set_artificial_error(handle->loop, UV_EINVAL);*/ + return -1; + } + + return 0; +} + static int uv__setsockopt_maybe_char(uv_udp_t* handle, int option, int val) { #if defined(__sun) diff --git a/src/win/udp.c b/src/win/udp.c index 31812e4642..680704c54d 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -539,7 +539,7 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, struct ip_mreq mreq; /* If the socket is unbound, bind to inaddr_any. */ - if (!(handle->flags & UV_HANDLE_BOUND)) { + if (!(handle->flags & UV_HANDLE_IPV6) && !(handle->flags & UV_HANDLE_BOUND)) { err = uv_udp_try_bind(handle, (const struct sockaddr*) &uv_addr_ip4_any_, sizeof(uv_addr_ip4_any_), @@ -549,7 +549,7 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, } if (handle->flags & UV_HANDLE_IPV6) { - return UV_ENOSYS; + return uv_udp_set_membership6(handle, multicast_addr, interface_addr, membership); } memset(&mreq, 0, sizeof mreq); @@ -570,7 +570,7 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, optname = IP_DROP_MEMBERSHIP; break; default: - return UV_EINVAL; + return UV__EINVAL; } if (setsockopt(handle->socket, @@ -585,6 +585,87 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, } +int uv_udp_set_membership6(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + uv_membership membership) { + int optname; + int interfaces_count; + int i; + int err; + struct ipv6_mreq mreq; + struct in6_addr multicast_addr_n; + struct in6_addr interface_addr_n; + uv_interface_address_t* interfaces; + + if (!(handle->flags & UV_HANDLE_IPV6)) { + return -1; + } + + if (!(handle->flags & UV_HANDLE_BOUND)) { + err = uv_udp_try_bind(handle, + (const struct sockaddr*) &uv_addr_ip6_any_, + sizeof(uv_addr_ip6_any_), + 0); + + if (err) { + return uv_translate_sys_error(err); + } + } + + memset(&mreq, 0, sizeof(mreq)); + memset(&multicast_addr_n, 0, sizeof multicast_addr_n); + memset(&interface_addr_n, 0, sizeof interface_addr_n); + + if (interface_addr != NULL) { + uv_inet_pton(AF_INET6, interface_addr, &interface_addr_n); + if (uv_interface_addresses(&interfaces, &interfaces_count) != 0) { + return uv_translate_sys_error(WSAGetLastError()); + } + + for (i = 0; i < interfaces_count; i++) { + if (interfaces[i].address.address6.sin6_family == AF_INET6) { + if (memcmp(&interfaces[i].address.address6.sin6_addr, + &interface_addr_n, + sizeof interface_addr_n) == 0) { + mreq.ipv6mr_interface = interfaces[i].if_index; + break; + } + } + } + + if (mreq.ipv6mr_interface == 0) { + return -1; + } + } + if (uv_inet_pton(AF_INET6, multicast_addr, &multicast_addr_n) != 0) { + return uv_translate_sys_error(WSAGetLastError()); + } + + mreq.ipv6mr_multiaddr = multicast_addr_n; + + switch (membership) { + case UV_JOIN_GROUP: + optname = IPV6_ADD_MEMBERSHIP; + break; + case UV_LEAVE_GROUP: + optname = IPV6_DROP_MEMBERSHIP; + break; + default: + return -1; + } + + if (setsockopt(handle->socket, + IPPROTO_IPV6, + optname, + (char*) &mreq, + sizeof mreq) == SOCKET_ERROR) { + return uv_translate_sys_error(WSAGetLastError()); + } + + return 0; +} + int uv_udp_set_broadcast(uv_udp_t* handle, int value) { BOOL optval = (BOOL) value; int err; diff --git a/src/win/util.c b/src/win/util.c index 266b881640..7fad03255d 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -920,6 +920,10 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr, (int) max_name_size, NULL, FALSE); + + uv_address->if_index = 0; + uv_address->if_index = (win_address->IfIndex) ? win_address->IfIndex : win_address->Ipv6IfIndex; + if (name_size <= 0) { free(win_address_buf); free(uv_address_buf); diff --git a/test/test-list.h b/test/test-list.h index 2195e232dc..b48d862264 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -80,6 +80,7 @@ TEST_DECLARE (tcp_bind6_error_inval) TEST_DECLARE (tcp_bind6_localhost_ok) TEST_DECLARE (udp_send_and_recv) TEST_DECLARE (udp_multicast_join) +TEST_DECLARE (udp_multicast_join6) TEST_DECLARE (udp_multicast_ttl) TEST_DECLARE (udp_dgram_too_big) TEST_DECLARE (udp_dual_stack) @@ -339,6 +340,7 @@ TASK_LIST_START TEST_ENTRY (udp_ipv6_only) TEST_ENTRY (udp_options) TEST_ENTRY (udp_multicast_join) + TEST_ENTRY (udp_multicast_join6) TEST_ENTRY (udp_multicast_ttl) TEST_ENTRY (udp_open) diff --git a/test/test-udp-multicast-join6.c b/test/test-udp-multicast-join6.c new file mode 100644 index 0000000000..b9ea674a88 --- /dev/null +++ b/test/test-udp-multicast-join6.c @@ -0,0 +1,148 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#include +#include +#include + +#define CHECK_HANDLE(handle) \ + ASSERT((uv_udp_t*)(handle) == &server || (uv_udp_t*)(handle) == &client) + +static uv_udp_t server; +static uv_udp_t client; + +static int cl_recv_cb_called; + +static int sv_send_cb_called; + +static int close_cb_called; + +static void alloc_cb(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + static char slab[65536]; + CHECK_HANDLE(handle); + ASSERT(suggested_size <= sizeof(slab)); + buf->base = slab; + buf->len = sizeof(slab); +} + + +static void close_cb(uv_handle_t* handle) { + CHECK_HANDLE(handle); + close_cb_called++; +} + + +static void sv_send_cb(uv_udp_send_t* req, int status) { + ASSERT(req != NULL); + ASSERT(status == 0); + CHECK_HANDLE(req->handle); + + sv_send_cb_called++; + + uv_close((uv_handle_t*) req->handle, close_cb); +} + + +static void cl_recv_cb(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* buf, + const struct sockaddr* addr, + unsigned flags) { + CHECK_HANDLE(handle); + ASSERT(flags == 0); + + cl_recv_cb_called++; + + if (nread < 0) { + ASSERT(0 && "unexpected error"); + } + + if (nread == 0) { + /* Returning unused buffer */ + /* Don't count towards cl_recv_cb_called */ + ASSERT(addr == NULL); + return; + } + + ASSERT(addr != NULL); + ASSERT(nread == 4); + ASSERT(!memcmp("PING", buf->base, nread)); + + /* we are done with the client handle, we can close it */ + uv_close((uv_handle_t*) &client, close_cb); +} + + +TEST_IMPL(udp_multicast_join6) { + int r; + uv_udp_send_t req; + uv_buf_t buf; + struct sockaddr_in6 addr; + + ASSERT(0 == uv_ip6_addr("::1", TEST_PORT, &addr)); + + r = uv_udp_init(uv_default_loop(), &server); + ASSERT(r == 0); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + /* bind to the desired port */ + r = uv_udp_bind(&client, (const struct sockaddr*) &addr, 0); + ASSERT(r == 0); + + /* join the multicast channel */ + r = uv_udp_set_membership(&client, "FF02::3", NULL, UV_JOIN_GROUP); + ASSERT(r == 0); + + r = uv_udp_recv_start(&client, alloc_cb, cl_recv_cb); + ASSERT(r == 0); + + buf = uv_buf_init("PING", 4); + + /* server sends "PING" */ + r = uv_udp_send(&req, + &server, + &buf, + 1, + (const struct sockaddr*) &addr, + sv_send_cb); + ASSERT(r == 0); + + ASSERT(close_cb_called == 0); + ASSERT(cl_recv_cb_called == 0); + ASSERT(sv_send_cb_called == 0); + + /* run the loop till all events are processed */ + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT(cl_recv_cb_called == 1); + ASSERT(sv_send_cb_called == 1); + ASSERT(close_cb_called == 2); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/uv.gyp b/uv.gyp index 50e19357de..b6e2e44c9a 100644 --- a/uv.gyp +++ b/uv.gyp @@ -383,6 +383,7 @@ 'test/test-udp-options.c', 'test/test-udp-send-and-recv.c', 'test/test-udp-multicast-join.c', + 'test/test-udp-multicast-join6.c', 'test/test-dlerror.c', 'test/test-udp-multicast-ttl.c', 'test/test-ip4-addr.c',