Skip to content

Commit 3a9a6e3

Browse files
authored
tcp: support customizing TCP_KEEPINTVL and TCP_KEEPCNT
Implement `uv_tcp_keepalive_ex` function that extends `uv_tcp_keepalive` to support `TCP_KEEPINTVL` and `TCP_KEEPCN` socket options in addition to TCP_KEEPIDLE.
1 parent 71ec5c0 commit 3a9a6e3

8 files changed

Lines changed: 199 additions & 59 deletions

File tree

docs/src/tcp.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,35 @@ API
9191
9292
.. versionchanged:: 1.49.0 If `delay` is less than 1 then ``UV_EINVAL``` is returned.
9393
94+
.. c:function:: int uv_tcp_keepalive_ex(uv_tcp_t* handle, int on, unsigned int idle, unsigned int intvl, unsigned int cnt)
95+
96+
Enable / disable TCP keep-alive with all socket options: `TCP_KEEPIDLE`, `TCP_KEEPINTVL` and `TCP_KEEPCNT`.
97+
`idle` is the value for `TCP_KEEPIDLE`, `intvl` is the value for `TCP_KEEPINTVL`,
98+
`cnt` is the value for `TCP_KEEPCNT`, ignored when `on` is zero.
99+
100+
With TCP keep-alive enabled, `idle` is the time (in seconds) the connection needs to remain idle before
101+
TCP starts sending keep-alive probes. `intvl` is the time (in seconds) between individual keep-alive probes.
102+
TCP will drop the connection after sending `cnt` probes without getting any replies from the peer, then the
103+
handle is destroyed with a ``UV_ETIMEDOUT`` error passed to the corresponding callback.
104+
105+
If one of `idle`, `intvl`, or `cnt` is less than 1, ``UV_EINVAL`` is returned.
106+
107+
.. versionchanged:: 1.52.0 added support of setting `TCP_KEEPINTVL` and `TCP_KEEPCNT` socket options.
108+
109+
.. note::
110+
Ensure that the socket options are supported by the underlying operating system.
111+
Currently supported platforms:
112+
- AIX
113+
- DragonFlyBSD
114+
- FreeBSD
115+
- HP-UX
116+
- illumos
117+
- Linux
118+
- macOS
119+
- NetBSD
120+
- Solaris
121+
- Windows
122+
94123
.. c:function:: int uv_tcp_simultaneous_accepts(uv_tcp_t* handle, int enable)
95124
96125
Enable / disable simultaneous asynchronous accept requests that are

include/uv.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,11 @@ UV_EXTERN int uv_tcp_nodelay(uv_tcp_t* handle, int enable);
604604
UV_EXTERN int uv_tcp_keepalive(uv_tcp_t* handle,
605605
int enable,
606606
unsigned int delay);
607+
UV_EXTERN int uv_tcp_keepalive_ex(uv_tcp_t* handle,
608+
int on,
609+
unsigned int idle,
610+
unsigned int intvl,
611+
unsigned int cnt);
607612
UV_EXTERN int uv_tcp_simultaneous_accepts(uv_tcp_t* handle, int enable);
608613

609614
enum uv_tcp_flags {

src/unix/internal.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,11 @@ int uv__slurp(const char* filename, char* buf, size_t len);
299299
/* tcp */
300300
int uv__tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb);
301301
int uv__tcp_nodelay(int fd, int on);
302-
int uv__tcp_keepalive(int fd, int on, unsigned int delay);
302+
int uv__tcp_keepalive(int fd,
303+
int on,
304+
unsigned int idle,
305+
unsigned int intvl,
306+
unsigned int cnt);
303307

304308
/* tty */
305309
void uv__tty_close(uv_tty_t* handle);

src/unix/stream.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ int uv__stream_open(uv_stream_t* stream, int fd, int flags) {
417417

418418
/* TODO Use delay the user passed in. */
419419
if ((stream->flags & UV_HANDLE_TCP_KEEPALIVE) &&
420-
uv__tcp_keepalive(fd, 1, 60)) {
420+
uv__tcp_keepalive(fd, 1, 60, 1, 10)) {
421421
return UV__ERR(errno);
422422
}
423423
}

src/unix/tcp.c

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -466,22 +466,18 @@ int uv__tcp_nodelay(int fd, int on) {
466466
#else
467467
#define UV_KEEPALIVE_FACTOR(x)
468468
#endif
469-
int uv__tcp_keepalive(int fd, int on, unsigned int delay) {
470-
int idle;
471-
int intvl;
472-
int cnt;
473-
474-
(void) &idle;
475-
(void) &intvl;
476-
(void) &cnt;
477-
469+
int uv__tcp_keepalive(int fd,
470+
int on,
471+
unsigned int idle,
472+
unsigned int intvl,
473+
unsigned int cnt) {
478474
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)))
479475
return UV__ERR(errno);
480476

481477
if (!on)
482478
return 0;
483479

484-
if (delay < 1)
480+
if (idle < 1 || intvl < 1 || cnt < 1)
485481
return UV_EINVAL;
486482

487483
#ifdef __sun
@@ -507,13 +503,16 @@ int uv__tcp_keepalive(int fd, int on, unsigned int delay) {
507503
* The TCP connection will be aborted after certain amount of probes, which is set by TCP_KEEPCNT, without receiving response.
508504
*/
509505

510-
idle = delay;
511-
/* Kernel expects at least 10 seconds. */
506+
/* Kernel expects at least 10 seconds for TCP_KEEPIDLE and TCP_KEEPINTVL. */
512507
if (idle < 10)
513508
idle = 10;
514-
/* Kernel expects at most 10 days. */
509+
if (intvl < 10)
510+
intvl = 10;
511+
/* Kernel expects at most 10 days for TCP_KEEPIDLE and TCP_KEEPINTVL. */
515512
if (idle > 10*24*60*60)
516513
idle = 10*24*60*60;
514+
if (intvl > 10*24*60*60)
515+
intvl = 10*24*60*60;
517516

518517
UV_KEEPALIVE_FACTOR(idle);
519518

@@ -523,12 +522,10 @@ int uv__tcp_keepalive(int fd, int on, unsigned int delay) {
523522
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)))
524523
return UV__ERR(errno);
525524

526-
intvl = 10; /* required at least 10 seconds */
527525
UV_KEEPALIVE_FACTOR(intvl);
528526
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)))
529527
return UV__ERR(errno);
530528

531-
cnt = 1; /* 1 retry, ensure (TCP_KEEPINTVL * TCP_KEEPCNT) is 10 seconds */
532529
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)))
533530
return UV__ERR(errno);
534531
#else
@@ -540,15 +537,14 @@ int uv__tcp_keepalive(int fd, int on, unsigned int delay) {
540537

541538
/* Note that the consequent probes will not be sent at equal intervals on Solaris,
542539
* but will be sent using the exponential backoff algorithm. */
543-
int time_to_abort = 10; /* 10 seconds */
540+
unsigned int time_to_abort = intvl * cnt;
544541
UV_KEEPALIVE_FACTOR(time_to_abort);
545542
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, &time_to_abort, sizeof(time_to_abort)))
546543
return UV__ERR(errno);
547544
#endif
548545

549546
#else /* !defined(__sun) */
550547

551-
idle = delay;
552548
UV_KEEPALIVE_FACTOR(idle);
553549
#ifdef TCP_KEEPIDLE
554550
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)))
@@ -560,14 +556,12 @@ int uv__tcp_keepalive(int fd, int on, unsigned int delay) {
560556
#endif
561557

562558
#ifdef TCP_KEEPINTVL
563-
intvl = 1; /* 1 second; same as default on Win32 */
564559
UV_KEEPALIVE_FACTOR(intvl);
565560
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)))
566561
return UV__ERR(errno);
567562
#endif
568563

569564
#ifdef TCP_KEEPCNT
570-
cnt = 10; /* 10 retries; same as hardcoded on Win32 */
571565
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)))
572566
return UV__ERR(errno);
573567
#endif
@@ -595,11 +589,20 @@ int uv_tcp_nodelay(uv_tcp_t* handle, int on) {
595589
}
596590

597591

598-
int uv_tcp_keepalive(uv_tcp_t* handle, int on, unsigned int delay) {
592+
int uv_tcp_keepalive(uv_tcp_t* handle, int on, unsigned int idle) {
593+
return uv_tcp_keepalive_ex(handle, on, idle, 1, 10);
594+
}
595+
596+
597+
int uv_tcp_keepalive_ex(uv_tcp_t* handle,
598+
int on,
599+
unsigned int idle,
600+
unsigned int intvl,
601+
unsigned int cnt) {
599602
int err;
600603

601604
if (uv__stream_fd(handle) != -1) {
602-
err =uv__tcp_keepalive(uv__stream_fd(handle), on, delay);
605+
err = uv__tcp_keepalive(uv__stream_fd(handle), on, idle, intvl, cnt);
603606
if (err)
604607
return err;
605608
}
@@ -609,7 +612,7 @@ int uv_tcp_keepalive(uv_tcp_t* handle, int on, unsigned int delay) {
609612
else
610613
handle->flags &= ~UV_HANDLE_TCP_KEEPALIVE;
611614

612-
/* TODO Store delay if uv__stream_fd(handle) == -1 but don't want to enlarge
615+
/* TODO Store idle if uv__stream_fd(handle) == -1 but don't want to enlarge
613616
* uv_tcp_t with an int that's almost never used...
614617
*/
615618

src/win/tcp.c

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,99 @@ static int uv__tcp_nodelay(uv_tcp_t* handle, SOCKET socket, int enable) {
4949
}
5050

5151

52-
static int uv__tcp_keepalive(uv_tcp_t* handle, SOCKET socket, int enable, unsigned int delay) {
52+
/*
53+
* Check if Windows version is 10.0.16299 (Windows 10, version 1709) or later.
54+
*/
55+
static int minimal_windows10_version1709(void) {
56+
OSVERSIONINFOW os_info;
57+
if (!pRtlGetVersion)
58+
return 0;
59+
pRtlGetVersion(&os_info);
60+
if (os_info.dwMajorVersion < 10)
61+
return 0;
62+
if (os_info.dwMajorVersion > 10)
63+
return 1;
64+
if (os_info.dwMinorVersion > 0)
65+
return 1;
66+
return os_info.dwBuildNumber >= 16299;
67+
}
68+
69+
70+
static int uv__tcp_keepalive(uv_tcp_t* handle,
71+
SOCKET socket,
72+
int on,
73+
unsigned int idle,
74+
unsigned int intvl,
75+
unsigned int cnt) {
5376
if (setsockopt(socket,
5477
SOL_SOCKET,
5578
SO_KEEPALIVE,
56-
(const char*)&enable,
57-
sizeof enable) == -1) {
79+
(const char*)&on,
80+
sizeof on) == -1) {
5881
return WSAGetLastError();
5982
}
6083

61-
if (!enable)
84+
if (!on)
6285
return 0;
6386

64-
if (delay < 1)
87+
if (idle < 1 || intvl < 1 || cnt < 1)
6588
return UV_EINVAL;
6689

67-
if (setsockopt(socket,
68-
IPPROTO_TCP,
69-
TCP_KEEPALIVE,
70-
(const char*)&delay,
71-
sizeof delay) == -1) {
72-
return WSAGetLastError();
90+
/* Windows 10, version 1709 (build 10.0.16299) and later require second units
91+
* for TCP keepalive options. */
92+
if (minimal_windows10_version1709()) {
93+
if (setsockopt(socket,
94+
IPPROTO_TCP,
95+
TCP_KEEPIDLE,
96+
(const char*)&idle,
97+
sizeof idle) == -1) {
98+
return WSAGetLastError();
99+
}
100+
101+
if (setsockopt(socket,
102+
IPPROTO_TCP,
103+
TCP_KEEPINTVL,
104+
(const char*)&intvl,
105+
sizeof intvl) == -1) {
106+
return WSAGetLastError();
107+
}
108+
109+
if (setsockopt(socket,
110+
IPPROTO_TCP,
111+
TCP_KEEPCNT,
112+
(const char*)&cnt,
113+
sizeof cnt) == -1) {
114+
return WSAGetLastError();
115+
}
116+
117+
return 0;
73118
}
74119

120+
/* For those versions prior to Windows 10 version 1709,
121+
* we fall back to SIO_KEEPALIVE_VALS that expects millisecond units.
122+
* The SIO_KEEPALIVE_VALS IOCTL is supported on Windows 2000
123+
* and later versions of the operating system. */
124+
struct tcp_keepalive keepalive;
125+
keepalive.onoff = on;
126+
keepalive.keepalivetime = idle * 1000;
127+
keepalive.keepaliveinterval = intvl * 1000;
128+
/* On Windows Vista and later, the number of keep-alive probes
129+
* (data retransmissions) is set to 10 and cannot be changed.
130+
* On Windows Server 2003, Windows XP, and Windows 2000, the default setting
131+
* for number of keep-alive probes is 5 and cannot be changed programmatically.
132+
*/
133+
DWORD dummy;
134+
if (WSAIoctl(socket,
135+
SIO_KEEPALIVE_VALS,
136+
(LPVOID) &keepalive,
137+
sizeof keepalive,
138+
NULL,
139+
0,
140+
&dummy,
141+
NULL,
142+
NULL) == -1)
143+
return WSAGetLastError();
144+
75145
return 0;
76146
}
77147

@@ -132,7 +202,7 @@ static int uv__tcp_set_socket(uv_loop_t* loop,
132202

133203
/* TODO: Use stored delay. */
134204
if (handle->flags & UV_HANDLE_TCP_KEEPALIVE) {
135-
err = uv__tcp_keepalive(handle, socket, 1, 60);
205+
err = uv__tcp_keepalive(handle, socket, 1, 60, 1, 10);
136206
if (err)
137207
return err;
138208
}
@@ -749,20 +819,6 @@ static int uv__is_loopback(const struct sockaddr_storage* storage) {
749819
return 0;
750820
}
751821

752-
// Check if Windows version is 10.0.16299 or later
753-
static int uv__is_fast_loopback_fail_supported(void) {
754-
OSVERSIONINFOW os_info;
755-
if (!pRtlGetVersion)
756-
return 0;
757-
pRtlGetVersion(&os_info);
758-
if (os_info.dwMajorVersion < 10)
759-
return 0;
760-
if (os_info.dwMajorVersion > 10)
761-
return 1;
762-
if (os_info.dwMinorVersion > 0)
763-
return 1;
764-
return os_info.dwBuildNumber >= 16299;
765-
}
766822

767823
static int uv__tcp_try_connect(uv_connect_t* req,
768824
uv_tcp_t* handle,
@@ -809,7 +865,7 @@ static int uv__tcp_try_connect(uv_connect_t* req,
809865
* is not reachable, instead of waiting for 2s. We do not care if this fails.
810866
* This only works on Windows version 10.0.16299 and later.
811867
*/
812-
if (uv__is_fast_loopback_fail_supported() && uv__is_loopback(&converted)) {
868+
if (minimal_windows10_version1709() && uv__is_loopback(&converted)) {
813869
memset(&retransmit_ioctl, 0, sizeof(retransmit_ioctl));
814870
retransmit_ioctl.Rtt = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS;
815871
retransmit_ioctl.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS;
@@ -1335,22 +1391,30 @@ int uv_tcp_nodelay(uv_tcp_t* handle, int enable) {
13351391
}
13361392

13371393

1338-
int uv_tcp_keepalive(uv_tcp_t* handle, int enable, unsigned int delay) {
1394+
int uv_tcp_keepalive(uv_tcp_t* handle, int on, unsigned int idle) {
1395+
return uv_tcp_keepalive_ex(handle, on, idle, 1, 10);
1396+
}
1397+
1398+
int uv_tcp_keepalive_ex(uv_tcp_t* handle,
1399+
int on,
1400+
unsigned int idle,
1401+
unsigned int intvl,
1402+
unsigned int cnt) {
13391403
int err;
13401404

13411405
if (handle->socket != INVALID_SOCKET) {
1342-
err = uv__tcp_keepalive(handle, handle->socket, enable, delay);
1406+
err = uv__tcp_keepalive(handle, handle->socket, on, idle, intvl, cnt);
13431407
if (err)
13441408
return uv_translate_sys_error(err);
13451409
}
13461410

1347-
if (enable) {
1411+
if (on) {
13481412
handle->flags |= UV_HANDLE_TCP_KEEPALIVE;
13491413
} else {
13501414
handle->flags &= ~UV_HANDLE_TCP_KEEPALIVE;
13511415
}
13521416

1353-
/* TODO: Store delay if handle->socket isn't created yet. */
1417+
/* TODO: Store idle if handle->socket isn't created yet. */
13541418

13551419
return 0;
13561420
}

0 commit comments

Comments
 (0)