Skip to content

Commit 3017a54

Browse files
amyssnippetjuanarbol
authored andcommitted
net: add setTOS and getTOS to Socket
PR-URL: #61503 Fixes: #61489 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 5020955 commit 3017a54

5 files changed

Lines changed: 429 additions & 86 deletions

File tree

doc/api/net.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,9 @@ it to interact with the client.
733733
<!-- YAML
734734
added: v0.3.4
735735
changes:
736+
- version: REPLACEME
737+
pr-url: https://github.com/nodejs/node/pull/61503
738+
description: Added `typeOfService` option.
736739
- version: v15.14.0
737740
pr-url: https://github.com/nodejs/node/pull/37735
738741
description: AbortSignal support was added.
@@ -774,6 +777,7 @@ changes:
774777
otherwise ignored. **Default:** `false`.
775778
* `signal` {AbortSignal} An Abort signal that may be used to destroy the
776779
socket.
780+
* `typeOfService` {number} The initial Type of Service (TOS) value.
777781
* `writable` {boolean} Allow writes on the socket when an `fd` is passed,
778782
otherwise ignored. **Default:** `false`.
779783
* Returns: {net.Socket}
@@ -1447,6 +1451,45 @@ If `timeout` is 0, then the existing idle timeout is disabled.
14471451
The optional `callback` parameter will be added as a one-time listener for the
14481452
[`'timeout'`][] event.
14491453

1454+
### `socket.getTypeOfService()`
1455+
1456+
<!-- YAML
1457+
added: REPLACEME
1458+
-->
1459+
1460+
* Returns: {integer} The current TOS value.
1461+
1462+
Returns the current Type of Service (TOS) field for IPv4 packets or Traffic
1463+
Class for IPv6 packets for this socket.
1464+
1465+
`setTypeOfService()` may be called before the socket is connected; the value
1466+
will be cached and applied when the socket establishes a connection.
1467+
`getTypeOfService()` will return the currently set value even before connection.
1468+
1469+
On some platforms (e.g., Linux), certain TOS/ECN bits may be masked or ignored,
1470+
and behavior can differ between IPv4 and IPv6 or dual-stack sockets. Callers
1471+
should verify platform-specific semantics.
1472+
1473+
### `socket.setTypeOfService(tos)`
1474+
1475+
<!-- YAML
1476+
added: REPLACEME
1477+
-->
1478+
1479+
* `tos` {integer} The TOS value to set (0-255).
1480+
* Returns: {net.Socket} The socket itself.
1481+
1482+
Sets the Type of Service (TOS) field for IPv4 packets or Traffic Class for IPv6
1483+
Packets sent from this socket. This can be used to prioritize network traffic.
1484+
1485+
`setTypeOfService()` may be called before the socket is connected; the value
1486+
will be cached and applied when the socket establishes a connection.
1487+
`getTypeOfService()` will return the currently set value even before connection.
1488+
1489+
On some platforms (e.g., Linux), certain TOS/ECN bits may be masked or ignored,
1490+
and behavior can differ between IPv4 and IPv6 or dual-stack sockets. Callers
1491+
should verify platform-specific semantics.
1492+
14501493
### `socket.timeout`
14511494

14521495
<!-- YAML

lib/net.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const {
5555
const assert = require('internal/assert');
5656
const {
5757
UV_EADDRINUSE,
58+
UV_EBADF,
5859
UV_EINVAL,
5960
UV_ENOTCONN,
6061
UV_ECANCELED,
@@ -359,6 +360,7 @@ const kBytesWritten = Symbol('kBytesWritten');
359360
const kSetNoDelay = Symbol('kSetNoDelay');
360361
const kSetKeepAlive = Symbol('kSetKeepAlive');
361362
const kSetKeepAliveInitialDelay = Symbol('kSetKeepAliveInitialDelay');
363+
const kSetTOS = Symbol('kSetTOS');
362364

363365
function Socket(options) {
364366
if (!(this instanceof Socket)) return new Socket(options);
@@ -474,6 +476,10 @@ function Socket(options) {
474476
this[kSetNoDelay] = Boolean(options.noDelay);
475477
this[kSetKeepAlive] = Boolean(options.keepAlive);
476478
this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000);
479+
if (options.typeOfService !== undefined) {
480+
validateInt32(options.typeOfService, 'options.typeOfService', 0, 255);
481+
}
482+
this[kSetTOS] = options.typeOfService;
477483

478484
// Shut down the socket when we're finished with it.
479485
this.on('end', onReadableStreamEnd);
@@ -653,6 +659,59 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
653659
};
654660

655661

662+
Socket.prototype.setTypeOfService = function(tos) {
663+
if (NumberIsNaN(tos)) {
664+
throw new ERR_INVALID_ARG_TYPE('tos', 'number', tos);
665+
}
666+
validateInt32(tos, 'tos', 0, 255);
667+
668+
if (!this._handle) {
669+
this[kSetTOS] = tos;
670+
return this;
671+
}
672+
673+
if (!this._handle.setTypeOfService) {
674+
this[kSetTOS] = tos;
675+
return this;
676+
}
677+
678+
if (tos !== this[kSetTOS]) {
679+
this[kSetTOS] = tos;
680+
const err = this._handle.setTypeOfService(tos);
681+
// On Windows, setting TOS is often restricted or returns error codes even if partially applied.
682+
// We treat this as a "best effort" operation and do not throw on Windows.
683+
if (err && !isWindows) {
684+
throw new ErrnoException(err, 'setTypeOfService');
685+
}
686+
}
687+
688+
return this;
689+
};
690+
691+
692+
Socket.prototype.getTypeOfService = function() {
693+
if (!this._handle) {
694+
// Return cached value if set, otherwise default to 0
695+
return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
696+
}
697+
698+
if (!this._handle.getTypeOfService) {
699+
return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
700+
}
701+
702+
const res = this._handle.getTypeOfService();
703+
if (typeof res === 'number' && res < 0) {
704+
// On Windows, getsockopt(IP_TOS) often fails. In that case, fall back
705+
// to the cached value we attempted to set, or 0.
706+
if (isWindows) {
707+
return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
708+
}
709+
throw new ErrnoException(res, 'getTypeOfService');
710+
}
711+
return res;
712+
};
713+
714+
656715
Socket.prototype.address = function() {
657716
return this._getsockname();
658717
};
@@ -1628,6 +1687,15 @@ function afterConnect(status, handle, req, readable, writable) {
16281687
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
16291688
}
16301689

1690+
if (self[kSetTOS] !== undefined && self._handle.setTypeOfService) {
1691+
const err = self._handle.setTypeOfService(self[kSetTOS]);
1692+
// On Windows, setting TOS is best-effort. If it fails, we shouldn't destroy
1693+
// the connection or emit an error, as the socket is otherwise healthy.
1694+
if (err && err !== UV_EBADF && !isWindows) {
1695+
self.emit('error', new ErrnoException(err, 'setTypeOfService'));
1696+
}
1697+
}
1698+
16311699
self.emit('connect');
16321700
self.emit('ready');
16331701

0 commit comments

Comments
 (0)