From eb53d09ac621756ce8cd3937dc504371bfae9b36 Mon Sep 17 00:00:00 2001 From: Amol Yadav Date: Sat, 24 Jan 2026 16:37:21 +0530 Subject: [PATCH 1/7] net: add setTOS and getTOS to Socket --- lib/net.js | 46 ++++++++++++++++ src/tcp_wrap.cc | 81 +++++++++++++++++++++++++++- src/tcp_wrap.h | 2 + test/parallel/test-net-socket-tos.js | 59 ++++++++++++++++++++ 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-net-socket-tos.js diff --git a/lib/net.js b/lib/net.js index a391e9da30f861..c3694fe9d7efdd 100644 --- a/lib/net.js +++ b/lib/net.js @@ -358,6 +358,7 @@ const kBytesWritten = Symbol('kBytesWritten'); const kSetNoDelay = Symbol('kSetNoDelay'); const kSetKeepAlive = Symbol('kSetKeepAlive'); const kSetKeepAliveInitialDelay = Symbol('kSetKeepAliveInitialDelay'); +const kSetTOS = Symbol('kSetTOS'); function Socket(options) { if (!(this instanceof Socket)) return new Socket(options); @@ -473,6 +474,7 @@ function Socket(options) { this[kSetNoDelay] = Boolean(options.noDelay); this[kSetKeepAlive] = Boolean(options.keepAlive); this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000); + this[kSetTOS] = options.TOS; // Shut down the socket when we're finished with it. this.on('end', onReadableStreamEnd); @@ -652,6 +654,46 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) { }; +Socket.prototype.setTOS = function(tos) { + if (NumberIsNaN(tos)) { + throw new ERR_INVALID_ARG_TYPE('tos', 'number', tos); + } + validateInt32(tos, 'tos', 0, 255); + + if (!this._handle) { + this[kSetTOS] = tos; + return this; + } + + if (this._handle.setTOS && tos !== this[kSetTOS]) { + this[kSetTOS] = tos; + const err = this._handle.setTOS(tos); + if (err) { + throw new ErrnoException(err, 'setTOS'); + } + } + + return this; +}; + + +Socket.prototype.getTOS = function() { + if (!this._handle) { + return this[kSetTOS]; + } + + if (!this._handle.getTOS) { + return this[kSetTOS]; + } + + const res = this._handle.getTOS(); + if (typeof res === 'number' && res < 0) { + throw new ErrnoException(res, 'getTOS'); + } + return res; +}; + + Socket.prototype.address = function() { return this._getsockname(); }; @@ -1619,6 +1661,10 @@ function afterConnect(status, handle, req, readable, writable) { self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]); } + if (self[kSetTOS] !== undefined && self._handle.setTOS) { + self._handle.setTOS(self[kSetTOS]); + } + self.emit('connect'); self.emit('ready'); diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index b2507f6dfbea1b..5085f245a4cf23 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -33,7 +33,10 @@ #include "stream_wrap.h" #include "util-inl.h" -#include +#ifndef _WIN32 +#include +#include +#endif namespace node { @@ -106,6 +109,8 @@ void TCPWrap::Initialize(Local target, GetSockOrPeerName); SetProtoMethod(isolate, t, "setNoDelay", SetNoDelay); SetProtoMethod(isolate, t, "setKeepAlive", SetKeepAlive); + SetProtoMethod(isolate, t, "setTOS", SetTOS); + SetProtoMethod(isolate, t, "getTOS", GetTOS); SetProtoMethod(isolate, t, "reset", Reset); #ifdef _WIN32 @@ -145,6 +150,8 @@ void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetSockOrPeerName); registry->Register(SetNoDelay); registry->Register(SetKeepAlive); + registry->Register(SetTOS); + registry->Register(GetTOS); registry->Register(Reset); #ifdef _WIN32 registry->Register(SetSimultaneousAccepts); @@ -209,6 +216,78 @@ void TCPWrap::SetKeepAlive(const FunctionCallbackInfo& args) { } +void TCPWrap::SetTOS(const FunctionCallbackInfo& args) { + TCPWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP( + &wrap, args.This(), args.GetReturnValue().Set(UV_EBADF)); + Environment* env = wrap->env(); + int tos; + if (!args[0]->Int32Value(env->context()).To(&tos)) return; + + int fd; + int err = uv_fileno(reinterpret_cast(&wrap->handle_), &fd); + if (err != 0) { + args.GetReturnValue().Set(err); + return; + } + +#ifdef _WIN32 + args.GetReturnValue().Set(UV_ENOSYS); +#else + // Try IPv4 first + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == 0) { + args.GetReturnValue().Set(0); + return; + } + + // If IPv4 failed, try IPv6 + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == 0) { + args.GetReturnValue().Set(0); + return; + } + + // If both failed, return the negative errno + args.GetReturnValue().Set(-errno); +#endif +} + + +void TCPWrap::GetTOS(const FunctionCallbackInfo& args) { + TCPWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP( + &wrap, args.This(), args.GetReturnValue().Set(UV_EBADF)); + + int fd; + int err = uv_fileno(reinterpret_cast(&wrap->handle_), &fd); + if (err != 0) { + args.GetReturnValue().Set(err); + return; + } + + int tos = 0; + socklen_t len = sizeof(tos); + +#ifdef _WIN32 + args.GetReturnValue().Set(UV_ENOSYS); +#else + // Try IPv4 first + if (getsockopt(fd, IPPROTO_IP, IP_TOS, &tos, &len) == 0) { + args.GetReturnValue().Set(tos); + return; + } + + // If IPv4 failed, try IPv6 + if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, &len) == 0) { + args.GetReturnValue().Set(tos); + return; + } + + // If both failed, return the negative errno + args.GetReturnValue().Set(-errno); +#endif +} + + #ifdef _WIN32 void TCPWrap::SetSimultaneousAccepts(const FunctionCallbackInfo& args) { TCPWrap* wrap; diff --git a/src/tcp_wrap.h b/src/tcp_wrap.h index a4684b65d24934..56e2a42d5fd3b7 100644 --- a/src/tcp_wrap.h +++ b/src/tcp_wrap.h @@ -74,6 +74,8 @@ class TCPWrap : public ConnectionWrap { static void New(const v8::FunctionCallbackInfo& args); static void SetNoDelay(const v8::FunctionCallbackInfo& args); static void SetKeepAlive(const v8::FunctionCallbackInfo& args); + static void SetTOS(const v8::FunctionCallbackInfo& args); + static void GetTOS(const v8::FunctionCallbackInfo& args); static void Bind(const v8::FunctionCallbackInfo& args); static void Bind6(const v8::FunctionCallbackInfo& args); static void Listen(const v8::FunctionCallbackInfo& args); diff --git a/test/parallel/test-net-socket-tos.js b/test/parallel/test-net-socket-tos.js new file mode 100644 index 00000000000000..33fed353a2e695 --- /dev/null +++ b/test/parallel/test-net-socket-tos.js @@ -0,0 +1,59 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +// 1. Check if the platform supports TOS +// Your implementation returns UV_ENOSYS on Windows, so we expect an error there. +const isWindows = common.isWindows; + +const server = net.createServer(common.mustCall((socket) => { + socket.end(); +})); + +server.listen(0, common.mustCall(() => { + const port = server.address().port; + const client = net.connect(port); + + client.on('connect', common.mustCall(() => { + // TEST 1: setTOS validation + // Should throw if value is not a number or out of range + assert.throws(() => client.setTOS('invalid'), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => client.setTOS(NaN), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => client.setTOS(256), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => client.setTOS(-1), { + code: 'ERR_OUT_OF_RANGE' + }); + + // TEST 2: setting and getting TOS + const tosValue = 0x10; // IPTOS_LOWDELAY (16) + + if (isWindows) { + // On Windows, your implementation returns UV_ENOSYS, which throws in JS + assert.throws(() => client.setTOS(tosValue), { + code: 'ENOSYS' + }); + } else { + // On POSIX (Linux/macOS), this should succeed + client.setTOS(tosValue); + + // Verify values + // Note: Some OSs might mask the value (e.g. Linux sometimes masks ECN bits), + // but usually 0x10 should return 0x10. + const got = client.getTOS(); + assert.strictEqual(got, tosValue, `Expected TOS ${tosValue}, got ${got}`); + } + + client.end(); + })); + + client.on('end', () => { + server.close(); + }); +})); From 9e7a18384ca4bc10491fd204d8a8820ee083c087 Mon Sep 17 00:00:00 2001 From: Amol Yadav Date: Sat, 24 Jan 2026 19:08:25 +0530 Subject: [PATCH 2/7] added doc --- doc/api/net.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/api/net.md b/doc/api/net.md index 0bc2adbd455b8b..c28129a70c80fe 100644 --- a/doc/api/net.md +++ b/doc/api/net.md @@ -1461,6 +1461,25 @@ If `timeout` is 0, then the existing idle timeout is disabled. The optional `callback` parameter will be added as a one-time listener for the [`'timeout'`][] event. +### `socket.getTOS()` + + + +* Returns: {integer} The current TOS value. + +Returns the current Type of Service (TOS) field for IPv4 packets or Traffic +Class for IPv6 packets for this socket. + +### `socket.setTOS(tos)` + +* `tos` {integer} The TOS value to set (0-255). +* Returns: {net.Socket} The socket itself. + +Sets the Type of Service (TOS) field for IPv4 packets or Traffic Class for IPv6 +Packets sent from this socket. This can be used to prioritize network traffic. + ### `socket.timeout` * Returns: {integer} The current TOS value. @@ -1474,6 +1474,10 @@ Class for IPv6 packets for this socket. ### `socket.setTOS(tos)` + + * `tos` {integer} The TOS value to set (0-255). * Returns: {net.Socket} The socket itself. From 71ecbd72e411d1ad285306e78da507f1815949ad Mon Sep 17 00:00:00 2001 From: Amol Yadav Date: Sat, 24 Jan 2026 19:55:13 +0530 Subject: [PATCH 6/7] common.mustCall used --- test/parallel/test-net-socket-tos.js | 115 +++++++++++++++------------ 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/test/parallel/test-net-socket-tos.js b/test/parallel/test-net-socket-tos.js index c736320e75a383..eeef250c6bfe9f 100644 --- a/test/parallel/test-net-socket-tos.js +++ b/test/parallel/test-net-socket-tos.js @@ -5,53 +5,68 @@ const net = require('net'); const isWindows = common.isWindows; -const server = net.createServer(common.mustCall((socket) => { - socket.end(); -})); - -server.listen(0, common.mustCall(() => { - const port = server.address().port; - const client = net.connect(port); - - client.on('connect', common.mustCall(() => { - // TEST 1: setTOS validation - // Should throw if value is not a number or out of range - assert.throws(() => client.setTOS('invalid'), { - code: 'ERR_INVALID_ARG_TYPE' - }); - assert.throws(() => client.setTOS(NaN), { - code: 'ERR_INVALID_ARG_TYPE' - }); - assert.throws(() => client.setTOS(256), { - code: 'ERR_OUT_OF_RANGE' - }); - assert.throws(() => client.setTOS(-1), { - code: 'ERR_OUT_OF_RANGE' - }); - - // TEST 2: setting and getting TOS - const tosValue = 0x10; // IPTOS_LOWDELAY (16) - - if (isWindows) { - // On Windows, your implementation returns UV_ENOSYS, which throws in JS - assert.throws(() => client.setTOS(tosValue), { - code: 'ENOSYS' - }); - } else { - // On POSIX (Linux/macOS), this should succeed - client.setTOS(tosValue); - - // Verify values - // Note: Some OSs might mask the value (e.g. Linux sometimes masks ECN bits), - // but usually 0x10 should return 0x10. - const got = client.getTOS(); - assert.strictEqual(got, tosValue, `Expected TOS ${tosValue}, got ${got}`); - } - - client.end(); - })); - - client.on('end', () => { - server.close(); - }); -})); +const server = net.createServer( + common.mustCall((socket) => { + socket.end(); + }), +); + +server.listen( + 0, + common.mustCall(() => { + const port = server.address().port; + const client = net.connect(port); + + client.on( + 'connect', + common.mustCall(() => { + // TEST 1: setTOS validation + // Should throw if value is not a number or out of range + assert.throws(() => client.setTOS('invalid'), { + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => client.setTOS(NaN), { + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => client.setTOS(256), { + code: 'ERR_OUT_OF_RANGE', + }); + assert.throws(() => client.setTOS(-1), { + code: 'ERR_OUT_OF_RANGE', + }); + + // TEST 2: setting and getting TOS + const tosValue = 0x10; // IPTOS_LOWDELAY (16) + + if (isWindows) { + // On Windows, your implementation returns UV_ENOSYS, which throws in JS + assert.throws(() => client.setTOS(tosValue), { + code: 'ENOSYS', + }); + } else { + // On POSIX (Linux/macOS), this should succeed + client.setTOS(tosValue); + + // Verify values + // Note: Some OSs might mask the value (e.g. Linux sometimes masks ECN bits), + // but usually 0x10 should return 0x10. + const got = client.getTOS(); + assert.strictEqual( + got, + tosValue, + `Expected TOS ${tosValue}, got ${got}`, + ); + } + + client.end(); + }), + ); + + client.on( + 'end', + common.mustCall(() => { + server.close(); + }), + ); + }), +); From c0407b28998d326f912341517621075ca305479a Mon Sep 17 00:00:00 2001 From: Amol Yadav Date: Sat, 24 Jan 2026 19:57:26 +0530 Subject: [PATCH 7/7] undo: unrelated header was removed irrelevantly --- src/tcp_wrap.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index 64110b2c784a4b..ba3324e25d70b4 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -32,7 +32,8 @@ #include "stream_base-inl.h" #include "stream_wrap.h" #include "util-inl.h" - +#include +#include #ifndef _WIN32 #include #include