Skip to content

Commit 45494d5

Browse files
guybedfordaduh95
authored andcommitted
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 4438cb5 commit 45494d5

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
@@ -3745,7 +3745,7 @@ changes:
37453745
**Default:** `false`.
37463746
* `keepAlive` {boolean} If set to `true`, it enables keep-alive functionality
37473747
on the socket immediately after a new incoming connection is received,
3748-
similarly on what is done in \[`socket.setKeepAlive([enable][, initialDelay])`]\[`socket.setKeepAlive(enable, initialDelay)`].
3748+
similarly on what is done in [`socket.setKeepAlive()`][].
37493749
**Default:** `false`.
37503750
* `keepAliveInitialDelay` {number} If set to a positive number, it sets the
37513751
initial delay before the first keepalive probe is sent on an idle socket.
@@ -4761,7 +4761,7 @@ const agent2 = new http.Agent({ proxyEnv: process.env });
47614761
[`server.timeout`]: #servertimeout
47624762
[`setHeader(name, value)`]: #requestsetheadername-value
47634763
[`socket.connect()`]: net.md#socketconnectoptions-connectlistener
4764-
[`socket.setKeepAlive()`]: net.md#socketsetkeepaliveenable-initialdelay
4764+
[`socket.setKeepAlive()`]: net.md#socketsetkeepalive
47654765
[`socket.setNoDelay()`]: net.md#socketsetnodelaynodelay
47664766
[`socket.setTimeout()`]: net.md#socketsettimeouttimeout-callback
47674767
[`socket.unref()`]: net.md#socketunref

doc/api/net.md

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

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

13831444
<!-- YAML
13841445
added: v0.1.92
13851446
changes:
1447+
- version: REPLACEME
1448+
pr-url: https://github.com/nodejs/node/pull/63825
1449+
description: Added the `interval` and `count` arguments to configure
1450+
`TCP_KEEPINTVL` and `TCP_KEEPCNT`.
13861451
- version:
13871452
- v13.12.0
13881453
- v12.17.0
@@ -1392,22 +1457,12 @@ changes:
13921457

13931458
* `enable` {boolean} **Default:** `false`
13941459
* `initialDelay` {number} **Default:** `0`
1460+
* `interval` {number} **Default:** `1000`
1461+
* `count` {number} **Default:** `10`
13951462
* Returns: {net.Socket} The socket itself.
13961463

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

14121467
### `socket.setNoDelay([noDelay])`
14131468

@@ -2074,7 +2129,9 @@ net.isIPv6('fhqwhgads'); // returns false
20742129
[`socket.pause()`]: #socketpause
20752130
[`socket.resume()`]: #socketresume
20762131
[`socket.setEncoding()`]: #socketsetencodingencoding
2077-
[`socket.setKeepAlive()`]: #socketsetkeepaliveenable-initialdelay
2132+
[`socket.setKeepAlive()`]: #socketsetkeepalive
2133+
[`socket.setKeepAlive(enable)`]: #socketsetkeepaliveenable-initialdelay-interval-count
2134+
[`socket.setKeepAlive(options)`]: #socketsetkeepaliveoptions
20782135
[`socket.setTimeout()`]: #socketsettimeouttimeout-callback
20792136
[`socket.setTimeout(timeout)`]: #socketsettimeouttimeout-callback
20802137
[`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,
@@ -630,13 +632,25 @@ Socket.prototype.setNoDelay = function(enable) {
630632
};
631633

632634

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

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

@@ -647,12 +661,16 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
647661
if (enable !== this[kSetKeepAlive] ||
648662
(
649663
enable &&
650-
this[kSetKeepAliveInitialDelay] !== initialDelay
664+
(this[kSetKeepAliveInitialDelay] !== initialDelay ||
665+
this[kSetKeepAliveInterval] !== interval ||
666+
this[kSetKeepAliveCount] !== count)
651667
)
652668
) {
653669
this[kSetKeepAlive] = enable;
654670
this[kSetKeepAliveInitialDelay] = initialDelay;
655-
this._handle.setKeepAlive(enable, initialDelay);
671+
this[kSetKeepAliveInterval] = interval;
672+
this[kSetKeepAliveCount] = count;
673+
this._handle.setKeepAlive(enable, initialDelay, interval, count);
656674
}
657675

658676
return this;
@@ -1676,7 +1694,9 @@ function afterConnect(status, handle, req, readable, writable) {
16761694
}
16771695

16781696
if (self[kSetKeepAlive] && self._handle.setKeepAlive) {
1679-
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
1697+
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay],
1698+
self[kSetKeepAliveInterval],
1699+
self[kSetKeepAliveCount]);
16801700
}
16811701

16821702
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)