From 7c5ab1a73e69c4cec0b1536f9de471843c11f827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Mon, 17 Mar 2014 10:06:25 +0100 Subject: [PATCH] unix, windows: add IPv6 support to uv_udp_set_membership Reworked from initial version by @snoj --- Makefile.am | 1 + src/unix/udp.c | 132 +++++++++++++++++++++------- src/win/udp.c | 88 +++++++++++++++++-- test/test-list.h | 2 + test/test-udp-multicast-join6.c | 149 ++++++++++++++++++++++++++++++++ uv.gyp | 1 + 6 files changed, 335 insertions(+), 38 deletions(-) create mode 100644 test/test-udp-multicast-join6.c diff --git a/Makefile.am b/Makefile.am index cd4f519f..02368515 100644 --- a/Makefile.am +++ b/Makefile.am @@ -204,6 +204,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-udp-ipv6.c \ test/test-udp-multicast-interface.c \ test/test-udp-multicast-join.c \ + test/test-udp-multicast-join6.c \ test/test-udp-multicast-ttl.c \ test/test-udp-open.c \ test/test-udp-options.c \ diff --git a/src/unix/udp.c b/src/unix/udp.c index ae3cc8dc..f1382491 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -28,6 +28,14 @@ #include #include +#if defined(IPV6_JOIN_GROUP) && !defined(IPV6_ADD_MEMBERSHIP) +# define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif + +#if defined(IPV6_LEAVE_GROUP) && !defined(IPV6_DROP_MEMBERSHIP) +# define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif + static void uv__udp_run_completed(uv_udp_t* handle); static void uv__udp_run_pending(uv_udp_t* handle); @@ -422,6 +430,92 @@ int uv__udp_send(uv_udp_send_t* req, } +static int uv__udp_set_membership4(uv_udp_t* handle, + const struct sockaddr_in* multicast_addr, + const char* interface_addr, + uv_membership membership) { + struct ip_mreq mreq; + int optname; + int err; + + memset(&mreq, 0, sizeof mreq); + + if (interface_addr) { + err = uv_inet_pton(AF_INET, interface_addr, &mreq.imr_interface.s_addr); + if (err) + return err; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + + mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; + + switch (membership) { + case UV_JOIN_GROUP: + optname = IP_ADD_MEMBERSHIP; + break; + case UV_LEAVE_GROUP: + optname = IP_DROP_MEMBERSHIP; + break; + default: + return -EINVAL; + } + + if (setsockopt(handle->io_watcher.fd, + IPPROTO_IP, + optname, + &mreq, + sizeof(mreq))) { + return -errno; + } + + return 0; +} + + +static int uv__udp_set_membership6(uv_udp_t* handle, + const struct sockaddr_in6* multicast_addr, + const char* interface_addr, + uv_membership membership) { + int optname; + struct ipv6_mreq mreq; + struct sockaddr_in6 addr6; + + memset(&mreq, 0, sizeof mreq); + + if (interface_addr) { + if (uv_ip6_addr(interface_addr, 0, &addr6)) + return -EINVAL; + mreq.ipv6mr_interface = addr6.sin6_scope_id; + } else { + mreq.ipv6mr_interface = 0; + } + + mreq.ipv6mr_multiaddr = multicast_addr->sin6_addr; + + switch (membership) { + case UV_JOIN_GROUP: + optname = IPV6_ADD_MEMBERSHIP; + break; + case UV_LEAVE_GROUP: + optname = IPV6_DROP_MEMBERSHIP; + break; + default: + return -EINVAL; + } + + if (setsockopt(handle->io_watcher.fd, + IPPROTO_IPV6, + optname, + &mreq, + sizeof(mreq))) { + return -errno; + } + + return 0; +} + + int uv_udp_init(uv_loop_t* loop, uv_udp_t* handle) { uv__handle_init(loop, (uv_handle_t*)handle, UV_UDP); handle->alloc_cb = NULL; @@ -453,39 +547,15 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership) { - struct ip_mreq mreq; - int optname; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; - memset(&mreq, 0, sizeof mreq); - - if (interface_addr) { - mreq.imr_interface.s_addr = inet_addr(interface_addr); - } else { - mreq.imr_interface.s_addr = htonl(INADDR_ANY); - } - - mreq.imr_multiaddr.s_addr = inet_addr(multicast_addr); - - switch (membership) { - case UV_JOIN_GROUP: - optname = IP_ADD_MEMBERSHIP; - break; - case UV_LEAVE_GROUP: - optname = IP_DROP_MEMBERSHIP; - break; - default: + if (uv_ip4_addr(multicast_addr, 0, &addr4) == 0) + return uv__udp_set_membership4(handle, &addr4, interface_addr, membership); + else if (uv_ip6_addr(multicast_addr, 0, &addr6) == 0) + return uv__udp_set_membership6(handle, &addr6, interface_addr, membership); + else return -EINVAL; - } - - if (setsockopt(handle->io_watcher.fd, - IPPROTO_IP, - optname, - &mreq, - sizeof(mreq))) { - return -errno; - } - - return 0; } diff --git a/src/win/udp.c b/src/win/udp.c index 282d4948..2643a26f 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -532,12 +532,17 @@ void uv_process_udp_send_req(uv_loop_t* loop, uv_udp_t* handle, } -int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, - const char* interface_addr, uv_membership membership) { +static int uv__udp_set_membership4(uv_udp_t* handle, + const struct sockaddr_in* multicast_addr, + const char* interface_addr, + uv_membership membership) { int err; int optname; struct ip_mreq mreq; + if (handle->flags & UV_HANDLE_IPV6) + return UV_EINVAL; + /* If the socket is unbound, bind to inaddr_any. */ if (!(handle->flags & UV_HANDLE_BOUND)) { err = uv_udp_try_bind(handle, @@ -548,10 +553,6 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, return uv_translate_sys_error(err); } - if (handle->flags & UV_HANDLE_IPV6) { - return UV_ENOSYS; - } - memset(&mreq, 0, sizeof mreq); if (interface_addr) { @@ -560,7 +561,7 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, mreq.imr_interface.s_addr = htonl(INADDR_ANY); } - mreq.imr_multiaddr.s_addr = inet_addr(multicast_addr); + mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; switch (membership) { case UV_JOIN_GROUP: @@ -585,6 +586,79 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, } +int uv__udp_set_membership6(uv_udp_t* handle, + const struct sockaddr_in6* multicast_addr, + const char* interface_addr, + uv_membership membership) { + int optname; + int err; + struct ipv6_mreq mreq; + struct sockaddr_in6 addr6; + + if ((handle->flags & UV_HANDLE_BOUND) && !(handle->flags & UV_HANDLE_IPV6)) + return UV_EINVAL; + + 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)); + + if (interface_addr) { + if (uv_ip6_addr(interface_addr, 0, &addr6)) + return UV_EINVAL; + mreq.ipv6mr_interface = addr6.sin6_scope_id; + } else { + mreq.ipv6mr_interface = 0; + } + + mreq.ipv6mr_multiaddr = multicast_addr->sin6_addr; + + switch (membership) { + case UV_JOIN_GROUP: + optname = IPV6_ADD_MEMBERSHIP; + break; + case UV_LEAVE_GROUP: + optname = IPV6_DROP_MEMBERSHIP; + break; + default: + return UV_EINVAL; + } + + 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_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + uv_membership membership) { + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + + if (uv_ip4_addr(multicast_addr, 0, &addr4) == 0) + return uv__udp_set_membership4(handle, &addr4, interface_addr, membership); + else if (uv_ip6_addr(multicast_addr, 0, &addr6) == 0) + return uv__udp_set_membership6(handle, &addr6, interface_addr, membership); + else + return UV_EINVAL; +} + + int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) { struct in_addr addr; int err; diff --git a/test/test-list.h b/test/test-list.h index 345df511..8f7473f6 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -81,6 +81,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_multicast_interface) TEST_DECLARE (udp_dgram_too_big) @@ -352,6 +353,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 00000000..fe619fec --- /dev/null +++ b/test/test-udp-multicast-join6.c @@ -0,0 +1,149 @@ +/* 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::1", 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 c1e2b987..34946c16 100644 --- a/uv.gyp +++ b/uv.gyp @@ -389,6 +389,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',