diff --git a/include/uv.h b/include/uv.h index 8082afaf..ad328564 100644 --- a/include/uv.h +++ b/include/uv.h @@ -239,6 +239,11 @@ typedef void (*uv_after_work_cb)(uv_work_t* req); typedef void (*uv_fs_event_cb)(uv_fs_event_t* handle, const char* filename, int events, int status); +typedef enum { + UV_LEAVE_GROUP = 0, + UV_JOIN_GROUP +} uv_membership; + struct uv_err_s { /* read-only */ @@ -550,6 +555,21 @@ int uv_udp_bind(uv_udp_t* handle, struct sockaddr_in addr, unsigned flags); int uv_udp_bind6(uv_udp_t* handle, struct sockaddr_in6 addr, unsigned flags); int uv_udp_getsockname(uv_udp_t* handle, struct sockaddr* name, int* namelen); +/* + * Set membership for a multicast 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, -1 on error. + */ +int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, + const char* interface_addr, uv_membership membership); + /* * Send data. If the socket has not previously been bound with `uv_udp_bind` * or `uv_udp_bind6`, it is bound to 0.0.0.0 (the "all interfaces" address) diff --git a/src/unix/udp.c b/src/unix/udp.c index 0fbd18bc..15cd609b 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -459,6 +459,42 @@ int uv__udp_bind6(uv_udp_t* handle, struct sockaddr_in6 addr, unsigned flags) { } +int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, + const char* interface_addr, uv_membership membership) { + + int optname; + struct ip_mreq mreq; + 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: + uv__set_sys_error(handle->loop, EFAULT); + return -1; + } + + if (setsockopt(handle->fd, IPPROTO_IP, optname, (void*) &mreq, sizeof mreq) == -1) { + uv__set_sys_error(handle->loop, errno); + return -1; + } + + return 0; +} + + int uv_udp_getsockname(uv_udp_t* handle, struct sockaddr* name, int* namelen) { socklen_t socklen; diff --git a/src/win/udp.c b/src/win/udp.c index 3877f399..70b9a876 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -223,6 +223,15 @@ int uv__udp_bind6(uv_udp_t* handle, struct sockaddr_in6 addr, } +int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, + const char* interface_addr, uv_membership membership) { + + /* not implemented yet */ + uv__set_sys_error(handle->loop, ENOSYS); + return -1; +} + + static void uv_udp_queue_recv(uv_loop_t* loop, uv_udp_t* handle) { uv_req_t* req; uv_buf_t buf; diff --git a/test/test-list.h b/test/test-list.h index dcf27860..a83cb312 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -43,6 +43,7 @@ TEST_DECLARE (tcp_bind6_error_fault) 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_dgram_too_big) TEST_DECLARE (udp_dual_stack) TEST_DECLARE (udp_ipv6_only) @@ -155,6 +156,7 @@ TASK_LIST_START TEST_ENTRY (udp_dgram_too_big) TEST_ENTRY (udp_dual_stack) TEST_ENTRY (udp_ipv6_only) + TEST_ENTRY (udp_multicast_join) TEST_ENTRY (pipe_bind_error_addrinuse) TEST_ENTRY (pipe_bind_error_addrnotavail) diff --git a/test/test-udp-multicast-join.c b/test/test-udp-multicast-join.c new file mode 100644 index 00000000..159dba08 --- /dev/null +++ b/test/test-udp-multicast-join.c @@ -0,0 +1,139 @@ +/* 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 uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char slab[65536]; + + CHECK_HANDLE(handle); + ASSERT(suggested_size <= sizeof slab); + + return uv_buf_init(slab, 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, + uv_buf_t buf, + 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_join) { + int r; + uv_udp_send_t req; + uv_buf_t buf; + struct sockaddr_in addr = uv_ip4_addr("239.255.0.1", TEST_PORT); + + 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, addr, 0); + ASSERT(r == 0); + + /* join the multicast channel */ + r = uv_udp_set_membership(&client, "239.255.0.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, 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()); + + ASSERT(cl_recv_cb_called == 1); + ASSERT(sv_send_cb_called == 1); + ASSERT(close_cb_called == 2); + + return 0; +} diff --git a/uv.gyp b/uv.gyp index aee6c283..1643e4d2 100644 --- a/uv.gyp +++ b/uv.gyp @@ -285,6 +285,7 @@ 'test/test-udp-dgram-too-big.c', 'test/test-udp-ipv6.c', 'test/test-udp-send-and-recv.c', + 'test/test-udp-multicast-join.c', ], 'conditions': [ [ 'OS=="win"', {