1
0
mirror of https://github.com/libuv/libuv synced 2025-03-28 21:13:16 +00:00

bsd,windows,zos: fix udp disconnect EINVAL (#3350)

Fixes: https://github.com/libuv/libuv/issues/3344
This commit is contained in:
deal 2022-01-03 18:31:29 +08:00 committed by GitHub
parent 64742416e0
commit a865f78124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 254 additions and 7 deletions

View File

@ -587,6 +587,7 @@ if(LIBUV_BUILD_TESTS)
test/test-udp-alloc-cb-fail.c
test/test-udp-bind.c
test/test-udp-connect.c
test/test-udp-connect6.c
test/test-udp-create-socket-early.c
test/test-udp-dgram-too-big.c
test/test-udp-ipv6.c

View File

@ -291,6 +291,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \
test/test-udp-alloc-cb-fail.c \
test/test-udp-bind.c \
test/test-udp-connect.c \
test/test-udp-connect6.c \
test/test-udp-create-socket-early.c \
test/test-udp-dgram-too-big.c \
test/test-udp-ipv6.c \

View File

@ -654,28 +654,71 @@ int uv__udp_connect(uv_udp_t* handle,
return 0;
}
/* From https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
* Any of uv supported UNIXs kernel should be standardized, but the kernel
* implementation logic not same, let's use pseudocode to explain the udp
* disconnect behaviors:
*
* Predefined stubs for pseudocode:
* 1. sodisconnect: The function to perform the real udp disconnect
* 2. pru_connect: The function to perform the real udp connect
* 3. so: The kernel object match with socket fd
* 4. addr: The sockaddr parameter from user space
*
* BSDs:
* if(sodisconnect(so) == 0) { // udp disconnect succeed
* if (addr->sa_len != so->addr->sa_len) return EINVAL;
* if (addr->sa_family != so->addr->sa_family) return EAFNOSUPPORT;
* pru_connect(so);
* }
* else return EISCONN;
*
* z/OS (same with Windows):
* if(addr->sa_len < so->addr->sa_len) return EINVAL;
* if (addr->sa_family == AF_UNSPEC) sodisconnect(so);
*
* AIX:
* if(addr->sa_len != sizeof(struct sockaddr)) return EINVAL; // ignore ip proto version
* if (addr->sa_family == AF_UNSPEC) sodisconnect(so);
*
* Linux,Others:
* if(addr->sa_len < sizeof(struct sockaddr)) return EINVAL;
* if (addr->sa_family == AF_UNSPEC) sodisconnect(so);
*/
int uv__udp_disconnect(uv_udp_t* handle) {
int r;
#if defined(__MVS__)
struct sockaddr_storage addr;
#else
struct sockaddr addr;
#endif
memset(&addr, 0, sizeof(addr));
#if defined(__MVS__)
addr.ss_family = AF_UNSPEC;
#else
addr.sa_family = AF_UNSPEC;
#endif
do {
errno = 0;
r = connect(handle->io_watcher.fd, &addr, sizeof(addr));
r = connect(handle->io_watcher.fd, (struct sockaddr*) &addr, sizeof(addr));
} while (r == -1 && errno == EINTR);
if (r == -1 && errno != EAFNOSUPPORT)
if (r == -1) {
#if defined(BSD) /* The macro BSD is from sys/param.h */
if (errno != EAFNOSUPPORT && errno != EINVAL)
return UV__ERR(errno);
#else
return UV__ERR(errno);
#endif
}
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[],

View File

@ -1083,7 +1083,7 @@ int uv__udp_connect(uv_udp_t* handle,
int uv__udp_disconnect(uv_udp_t* handle) {
int err;
struct sockaddr addr;
struct sockaddr_storage addr;
memset(&addr, 0, sizeof(addr));

View File

@ -160,6 +160,7 @@ TEST_DECLARE (udp_alloc_cb_fail)
TEST_DECLARE (udp_bind)
TEST_DECLARE (udp_bind_reuseaddr)
TEST_DECLARE (udp_connect)
TEST_DECLARE (udp_connect6)
TEST_DECLARE (udp_create_early)
TEST_DECLARE (udp_create_early_bad_bind)
TEST_DECLARE (udp_create_early_bad_domain)
@ -737,6 +738,7 @@ TASK_LIST_START
TEST_ENTRY (udp_bind)
TEST_ENTRY (udp_bind_reuseaddr)
TEST_ENTRY (udp_connect)
TEST_ENTRY (udp_connect6)
TEST_ENTRY (udp_create_early)
TEST_ENTRY (udp_create_early_bad_bind)
TEST_ENTRY (udp_create_early_bad_domain)

200
test/test-udp-connect6.c Normal file
View File

@ -0,0 +1,200 @@
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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_in6 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_LE(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_NOT_NULL(req);
ASSERT_EQ(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_EQ(r, UV_EDESTADDRREQ);
r = uv_udp_send(req,
&client,
&buf,
1,
(const struct sockaddr*) &lo_addr,
cl_send_cb);
ASSERT_EQ(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_EQ(nread, 4);
ASSERT_NOT_NULL(addr);
ASSERT_EQ(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_connect6) {
#if defined(__PASE__)
RETURN_SKIP(
"IBMi PASE's UDP connection can not be disconnected with AF_UNSPEC.");
#endif
uv_udp_send_t req;
struct sockaddr_in6 ext_addr;
struct sockaddr_in6 tmp_addr;
int r;
int addrlen;
if (!can_ipv6())
RETURN_SKIP("IPv6 not supported");
ASSERT_EQ(0, uv_ip6_addr("::", TEST_PORT, &lo_addr));
r = uv_udp_init(uv_default_loop(), &server);
ASSERT_EQ(r, 0);
r = uv_udp_bind(&server, (const struct sockaddr*) &lo_addr, 0);
ASSERT_EQ(r, 0);
r = uv_udp_recv_start(&server, alloc_cb, sv_recv_cb);
ASSERT_EQ(r, 0);
r = uv_udp_init(uv_default_loop(), &client);
ASSERT_EQ(r, 0);
buf = uv_buf_init("EXIT", 4);
/* connect() to INADDR_ANY fails on Windows wih WSAEADDRNOTAVAIL */
ASSERT_EQ(0, uv_ip6_addr("::", TEST_PORT, &tmp_addr));
r = uv_udp_connect(&client, (const struct sockaddr*) &tmp_addr);
#ifdef _WIN32
ASSERT_EQ(r, UV_EADDRNOTAVAIL);
#else
ASSERT_EQ(r, 0);
r = uv_udp_connect(&client, NULL);
ASSERT_EQ(r, 0);
#endif
ASSERT_EQ(0, uv_ip6_addr("2001:4860:4860::8888", TEST_PORT, &ext_addr));
ASSERT_EQ(0, uv_ip6_addr("::1", TEST_PORT, &lo_addr));
r = uv_udp_connect(&client, (const struct sockaddr*) &lo_addr);
ASSERT_EQ(r, 0);
r = uv_udp_connect(&client, (const struct sockaddr*) &ext_addr);
ASSERT_EQ(r, UV_EISCONN);
addrlen = sizeof(tmp_addr);
r = uv_udp_getpeername(&client, (struct sockaddr*) &tmp_addr, &addrlen);
ASSERT_EQ(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_EQ(r, UV_EISCONN);
r = uv_udp_try_send(&client, &buf, 1, NULL);
ASSERT_EQ(r, 4);
r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &ext_addr);
ASSERT_EQ(r, UV_EISCONN);
r = uv_udp_connect(&client, NULL);
ASSERT_EQ(r, 0);
r = uv_udp_connect(&client, NULL);
ASSERT_EQ(r, UV_ENOTCONN);
addrlen = sizeof(tmp_addr);
r = uv_udp_getpeername(&client, (struct sockaddr*) &tmp_addr, &addrlen);
ASSERT_EQ(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_EQ(r, 4);
r = uv_udp_try_send(&client, &buf, 1, NULL);
ASSERT_EQ(r, UV_EDESTADDRREQ);
r = uv_udp_connect(&client, (const struct sockaddr*) &lo_addr);
ASSERT_EQ(r, 0);
r = uv_udp_send(&req,
&client,
&buf,
1,
(const struct sockaddr*) &lo_addr,
cl_send_cb);
ASSERT_EQ(r, UV_EISCONN);
r = uv_udp_send(&req, &client, &buf, 1, NULL, cl_send_cb);
ASSERT_EQ(r, 0);
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
ASSERT_EQ(close_cb_called, 2);
ASSERT_EQ(sv_recv_cb_called, 4);
ASSERT_EQ(cl_send_cb_called, 2);
ASSERT_EQ(client.send_queue_size, 0);
ASSERT_EQ(server.send_queue_size, 0);
MAKE_VALGRIND_HAPPY();
return 0;
}