From 70d437130393dc8d9d01f2758b9b3c23a4dccedb Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Mon, 20 Mar 2017 11:32:25 +0100 Subject: [PATCH] udp: add support for UDP connected sockets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two new methods: `uv_udp_connect()` to connect / disconnect an UDP handle. `uv_udp_getpeername()` to get the remote peer address of a connected UDP handle. Modify `uv_udp_send()` and `uv_udp_try_send()` to accept a `NULL` `addr` to send messages over an "UDP connection". Refs: https://github.com/libuv/leps/pull/10 PR-URL: https://github.com/libuv/libuv/pull/1872 Reviewed-By: Saúl Ibarra Corretgé Reviewed-By: Colin Ihrig --- CMakeLists.txt | 1 + Makefile.am | 1 + docs/src/udp.rst | 54 ++++++++++++ include/uv.h | 4 + src/unix/pipe.c | 6 +- src/unix/udp.c | 91 +++++++++++++++++--- src/uv-common.c | 89 ++++++++++++++++---- src/uv-common.h | 9 ++ src/win/core.c | 6 +- src/win/udp.c | 75 ++++++++++++++++- test/test-list.h | 5 ++ test/test-udp-connect.c | 182 ++++++++++++++++++++++++++++++++++++++++ test/test-udp-open.c | 51 +++++++++++ test/test.gyp | 1 + 14 files changed, 540 insertions(+), 35 deletions(-) create mode 100644 test/test-udp-connect.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ffa65bd..391f8d7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ set(uv_test_sources test/test-tty.c test/test-udp-alloc-cb-fail.c test/test-udp-bind.c + test/test-udp-connect.c test/test-udp-create-socket-early.c test/test-udp-dgram-too-big.c test/test-udp-ipv6.c diff --git a/Makefile.am b/Makefile.am index d2083fca..29c4a19c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -273,6 +273,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-tty.c \ test/test-udp-alloc-cb-fail.c \ test/test-udp-bind.c \ + test/test-udp-connect.c \ test/test-udp-create-socket-early.c \ test/test-udp-dgram-too-big.c \ test/test-udp-ipv6.c \ diff --git a/docs/src/udp.rst b/docs/src/udp.rst index 81488285..d9deb78b 100644 --- a/docs/src/udp.rst +++ b/docs/src/udp.rst @@ -150,6 +150,44 @@ API :returns: 0 on success, or an error code < 0 on failure. +.. c:function:: int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr) + + Associate the UDP handle to a remote address and port, so every + message sent by this handle is automatically sent to that destination. + Calling this function with a `NULL` `addr` disconnects the handle. + Trying to call `uv_udp_connect()` on an already connected handle will result + in an `UV_EISCONN` error. Trying to disconnect a handle that is not + connected will return an `UV_ENOTCONN` error. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init`. + + :param addr: `struct sockaddr_in` or `struct sockaddr_in6` + with the address and port to associate to. + + :returns: 0 on success, or an error code < 0 on failure. + + .. versionadded:: 2.0.0 + +.. c:function:: int uv_udp_getpeername(const uv_udp_t* handle, struct sockaddr* name, int* namelen) + + Get the remote IP and port of the UDP handle on connected UDP handles. + On unconnected handles, it returns `UV_ENOTCONN`. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init` and bound. + + :param name: Pointer to the structure to be filled with the address data. + In order to support IPv4 and IPv6 `struct sockaddr_storage` should be + used. + + :param namelen: On input it indicates the data of the `name` field. On + output it indicates how much of it was filled. + + :returns: 0 on success, or an error code < 0 on failure + + .. versionadded:: 2.0.0 + .. c:function:: int uv_udp_getsockname(const uv_udp_t* handle, struct sockaddr* name, int* namelen) Get the local IP and port of the UDP handle. @@ -247,6 +285,12 @@ API (``0.0.0.0`` or ``::``) it will be changed to point to ``localhost``. This is done to match the behavior of Linux systems. + For connected UDP handles, `addr` must be set to `NULL`, otherwise it will + return `UV_EISCONN` error. + + For connectionless UDP handles, `addr` cannot be `NULL`, otherwise it will + return `UV_EDESTADDRREQ` error. + :param req: UDP request handle. Need not be initialized. :param handle: UDP handle. Should have been initialized with @@ -266,15 +310,25 @@ API .. versionchanged:: 1.19.0 added ``0.0.0.0`` and ``::`` to ``localhost`` mapping + .. versionchanged:: 2.0.0 added support for connected sockets + .. c:function:: int uv_udp_try_send(uv_udp_t* handle, const uv_buf_t bufs[], unsigned int nbufs, const struct sockaddr* addr) Same as :c:func:`uv_udp_send`, but won't queue a send request if it can't be completed immediately. + For connected UDP handles, `addr` must be set to `NULL`, otherwise it will + return `UV_EISCONN` error. + + For connectionless UDP handles, `addr` cannot be `NULL`, otherwise it will + return `UV_EDESTADDRREQ` error. + :returns: >= 0: number of bytes sent (it matches the given buffer size). < 0: negative error code (``UV_EAGAIN`` is returned when the message can't be sent immediately). + .. versionchanged:: 2.0.0 added support for connected sockets + .. c:function:: int uv_udp_recv_start(uv_udp_t* handle, uv_alloc_cb alloc_cb, uv_udp_recv_cb recv_cb) Prepare for receiving data. If the socket has not previously been bound diff --git a/include/uv.h b/include/uv.h index 39b3347c..7a2a2f59 100644 --- a/include/uv.h +++ b/include/uv.h @@ -683,7 +683,11 @@ UV_EXTERN int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock); UV_EXTERN int uv_udp_bind(uv_udp_t* handle, const struct sockaddr* addr, unsigned int flags); +UV_EXTERN int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr); +UV_EXTERN int uv_udp_getpeername(const uv_udp_t* handle, + struct sockaddr* name, + int* namelen); UV_EXTERN int uv_udp_getsockname(const uv_udp_t* handle, struct sockaddr* name, int* namelen); diff --git a/src/unix/pipe.c b/src/unix/pipe.c index d1b048cc..78baad38 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -244,9 +244,9 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, addrlen = sizeof(sa); memset(&sa, 0, addrlen); err = uv__getsockpeername((const uv_handle_t*) handle, - func, - (struct sockaddr*) &sa, - (int*) &addrlen); + func, + (struct sockaddr*) &sa, + (int*) &addrlen); if (err < 0) { *size = 0; return err; diff --git a/src/unix/udp.c b/src/unix/udp.c index a9cb6f52..6352028e 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -227,9 +227,14 @@ static void uv__udp_sendmsg(uv_udp_t* handle) { assert(req != NULL); memset(&h, 0, sizeof h); - h.msg_name = &req->addr; - h.msg_namelen = (req->addr.ss_family == AF_INET6 ? - sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (req->addr.ss_family == AF_UNSPEC) { + h.msg_name = NULL; + h.msg_namelen = 0; + } else { + h.msg_name = &req->addr; + h.msg_namelen = req->addr.ss_family == AF_INET6 ? + sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); + } h.msg_iov = (struct iovec*) req->bufs; h.msg_iovlen = req->nbufs; @@ -383,6 +388,50 @@ static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, } +int uv__udp_connect(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen) { + int err; + + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); + if (err) + return err; + + do { + errno = 0; + err = connect(handle->io_watcher.fd, addr, addrlen); + } while (err == -1 && errno == EINTR); + + if (err) + return UV__ERR(errno); + + handle->flags |= UV_HANDLE_UDP_CONNECTED; + + return 0; +} + + +int uv__udp_disconnect(uv_udp_t* handle) { + int r; + struct sockaddr addr; + + memset(&addr, 0, sizeof(addr)); + + addr.sa_family = AF_UNSPEC; + + do { + errno = 0; + r = connect(handle->io_watcher.fd, &addr, sizeof(addr)); + } while (r == -1 && errno == EINTR); + + if (r == -1 && errno != EAFNOSUPPORT) + return UV__ERR(errno); + + handle->flags &= ~UV_HANDLE_UDP_CONNECTED; + return 0; +} + + int uv__udp_send(uv_udp_send_t* req, uv_udp_t* handle, const uv_buf_t bufs[], @@ -395,9 +444,11 @@ int uv__udp_send(uv_udp_send_t* req, assert(nbufs > 0); - err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); - if (err) - return err; + if (addr) { + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); + if (err) + return err; + } /* It's legal for send_queue_count > 0 even when the write_queue is empty; * it means there are error-state requests in the write_completed_queue that @@ -407,7 +458,10 @@ int uv__udp_send(uv_udp_send_t* req, uv__req_init(handle->loop, req, UV_UDP_SEND); assert(addrlen <= sizeof(req->addr)); - memcpy(&req->addr, addr, addrlen); + if (addr == NULL) + req->addr.ss_family = AF_UNSPEC; + else + memcpy(&req->addr, addr, addrlen); req->send_cb = send_cb; req->handle = handle; req->nbufs = nbufs; @@ -459,9 +513,13 @@ int uv__udp_try_send(uv_udp_t* handle, if (handle->send_queue_count != 0) return UV_EAGAIN; - err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); - if (err) - return err; + if (addr) { + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); + if (err) + return err; + } else { + assert(handle->flags & UV_HANDLE_UDP_CONNECTED); + } memset(&h, 0, sizeof h); h.msg_name = (struct sockaddr*) addr; @@ -604,6 +662,7 @@ int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { uv__io_init(&handle->io_watcher, uv__udp_io, fd); QUEUE_INIT(&handle->write_queue); QUEUE_INIT(&handle->write_completed_queue); + return 0; } @@ -632,6 +691,9 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { return err; handle->io_watcher.fd = sock; + if (uv__udp_is_connected(handle)) + handle->flags |= UV_HANDLE_UDP_CONNECTED; + return 0; } @@ -843,6 +905,15 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) return 0; } +int uv_udp_getpeername(const uv_udp_t* handle, + struct sockaddr* name, + int* namelen) { + + return uv__getsockpeername((const uv_handle_t*) handle, + getpeername, + name, + namelen); +} int uv_udp_getsockname(const uv_udp_t* handle, struct sockaddr* name, diff --git a/src/uv-common.c b/src/uv-common.c index 219e29bc..3a3c0681 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -312,17 +312,20 @@ int uv_tcp_connect(uv_connect_t* req, } -int uv_udp_send(uv_udp_send_t* req, - uv_udp_t* handle, - const uv_buf_t bufs[], - unsigned int nbufs, - const struct sockaddr* addr, - uv_udp_send_cb send_cb) { +int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr) { unsigned int addrlen; if (handle->type != UV_UDP) return UV_EINVAL; + /* Disconnect the handle */ + if (addr == NULL) { + if (!(handle->flags & UV_HANDLE_UDP_CONNECTED)) + return UV_ENOTCONN; + + return uv__udp_disconnect(handle); + } + if (addr->sa_family == AF_INET) addrlen = sizeof(struct sockaddr_in); else if (addr->sa_family == AF_INET6) @@ -330,6 +333,66 @@ int uv_udp_send(uv_udp_send_t* req, else return UV_EINVAL; + if (handle->flags & UV_HANDLE_UDP_CONNECTED) + return UV_EISCONN; + + return uv__udp_connect(handle, addr, addrlen); +} + + +int uv__udp_is_connected(uv_udp_t* handle) { + struct sockaddr_storage addr; + int addrlen; + if (handle->type != UV_UDP) + return 0; + + addrlen = sizeof(addr); + if (uv_udp_getpeername(handle, (struct sockaddr*) &addr, &addrlen) != 0) + return 0; + + return addrlen > 0; +} + + +int uv__udp_check_before_send(uv_udp_t* handle, const struct sockaddr* addr) { + unsigned int addrlen; + + if (handle->type != UV_UDP) + return UV_EINVAL; + + if (addr != NULL && (handle->flags & UV_HANDLE_UDP_CONNECTED)) + return UV_EISCONN; + + if (addr == NULL && !(handle->flags & UV_HANDLE_UDP_CONNECTED)) + return UV_EDESTADDRREQ; + + if (addr != NULL) { + if (addr->sa_family == AF_INET) + addrlen = sizeof(struct sockaddr_in); + else if (addr->sa_family == AF_INET6) + addrlen = sizeof(struct sockaddr_in6); + else + return UV_EINVAL; + } else { + addrlen = 0; + } + + return addrlen; +} + + +int uv_udp_send(uv_udp_send_t* req, + uv_udp_t* handle, + const uv_buf_t bufs[], + unsigned int nbufs, + const struct sockaddr* addr, + uv_udp_send_cb send_cb) { + int addrlen; + + addrlen = uv__udp_check_before_send(handle, addr); + if (addrlen < 0) + return addrlen; + return uv__udp_send(req, handle, bufs, nbufs, addr, addrlen, send_cb); } @@ -338,17 +401,11 @@ int uv_udp_try_send(uv_udp_t* handle, const uv_buf_t bufs[], unsigned int nbufs, const struct sockaddr* addr) { - unsigned int addrlen; + int addrlen; - if (handle->type != UV_UDP) - return UV_EINVAL; - - if (addr->sa_family == AF_INET) - addrlen = sizeof(struct sockaddr_in); - else if (addr->sa_family == AF_INET6) - addrlen = sizeof(struct sockaddr_in6); - else - return UV_EINVAL; + addrlen = uv__udp_check_before_send(handle, addr); + if (addrlen < 0) + return addrlen; return uv__udp_try_send(handle, bufs, nbufs, addr, addrlen); } diff --git a/src/uv-common.h b/src/uv-common.h index c3e5ce35..ea8388b5 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -93,6 +93,7 @@ enum { /* Only used by uv_udp_t handles. */ UV_HANDLE_UDP_PROCESSING = 0x01000000, + UV_HANDLE_UDP_CONNECTED = 0x02000000, /* Only used by uv_pipe_t handles. */ UV_HANDLE_NON_OVERLAPPED_PIPE = 0x01000000, @@ -132,6 +133,14 @@ int uv__udp_bind(uv_udp_t* handle, unsigned int addrlen, unsigned int flags); +int uv__udp_connect(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen); + +int uv__udp_disconnect(uv_udp_t* handle); + +int uv__udp_is_connected(uv_udp_t* handle); + int uv__udp_send(uv_udp_send_t* req, uv_udp_t* handle, const uv_buf_t bufs[], diff --git a/src/win/core.c b/src/win/core.c index c52fc701..4ba8a35e 100644 --- a/src/win/core.c +++ b/src/win/core.c @@ -429,10 +429,11 @@ int uv__socket_sockopt(uv_handle_t* handle, int optname, int* value) { return 0; } -<<<<<<< HEAD + int uv_cpumask_size(void) { return (int)(sizeof(DWORD_PTR) * 8); -======= +} + int uv__getsockpeername(const uv_handle_t* handle, uv__peersockfunc func, @@ -455,5 +456,4 @@ int uv__getsockpeername(const uv_handle_t* handle, return uv_translate_sys_error(WSAGetLastError()); return 0; ->>>>>>> f574d69a... unix: refactor getsockname/getpeername methods } diff --git a/src/win/udp.c b/src/win/udp.c index 41d8f805..18465830 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -29,6 +29,25 @@ #include "req-inl.h" +/* + * Threshold of active udp streams for which to preallocate udp read buffers. + */ +const unsigned int uv_active_udp_streams_threshold = 0; + +/* A zero-size buffer for use by uv_udp_read */ +static char uv_zero_[] = ""; +int uv_udp_getpeername(const uv_udp_t* handle, + struct sockaddr* name, + int* namelen) { + + return uv__getsockpeername((const uv_handle_t*) handle, + getpeername, + name, + namelen, + 0); +} + + int uv_udp_getsockname(const uv_udp_t* handle, struct sockaddr* name, int* namelen) { @@ -755,6 +774,9 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { if (uv__udp_is_bound(handle)) handle->flags |= UV_HANDLE_BOUND; + if (uv__udp_is_connected(handle)) + handle->flags |= UV_HANDLE_UDP_CONNECTED; + return 0; } @@ -832,6 +854,50 @@ int uv__udp_bind(uv_udp_t* handle, } +int uv__udp_connect(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen) { + const struct sockaddr* bind_addr; + int err; + + if (!(handle->flags & UV_HANDLE_BOUND)) { + if (addrlen == sizeof(uv_addr_ip4_any_)) + bind_addr = (const struct sockaddr*) &uv_addr_ip4_any_; + else if (addrlen == sizeof(uv_addr_ip6_any_)) + bind_addr = (const struct sockaddr*) &uv_addr_ip6_any_; + else + return UV_EINVAL; + + err = uv_udp_maybe_bind(handle, bind_addr, addrlen, 0); + if (err) + return uv_translate_sys_error(err); + } + + err = connect(handle->socket, addr, addrlen); + if (err) + return uv_translate_sys_error(err); + + handle->flags |= UV_HANDLE_UDP_CONNECTED; + + return 0; +} + + +int uv__udp_disconnect(uv_udp_t* handle) { + int err; + struct sockaddr addr; + + memset(&addr, 0, sizeof(addr)); + + err = connect(handle->socket, &addr, sizeof(addr)); + if (err) + return uv_translate_sys_error(err); + + handle->flags &= ~UV_HANDLE_UDP_CONNECTED; + return 0; +} + + /* This function is an egress point, i.e. it returns libuv errors rather than * system errors. */ @@ -852,6 +918,7 @@ int uv__udp_send(uv_udp_send_t* req, bind_addr = (const struct sockaddr*) &uv_addr_ip6_any_; else return UV_EINVAL; + err = uv_udp_maybe_bind(handle, bind_addr, addrlen, 0); if (err) return uv_translate_sys_error(err); @@ -877,9 +944,11 @@ int uv__udp_try_send(uv_udp_t* handle, assert(nbufs > 0); - err = uv__convert_to_localhost_if_unspecified(addr, &converted); - if (err) - return err; + if (addr != NULL) { + err = uv__convert_to_localhost_if_unspecified(addr, &converted); + if (err) + return err; + } /* Already sending a message.*/ if (handle->send_queue_count != 0) diff --git a/test/test-list.h b/test/test-list.h index 4ac27b25..bf51caa8 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -129,6 +129,7 @@ TEST_DECLARE (tcp_write_ready) TEST_DECLARE (udp_alloc_cb_fail) TEST_DECLARE (udp_bind) TEST_DECLARE (udp_bind_reuseaddr) +TEST_DECLARE (udp_connect) TEST_DECLARE (udp_create_early) TEST_DECLARE (udp_create_early_bad_bind) TEST_DECLARE (udp_create_early_bad_domain) @@ -150,6 +151,7 @@ TEST_DECLARE (udp_no_autobind) TEST_DECLARE (udp_open) TEST_DECLARE (udp_open_twice) TEST_DECLARE (udp_open_bound) +TEST_DECLARE (udp_open_connect) TEST_DECLARE (udp_try_send) TEST_DECLARE (pipe_bind_error_addrinuse) TEST_DECLARE (pipe_bind_error_addrnotavail) @@ -614,6 +616,7 @@ TASK_LIST_START TEST_ENTRY (udp_alloc_cb_fail) TEST_ENTRY (udp_bind) TEST_ENTRY (udp_bind_reuseaddr) + TEST_ENTRY (udp_connect) TEST_ENTRY (udp_create_early) TEST_ENTRY (udp_create_early_bad_bind) TEST_ENTRY (udp_create_early_bad_domain) @@ -638,6 +641,8 @@ TASK_LIST_START TEST_HELPER (udp_open, udp4_echo_server) TEST_ENTRY (udp_open_twice) TEST_ENTRY (udp_open_bound) + TEST_ENTRY (udp_open_connect) + TEST_HELPER (udp_open_connect, udp4_echo_server) TEST_ENTRY (pipe_bind_error_addrinuse) TEST_ENTRY (pipe_bind_error_addrnotavail) diff --git a/test/test-udp-connect.c b/test/test-udp-connect.c new file mode 100644 index 00000000..f4463424 --- /dev/null +++ b/test/test-udp-connect.c @@ -0,0 +1,182 @@ +/* Copyright libuv project and 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 uv_buf_t buf; +static struct sockaddr_in lo_addr; + +static int cl_send_cb_called; +static int sv_recv_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); + ASSERT(uv_is_closing(handle)); + close_cb_called++; +} + + +static void cl_send_cb(uv_udp_send_t* req, int status) { + int r; + + ASSERT(req != NULL); + ASSERT(status == 0); + CHECK_HANDLE(req->handle); + if (++cl_send_cb_called == 1) { + uv_udp_connect(&client, NULL); + r = uv_udp_send(req, &client, &buf, 1, NULL, cl_send_cb); + ASSERT(r == UV_EDESTADDRREQ); + r = uv_udp_send(req, + &client, + &buf, + 1, + (const struct sockaddr*) &lo_addr, + cl_send_cb); + ASSERT(r == 0); + } + +} + + +static void sv_recv_cb(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* rcvbuf, + const struct sockaddr* addr, + unsigned flags) { + if (nread > 0) { + ASSERT(nread == 4); + ASSERT(addr != NULL); + ASSERT(memcmp("EXIT", rcvbuf->base, nread) == 0); + if (++sv_recv_cb_called == 4) { + uv_close((uv_handle_t*) &server, close_cb); + uv_close((uv_handle_t*) &client, close_cb); + } + } +} + + +TEST_IMPL(udp_connect) { + uv_udp_send_t req; + struct sockaddr_in ext_addr; + struct sockaddr_in tmp_addr; + int r; + int addrlen; + + ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &lo_addr)); + + r = uv_udp_init(uv_default_loop(), &server); + ASSERT(r == 0); + + r = uv_udp_bind(&server, (const struct sockaddr*) &lo_addr, 0); + ASSERT(r == 0); + + r = uv_udp_recv_start(&server, alloc_cb, sv_recv_cb); + ASSERT(r == 0); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + buf = uv_buf_init("EXIT", 4); + + ASSERT(0 == uv_ip4_addr("8.8.8.8", TEST_PORT, &ext_addr)); + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &lo_addr)); + + r = uv_udp_connect(&client, (const struct sockaddr*) &lo_addr); + ASSERT(r == 0); + r = uv_udp_connect(&client, (const struct sockaddr*) &ext_addr); + ASSERT(r == UV_EISCONN); + + addrlen = sizeof(tmp_addr); + r = uv_udp_getpeername(&client, (struct sockaddr*) &tmp_addr, &addrlen); + ASSERT(r == 0); + + /* To send messages in connected UDP sockets addr must be NULL */ + r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &lo_addr); + ASSERT(r == UV_EISCONN); + r = uv_udp_try_send(&client, &buf, 1, NULL); + ASSERT(r == 4); + r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &ext_addr); + ASSERT(r == UV_EISCONN); + + r = uv_udp_connect(&client, NULL); + ASSERT(r == 0); + r = uv_udp_connect(&client, NULL); + ASSERT(r == UV_ENOTCONN); + + addrlen = sizeof(tmp_addr); + r = uv_udp_getpeername(&client, (struct sockaddr*) &tmp_addr, &addrlen); + ASSERT(r == UV_ENOTCONN); + + /* To send messages in disconnected UDP sockets addr must be set */ + r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &lo_addr); + ASSERT(r == 4); + r = uv_udp_try_send(&client, &buf, 1, NULL); + ASSERT(r == UV_EDESTADDRREQ); + + + r = uv_udp_connect(&client, (const struct sockaddr*) &lo_addr); + ASSERT(r == 0); + r = uv_udp_send(&req, + &client, + &buf, + 1, + (const struct sockaddr*) &lo_addr, + cl_send_cb); + ASSERT(r == UV_EISCONN); + r = uv_udp_send(&req, &client, &buf, 1, NULL, cl_send_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT(close_cb_called == 2); + ASSERT(sv_recv_cb_called == 4); + ASSERT(cl_send_cb_called == 2); + + ASSERT(client.send_queue_size == 0); + ASSERT(server.send_queue_size == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-udp-open.c b/test/test-udp-open.c index 6dae19ba..0390bae2 100644 --- a/test/test-udp-open.c +++ b/test/test-udp-open.c @@ -129,6 +129,7 @@ static void send_cb(uv_udp_send_t* req, int status) { ASSERT(status == 0); send_cb_called++; + uv_close((uv_handle_t*)req->handle, close_cb); } @@ -245,3 +246,53 @@ TEST_IMPL(udp_open_bound) { MAKE_VALGRIND_HAPPY(); return 0; } + +TEST_IMPL(udp_open_connect) { + struct sockaddr_in addr; + uv_buf_t buf = uv_buf_init("PING", 4); + uv_udp_t client; + uv_udp_t server; + uv_os_sock_t sock; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + startup(); + sock = create_udp_socket(); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = connect(sock, (const struct sockaddr*) &addr, sizeof(addr)); + ASSERT(r == 0); + + r = uv_udp_open(&client, sock); + ASSERT(r == 0); + + r = uv_udp_init(uv_default_loop(), &server); + ASSERT(r == 0); + + r = uv_udp_bind(&server, (const struct sockaddr*) &addr, 0); + ASSERT(r == 0); + + r = uv_udp_recv_start(&server, alloc_cb, recv_cb); + ASSERT(r == 0); + + r = uv_udp_send(&send_req, + &client, + &buf, + 1, + NULL, + send_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT(send_cb_called == 1); + ASSERT(close_cb_called == 2); + + ASSERT(client.send_queue_size == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test.gyp b/test/test.gyp index 845c6598..6a06542c 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -135,6 +135,7 @@ 'test-tty.c', 'test-udp-alloc-cb-fail.c', 'test-udp-bind.c', + 'test-udp-connect.c', 'test-udp-create-socket-early.c', 'test-udp-dgram-too-big.c', 'test-udp-ipv6.c',