Skip to content

Commit 1efdce3

Browse files
authored
net: support TCP_KEEPINTVL and TCP_KEEPCNT in setKeepAlive
Signed-off-by: Guy Bedford <guybedford@gmail.com> PR-URL: #63825 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
1 parent fc28d94 commit 1efdce3

6 files changed

Lines changed: 215 additions & 23 deletions

File tree

doc/api/http.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3763,7 +3763,7 @@ changes:
37633763
**Default:** `false`.
37643764
* `keepAlive` {boolean} If set to `true`, it enables keep-alive functionality
37653765
on the socket immediately after a new incoming connection is received,
3766-
similarly on what is done in \[`socket.setKeepAlive([enable][, initialDelay])`]\[`socket.setKeepAlive(enable, initialDelay)`].
3766+
similarly on what is done in [`socket.setKeepAlive()`][].
37673767
**Default:** `false`.
37683768
* `keepAliveInitialDelay` {number} If set to a positive number, it sets the
37693769
initial delay before the first keepalive probe is sent on an idle socket.
@@ -4779,7 +4779,7 @@ const agent2 = new http.Agent({ proxyEnv: process.env });
47794779
[`server.timeout`]: #servertimeout
47804780
[`setHeader(name, value)`]: #requestsetheadername-value
47814781
[`socket.connect()`]: net.md#socketconnectoptions-connectlistener
4782-
[`socket.setKeepAlive()`]: net.md#socketsetkeepaliveenable-initialdelay
4782+
[`socket.setKeepAlive()`]: net.md#socketsetkeepalive
47834783
[`socket.setNoDelay()`]: net.md#socketsetnodelaynodelay
47844784
[`socket.setTimeout()`]: net.md#socketsettimeouttimeout-callback
47854785
[`socket.unref()`]: net.md#socketunref

doc/api/net.md

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,11 +1380,76 @@ added: v0.1.90
13801380
Set the encoding for the socket as a [Readable Stream][]. See
13811381
[`readable.setEncoding()`][] for more information.
13821382

1383-
### `socket.setKeepAlive([enable][, initialDelay])`
1383+
### `socket.setKeepAlive()`
1384+
1385+
Enable/disable keep-alive functionality, and optionally configure the
1386+
keepalive probe timing. Returns the socket itself.
1387+
1388+
Possible signatures:
1389+
1390+
* [`socket.setKeepAlive([options])`][`socket.setKeepAlive(options)`]
1391+
* [`socket.setKeepAlive([enable][, initialDelay][, interval][, count])`][`socket.setKeepAlive(enable)`]
1392+
1393+
Enabling keep-alive sets the initial delay before the first keepalive probe is
1394+
sent on an idle socket.
1395+
1396+
Set `initialDelay` (in milliseconds) to set the delay between the last
1397+
data packet received and the first keepalive probe. Setting `0` for
1398+
`initialDelay` will leave the value unchanged from the default
1399+
(or previous) setting.
1400+
1401+
Set `interval` (in milliseconds) to set the delay between successive
1402+
keepalive probes once they begin (`TCP_KEEPINTVL`). Set `count` to the
1403+
number of unacknowledged probes sent before the connection is dropped
1404+
(`TCP_KEEPCNT`). Both are only applied when keep-alive is enabled.
1405+
Omitting `interval` or `count` uses the defaults of `1000` ms and `10`.
1406+
As with `initialDelay`, a non-positive `interval` or `count` leaves the
1407+
corresponding system default unchanged.
1408+
1409+
`initialDelay` and `interval` are specified in milliseconds but the
1410+
underlying socket options are configured in whole seconds; the values are
1411+
divided by `1000` and rounded down before being applied.
1412+
1413+
Enabling the keep-alive functionality will set the following socket options:
1414+
1415+
* `SO_KEEPALIVE=1`
1416+
* `TCP_KEEPIDLE=initialDelay / 1000`
1417+
* `TCP_KEEPCNT=count`
1418+
* `TCP_KEEPINTVL=interval / 1000`
1419+
1420+
On Windows versions older than build 1709, keep-alive is configured through
1421+
`SIO_KEEPALIVE_VALS`, which has no probe-count field, so `count` is ignored on
1422+
those platforms.
1423+
1424+
#### `socket.setKeepAlive([options])`
1425+
1426+
<!-- YAML
1427+
added: REPLACEME
1428+
-->
1429+
1430+
* `options` {Object}
1431+
* `enable` {boolean} **Default:** `false`
1432+
* `initialDelay` {number} **Default:** `0`
1433+
* `interval` {number} **Default:** `1000`
1434+
* `count` {number} **Default:** `10`
1435+
* Returns: {net.Socket} The socket itself.
1436+
1437+
Configure keep-alive using an options object. See [`socket.setKeepAlive()`][]
1438+
for a description of each property.
1439+
1440+
```js
1441+
socket.setKeepAlive({ enable: true, initialDelay: 1000, interval: 1000, count: 10 });
1442+
```
1443+
1444+
#### `socket.setKeepAlive([enable][, initialDelay][, interval][, count])`
13841445

13851446
<!-- YAML
13861447
added: v0.1.92
13871448
changes:
1449+
- version: REPLACEME
1450+
pr-url: https://github.com/nodejs/node/pull/63825
1451+
description: Added the `interval` and `count` arguments to configure
1452+
`TCP_KEEPINTVL` and `TCP_KEEPCNT`.
13881453
- version:
13891454
- v13.12.0
13901455
- v12.17.0
@@ -1394,22 +1459,12 @@ changes:
13941459

13951460
* `enable` {boolean} **Default:** `false`
13961461
* `initialDelay` {number} **Default:** `0`
1462+
* `interval` {number} **Default:** `1000`
1463+
* `count` {number} **Default:** `10`
13971464
* Returns: {net.Socket} The socket itself.
13981465

1399-
Enable/disable keep-alive functionality, and optionally set the initial
1400-
delay before the first keepalive probe is sent on an idle socket.
1401-
1402-
Set `initialDelay` (in milliseconds) to set the delay between the last
1403-
data packet received and the first keepalive probe. Setting `0` for
1404-
`initialDelay` will leave the value unchanged from the default
1405-
(or previous) setting.
1406-
1407-
Enabling the keep-alive functionality will set the following socket options:
1408-
1409-
* `SO_KEEPALIVE=1`
1410-
* `TCP_KEEPIDLE=initialDelay`
1411-
* `TCP_KEEPCNT=10`
1412-
* `TCP_KEEPINTVL=1`
1466+
Configure keep-alive using positional arguments. See
1467+
[`socket.setKeepAlive()`][] for a description of each argument.
14131468

14141469
### `socket.setNoDelay([noDelay])`
14151470

@@ -2080,7 +2135,9 @@ net.isIPv6('fhqwhgads'); // returns false
20802135
[`socket.pause()`]: #socketpause
20812136
[`socket.resume()`]: #socketresume
20822137
[`socket.setEncoding()`]: #socketsetencodingencoding
2083-
[`socket.setKeepAlive()`]: #socketsetkeepaliveenable-initialdelay
2138+
[`socket.setKeepAlive()`]: #socketsetkeepalive
2139+
[`socket.setKeepAlive(enable)`]: #socketsetkeepaliveenable-initialdelay-interval-count
2140+
[`socket.setKeepAlive(options)`]: #socketsetkeepaliveoptions
20842141
[`socket.setTimeout()`]: #socketsettimeouttimeout-callback
20852142
[`socket.setTimeout(timeout)`]: #socketsettimeouttimeout-callback
20862143
[`stream.getDefaultHighWaterMark()`]: stream.md#streamgetdefaulthighwatermarkobjectmode

lib/internal/net.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ module.exports = {
103103
kSetNoDelay: Symbol('kSetNoDelay'),
104104
kSetKeepAlive: Symbol('kSetKeepAlive'),
105105
kSetKeepAliveInitialDelay: Symbol('kSetKeepAliveInitialDelay'),
106+
kSetKeepAliveInterval: Symbol('kSetKeepAliveInterval'),
107+
kSetKeepAliveCount: Symbol('kSetKeepAliveCount'),
106108
isIP,
107109
isIPv4,
108110
isIPv6,

lib/net.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const {
5151
kSetNoDelay,
5252
kSetKeepAlive,
5353
kSetKeepAliveInitialDelay,
54+
kSetKeepAliveInterval,
55+
kSetKeepAliveCount,
5456
isIP,
5557
isIPv4,
5658
isIPv6,
@@ -629,13 +631,25 @@ Socket.prototype.setNoDelay = function(enable) {
629631
};
630632

631633

632-
Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
634+
Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs,
635+
intervalMsecs, count) {
636+
if (enable !== null && typeof enable === 'object') {
637+
const options = enable;
638+
enable = options.enable;
639+
initialDelayMsecs = options.initialDelay;
640+
intervalMsecs = options.interval;
641+
count = options.count;
642+
}
633643
enable = Boolean(enable);
634644
const initialDelay = ~~(initialDelayMsecs / 1000);
645+
const interval = intervalMsecs === undefined ?
646+
undefined : ~~(intervalMsecs / 1000);
635647

636648
if (!this._handle) {
637649
this[kSetKeepAlive] = enable;
638650
this[kSetKeepAliveInitialDelay] = initialDelay;
651+
this[kSetKeepAliveInterval] = interval;
652+
this[kSetKeepAliveCount] = count;
639653
return this;
640654
}
641655

@@ -646,12 +660,16 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
646660
if (enable !== this[kSetKeepAlive] ||
647661
(
648662
enable &&
649-
this[kSetKeepAliveInitialDelay] !== initialDelay
663+
(this[kSetKeepAliveInitialDelay] !== initialDelay ||
664+
this[kSetKeepAliveInterval] !== interval ||
665+
this[kSetKeepAliveCount] !== count)
650666
)
651667
) {
652668
this[kSetKeepAlive] = enable;
653669
this[kSetKeepAliveInitialDelay] = initialDelay;
654-
this._handle.setKeepAlive(enable, initialDelay);
670+
this[kSetKeepAliveInterval] = interval;
671+
this[kSetKeepAliveCount] = count;
672+
this._handle.setKeepAlive(enable, initialDelay, interval, count);
655673
}
656674

657675
return this;
@@ -1675,7 +1693,9 @@ function afterConnect(status, handle, req, readable, writable) {
16751693
}
16761694

16771695
if (self[kSetKeepAlive] && self._handle.setKeepAlive) {
1678-
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
1696+
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay],
1697+
self[kSetKeepAliveInterval],
1698+
self[kSetKeepAliveCount]);
16791699
}
16801700

16811701
if (self[kSetTOS] !== undefined && self._handle.setTypeOfService) {

src/tcp_wrap.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,14 @@ void TCPWrap::SetKeepAlive(const FunctionCallbackInfo<Value>& args) {
209209
int enable;
210210
if (!args[0]->Int32Value(env->context()).To(&enable)) return;
211211
unsigned int delay = static_cast<unsigned int>(args[1].As<Uint32>()->Value());
212-
int err = uv_tcp_keepalive(&wrap->handle_, enable, delay);
212+
// interval and count are optional. Fall back to the libuv defaults
213+
// (1 second, 10 probes) when they are not provided so that callers using
214+
// the legacy two-argument form of this handle method keep working.
215+
unsigned int interval = 1;
216+
unsigned int count = 10;
217+
if (args[2]->IsUint32()) interval = args[2].As<Uint32>()->Value();
218+
if (args[3]->IsUint32()) count = args[3].As<Uint32>()->Value();
219+
int err = uv_tcp_keepalive_ex(&wrap->handle_, enable, delay, interval, count);
213220
args.GetReturnValue().Set(err);
214221
}
215222

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const net = require('net');
6+
7+
// Verifies that setKeepAlive() forwards the keepalive probe interval
8+
// (TCP_KEEPINTVL) and probe count (TCP_KEEPCNT) to the handle. The interval is
9+
// converted from milliseconds to whole seconds, mirroring the initialDelay
10+
// handling and uv_tcp_keepalive_ex().
11+
12+
// Explicit setKeepAlive(enable, initialDelay, interval, count) forwards all
13+
// four values, converting milliseconds to seconds for the delays.
14+
{
15+
const server = net.createServer();
16+
server.listen(0, common.mustCall(() => {
17+
const client = net.connect(
18+
{ port: server.address().port },
19+
common.mustCall(() => {
20+
client._handle.setKeepAlive = common.mustCall(
21+
(enable, delay, interval, count) => {
22+
assert.strictEqual(enable, true);
23+
assert.strictEqual(delay, 5);
24+
assert.strictEqual(interval, 10);
25+
assert.strictEqual(count, 9);
26+
});
27+
client.setKeepAlive(true, 5000, 10000, 9);
28+
client.end();
29+
}));
30+
31+
client.on('end', common.mustCall(() => server.close()));
32+
}));
33+
}
34+
35+
// Omitting interval/count passes undefined through; the handle applies the
36+
// libuv defaults (1s interval, 10 probes).
37+
{
38+
const server = net.createServer();
39+
server.listen(0, common.mustCall(() => {
40+
const client = net.connect(
41+
{ port: server.address().port },
42+
common.mustCall(() => {
43+
client._handle.setKeepAlive = common.mustCall(
44+
(enable, delay, interval, count) => {
45+
assert.strictEqual(enable, true);
46+
assert.strictEqual(delay, 5);
47+
assert.strictEqual(interval, undefined);
48+
assert.strictEqual(count, undefined);
49+
});
50+
client.setKeepAlive(true, 5000);
51+
client.end();
52+
}));
53+
54+
client.on('end', common.mustCall(() => server.close()));
55+
}));
56+
}
57+
58+
// An options object as the first argument is equivalent to the positional
59+
// form, forwarding enable/initialDelay/interval/count to the handle.
60+
{
61+
const server = net.createServer();
62+
server.listen(0, common.mustCall(() => {
63+
const client = net.connect(
64+
{ port: server.address().port },
65+
common.mustCall(() => {
66+
client._handle.setKeepAlive = common.mustCall(
67+
(enable, delay, interval, count) => {
68+
assert.strictEqual(enable, true);
69+
assert.strictEqual(delay, 5);
70+
assert.strictEqual(interval, 10);
71+
assert.strictEqual(count, 9);
72+
});
73+
client.setKeepAlive({
74+
enable: true,
75+
initialDelay: 5000,
76+
interval: 10000,
77+
count: 9,
78+
});
79+
client.end();
80+
}));
81+
82+
client.on('end', common.mustCall(() => server.close()));
83+
}));
84+
}
85+
86+
// Omitted options properties behave like omitted positional arguments.
87+
{
88+
const server = net.createServer();
89+
server.listen(0, common.mustCall(() => {
90+
const client = net.connect(
91+
{ port: server.address().port },
92+
common.mustCall(() => {
93+
client._handle.setKeepAlive = common.mustCall(
94+
(enable, delay, interval, count) => {
95+
assert.strictEqual(enable, true);
96+
assert.strictEqual(delay, 5);
97+
assert.strictEqual(interval, undefined);
98+
assert.strictEqual(count, undefined);
99+
});
100+
client.setKeepAlive({ enable: true, initialDelay: 5000 });
101+
client.end();
102+
}));
103+
104+
client.on('end', common.mustCall(() => server.close()));
105+
}));
106+
}

0 commit comments

Comments
 (0)