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

tcp: fail instantly if local port is unbound

On Windows when connecting to an unavailable port, the connect() will
retry for 2s, even on loopback devices. This uses a call to WSAIoctl to
make the connect() call fail instantly on local connections.

PR-URL: https://github.com/libuv/libuv/pull/2896
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Jameson Nash <vtjnash@gmail.com>
This commit is contained in:
Bartosz Sosnowski 2020-08-13 17:07:21 +02:00
parent 904b1c9b47
commit 99e88edf73
4 changed files with 175 additions and 0 deletions

View File

@ -748,6 +748,40 @@ int uv_tcp_read_start(uv_tcp_t* handle, uv_alloc_cb alloc_cb,
return 0;
}
static int uv__is_loopback(const struct sockaddr_storage* storage) {
const struct sockaddr_in* in4;
const struct sockaddr_in6* in6;
int i;
if (storage->ss_family == AF_INET) {
in4 = (const struct sockaddr_in*) storage;
return in4->sin_addr.S_un.S_un_b.s_b1 == 127;
}
if (storage->ss_family == AF_INET6) {
in6 = (const struct sockaddr_in6*) storage;
for (i = 0; i < 7; ++i) {
if (in6->sin6_addr.u.Word[i] != 0)
return 0;
}
return in6->sin6_addr.u.Word[7] == htons(1);
}
return 0;
}
// Check if Windows version is 10.0.16299 or later
static int uv__is_fast_loopback_fail_supported() {
OSVERSIONINFOW os_info;
if (!pRtlGetVersion)
return 0;
pRtlGetVersion(&os_info);
if (os_info.dwMajorVersion < 10)
return 0;
if (os_info.dwMajorVersion > 10)
return 1;
if (os_info.dwMinorVersion > 0)
return 1;
return os_info.dwBuildNumber >= 16299;
}
static int uv_tcp_try_connect(uv_connect_t* req,
uv_tcp_t* handle,
@ -755,6 +789,7 @@ static int uv_tcp_try_connect(uv_connect_t* req,
unsigned int addrlen,
uv_connect_cb cb) {
uv_loop_t* loop = handle->loop;
TCP_INITIAL_RTO_PARAMETERS retransmit_ioctl;
const struct sockaddr* bind_addr;
struct sockaddr_storage converted;
BOOL success;
@ -790,6 +825,25 @@ static int uv_tcp_try_connect(uv_connect_t* req,
}
}
/* This makes connect() fail instantly if the target port on the localhost
* is not reachable, instead of waiting for 2s. We do not care if this fails.
* This only works on Windows version 10.0.16299 and later.
*/
if (uv__is_fast_loopback_fail_supported() && uv__is_loopback(&converted)) {
memset(&retransmit_ioctl, 0, sizeof(retransmit_ioctl));
retransmit_ioctl.Rtt = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS;
retransmit_ioctl.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS;
WSAIoctl(handle->socket,
SIO_TCP_INITIAL_RTO,
&retransmit_ioctl,
sizeof(retransmit_ioctl),
NULL,
0,
&bytes,
NULL,
NULL);
}
UV_REQ_INIT(req, UV_CONNECT);
req->handle = (uv_stream_t*) handle;
req->cb = cb;

View File

@ -4726,6 +4726,18 @@ typedef HWINEVENTHOOK (WINAPI *sSetWinEventHook)
DWORD idThread,
UINT dwflags);
/* From mstcpip.h */
typedef struct _TCP_INITIAL_RTO_PARAMETERS {
USHORT Rtt;
UCHAR MaxSynRetransmissions;
} TCP_INITIAL_RTO_PARAMETERS, *PTCP_INITIAL_RTO_PARAMETERS;
#ifndef TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS
# define TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS ((UCHAR) -2)
#endif
#ifndef SIO_TCP_INITIAL_RTO
# define SIO_TCP_INITIAL_RTO _WSAIOW(IOC_VENDOR,17)
#endif
/* Ntdll function pointers */
extern sRtlGetVersion pRtlGetVersion;

View File

@ -127,6 +127,8 @@ TEST_DECLARE (tcp_bind_writable_flags)
TEST_DECLARE (tcp_listen_without_bind)
TEST_DECLARE (tcp_connect_error_fault)
TEST_DECLARE (tcp_connect_timeout)
TEST_DECLARE (tcp_local_connect_timeout)
TEST_DECLARE (tcp6_local_connect_timeout)
TEST_DECLARE (tcp_close_while_connecting)
TEST_DECLARE (tcp_close)
TEST_DECLARE (tcp_close_reset_accepted)
@ -684,6 +686,8 @@ TASK_LIST_START
TEST_ENTRY (tcp_listen_without_bind)
TEST_ENTRY (tcp_connect_error_fault)
TEST_ENTRY (tcp_connect_timeout)
TEST_ENTRY (tcp_local_connect_timeout)
TEST_ENTRY (tcp6_local_connect_timeout)
TEST_ENTRY (tcp_close_while_connecting)
TEST_ENTRY (tcp_close)
TEST_ENTRY (tcp_close_reset_accepted)

View File

@ -89,3 +89,108 @@ TEST_IMPL(tcp_connect_timeout) {
MAKE_VALGRIND_HAPPY();
return 0;
}
/* Make sure connect fails instantly if the target is nonexisting
* local port.
*/
static void connect_local_cb(uv_connect_t* req, int status) {
ASSERT_PTR_EQ(req, &connect_req);
ASSERT_NE(status, UV_ECANCELED);
connect_cb_called++;
}
static int is_supported_system() {
int semver[3];
int min_semver[3] = {10, 0, 16299};
int cnt;
uv_utsname_t uname;
ASSERT_EQ(uv_os_uname(&uname), 0);
if (strcmp(uname.sysname, "Windows_NT") == 0) {
cnt = sscanf(uname.release, "%d.%d.%d", &semver[0], &semver[1], &semver[2]);
if (cnt != 3) {
return 0;
}
// relase >= 10.0.16299
for (cnt = 0; cnt < 3; ++cnt) {
if (semver[cnt] > min_semver[cnt])
return 1;
if (semver[cnt] < min_semver[cnt])
return 0;
}
return 1;
}
return 1;
}
TEST_IMPL(tcp_local_connect_timeout) {
struct sockaddr_in addr;
int r;
if (!is_supported_system()) {
RETURN_SKIP("Unsupported system");
}
ASSERT_EQ(0, uv_ip4_addr("127.0.0.1", TEST_PORT, &addr));
r = uv_timer_init(uv_default_loop(), &timer);
ASSERT_EQ(r, 0);
/* Give it 1s to timeout. */
r = uv_timer_start(&timer, timer_cb, 1000, 0);
ASSERT_EQ(r, 0);
r = uv_tcp_init(uv_default_loop(), &conn);
ASSERT_EQ(r, 0);
r = uv_tcp_connect(&connect_req,
&conn,
(const struct sockaddr*) &addr,
connect_local_cb);
if (r == UV_ENETUNREACH)
RETURN_SKIP("Network unreachable.");
ASSERT_EQ(r, 0);
r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
ASSERT(r == 0);
MAKE_VALGRIND_HAPPY();
return 0;
}
TEST_IMPL(tcp6_local_connect_timeout) {
struct sockaddr_in6 addr;
int r;
if (!is_supported_system()) {
RETURN_SKIP("Unsupported system");
}
if (!can_ipv6()) {
RETURN_SKIP("IPv6 not supported");
}
ASSERT_EQ(0, uv_ip6_addr("::1", 9999, &addr));
r = uv_timer_init(uv_default_loop(), &timer);
ASSERT_EQ(r, 0);
/* Give it 1s to timeout. */
r = uv_timer_start(&timer, timer_cb, 1000, 0);
ASSERT_EQ(r, 0);
r = uv_tcp_init(uv_default_loop(), &conn);
ASSERT_EQ(r, 0);
r = uv_tcp_connect(&connect_req,
&conn,
(const struct sockaddr*) &addr,
connect_local_cb);
if (r == UV_ENETUNREACH)
RETURN_SKIP("Network unreachable.");
ASSERT_EQ(r, 0);
r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
ASSERT_EQ(r, 0);
MAKE_VALGRIND_HAPPY();
return 0;
}