Skip to content

Commit e3f8906

Browse files
committed
net: support TCP_KEEPINTVL and TCP_KEEPCNT in setKeepAlive
Signed-off-by: Guy Bedford <guybedford@gmail.com>
1 parent 1be7464 commit e3f8906

5 files changed

Lines changed: 109 additions & 9 deletions

File tree

‎doc/api/net.md‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,11 +1380,15 @@ 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([enable][, initialDelay][, interval][, count])`
13841384

13851385
<!-- YAML
13861386
added: v0.1.92
13871387
changes:
1388+
- version: REPLACEME
1389+
pr-url: https://github.com/nodejs/node/pull/63825
1390+
description: Added the `interval` and `count` arguments to configure
1391+
`TCP_KEEPINTVL` and `TCP_KEEPCNT`.
13881392
- version:
13891393
- v13.12.0
13901394
- v12.17.0
@@ -1394,6 +1398,8 @@ changes:
13941398

13951399
* `enable` {boolean} **Default:** `false`
13961400
* `initialDelay` {number} **Default:** `0`
1401+
* `interval` {number} **Default:** `1000`
1402+
* `count` {number} **Default:** `10`
13971403
* Returns: {net.Socket} The socket itself.
13981404

13991405
Enable/disable keep-alive functionality, and optionally set the initial
@@ -1404,12 +1410,28 @@ data packet received and the first keepalive probe. Setting `0` for
14041410
`initialDelay` will leave the value unchanged from the default
14051411
(or previous) setting.
14061412

1413+
Set `interval` (in milliseconds) to set the delay between successive
1414+
keepalive probes once they begin (`TCP_KEEPINTVL`). Set `count` to the
1415+
number of unacknowledged probes sent before the connection is dropped
1416+
(`TCP_KEEPCNT`). Both are only applied when keep-alive is enabled.
1417+
Omitting `interval` or `count` uses the defaults of `1000` ms and `10`.
1418+
As with `initialDelay`, a non-positive `interval` or `count` leaves the
1419+
corresponding system default unchanged.
1420+
1421+
`initialDelay` and `interval` are specified in milliseconds but the
1422+
underlying socket options are configured in whole seconds; the values are
1423+
divided by `1000` and rounded down before being applied.
1424+
14071425
Enabling the keep-alive functionality will set the following socket options:
14081426

14091427
* `SO_KEEPALIVE=1`
1410-
* `TCP_KEEPIDLE=initialDelay`
1411-
* `TCP_KEEPCNT=10`
1412-
* `TCP_KEEPINTVL=1`
1428+
* `TCP_KEEPIDLE=initialDelay / 1000`
1429+
* `TCP_KEEPCNT=count`
1430+
* `TCP_KEEPINTVL=interval / 1000`
1431+
1432+
On Windows versions older than build 1709, keep-alive is configured through
1433+
`SIO_KEEPALIVE_VALS`, which has no probe-count field, so `count` is ignored on
1434+
those platforms.
14131435

14141436
### `socket.setNoDelay([noDelay])`
14151437

‎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: 17 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,18 @@ 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) {
633636
enable = Boolean(enable);
634637
const initialDelay = ~~(initialDelayMsecs / 1000);
638+
const interval = intervalMsecs === undefined ?
639+
undefined : ~~(intervalMsecs / 1000);
635640

636641
if (!this._handle) {
637642
this[kSetKeepAlive] = enable;
638643
this[kSetKeepAliveInitialDelay] = initialDelay;
644+
this[kSetKeepAliveInterval] = interval;
645+
this[kSetKeepAliveCount] = count;
639646
return this;
640647
}
641648

@@ -646,12 +653,16 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
646653
if (enable !== this[kSetKeepAlive] ||
647654
(
648655
enable &&
649-
this[kSetKeepAliveInitialDelay] !== initialDelay
656+
(this[kSetKeepAliveInitialDelay] !== initialDelay ||
657+
this[kSetKeepAliveInterval] !== interval ||
658+
this[kSetKeepAliveCount] !== count)
650659
)
651660
) {
652661
this[kSetKeepAlive] = enable;
653662
this[kSetKeepAliveInitialDelay] = initialDelay;
654-
this._handle.setKeepAlive(enable, initialDelay);
663+
this[kSetKeepAliveInterval] = interval;
664+
this[kSetKeepAliveCount] = count;
665+
this._handle.setKeepAlive(enable, initialDelay, interval, count);
655666
}
656667

657668
return this;
@@ -1675,7 +1686,9 @@ function afterConnect(status, handle, req, readable, writable) {
16751686
}
16761687

16771688
if (self[kSetKeepAlive] && self._handle.setKeepAlive) {
1678-
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
1689+
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay],
1690+
self[kSetKeepAliveInterval],
1691+
self[kSetKeepAliveCount]);
16791692
}
16801693

16811694
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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
}

0 commit comments

Comments
 (0)