Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eb53d09
net: add setTOS and getTOS to Socket
amyssnippet Jan 24, 2026
9e7a183
added doc
amyssnippet Jan 24, 2026
14b3fb0
fixed some lint issues
amyssnippet Jan 24, 2026
f339cd0
already complete with validation and platform specific tests
amyssnippet Jan 24, 2026
d32f212
added: REPLACEME
amyssnippet Jan 24, 2026
71ecbd7
common.mustCall used
amyssnippet Jan 24, 2026
c0407b2
undo: unrelated header was removed irrelevantly
amyssnippet Jan 24, 2026
8439cfd
fix: resolve windows compilation and linter issues
amyssnippet Jan 25, 2026
fe329db
rollback to tcp only
amyssnippet Jan 25, 2026
778580a
fix: ensure getTOS returns numeric default when handle lacks getTOS
amyssnippet Jan 26, 2026
efb5f88
fix: handle setTOS errors in afterConnect and use options.tos
amyssnippet Jan 26, 2026
8dce839
fix: capture errno immediately after getsockopt failures
amyssnippet Jan 26, 2026
1fb4683
fix: use correct length type for Windows getsockopt and fix DSCP mask…
amyssnippet Jan 26, 2026
1084571
fix: validate options.tos in constructor
amyssnippet Jan 26, 2026
0a90f1c
fix: use validateInt32 for options.tos and standardize GetTOS error
amyssnippet Jan 26, 2026
603445d
fix: remove unused errno_val in TCPWrap::GetTOS POSIX path
amyssnippet Jan 26, 2026
f22a74c
test: add boundary value assertions for TOS
amyssnippet Jan 26, 2026
b272fda
test: add pre-connect TOS caching test
amyssnippet Jan 26, 2026
c6f1153
doc: add notes for setTOS and getTOS about pre-connect caching and OS…
amyssnippet Jan 26, 2026
fe14b34
fix: import UV_EBADF and remove duplicate mask declaration
amyssnippet Jan 26, 2026
041c30a
fix: update TOS cache when handle lacks setTOS
amyssnippet Jan 26, 2026
7516aff
doc: clarify pre-connect TOS test comment
amyssnippet Jan 26, 2026
83d875c
refined: comments
amyssnippet Jan 26, 2026
c03162b
doc: remove prohibited 'Note that' phrases
amyssnippet Jan 26, 2026
b884430
fix: reorder includes for Windows compatibility and add missing POSIX…
amyssnippet Jan 26, 2026
c39d13e
fix: resolve windows header order and doc lint errors
amyssnippet Jan 26, 2026
98747a7
feat: rename setTOS/getTOS to setTypeOfService/getTypeOfService for A…
amyssnippet Jan 26, 2026
551a5d3
fix: update internal handle calls to use new TypeOfService method names
amyssnippet Jan 26, 2026
2bb4591
win vs2022 ci fix
amyssnippet Jan 27, 2026
a2d4c36
added: todo for libuv api
amyssnippet Jan 27, 2026
b04b62f
fixed:win vs2022 lint ci
amyssnippet Jan 27, 2026
569debb
feat: rename C++ methods from SetTOS/GetTOS to SetTypeOfService/GetTy…
amyssnippet Jan 27, 2026
e33561d
docs: update net.md for typeOfService option and fix lib/net.js const…
amyssnippet Jan 27, 2026
d53c015
Apply suggestions from code review
ronag Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,45 @@ 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()`
Comment thread
addaleax marked this conversation as resolved.
Outdated

<!-- YAML
added: REPLACEME
-->

* 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.

Note that `setTOS()` may be called before the socket is connected; the value
will be cached and applied when the socket establishes a connection. `getTOS()`
will return the currently set value even before connection.

On some platforms (e.g., Linux), certain TOS/ECN bits may be masked or ignored,
and behavior can differ between IPv4 and IPv6 or dual-stack sockets. Callers
should verify platform-specific semantics.

### `socket.setTOS(tos)`
Comment thread
amyssnippet marked this conversation as resolved.
Outdated

<!-- YAML
added: REPLACEME
-->

* `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.
Comment thread
amyssnippet marked this conversation as resolved.

Note that `setTOS()` may be called before the socket is connected; the value
will be cached and applied when the socket establishes a connection. `getTOS()`
will return the currently set value even before connection.

On some platforms (e.g., Linux), certain TOS/ECN bits may be masked or ignored,
and behavior can differ between IPv4 and IPv6 or dual-stack sockets. Callers
should verify platform-specific semantics.

### `socket.timeout`

<!-- YAML
Expand Down
59 changes: 59 additions & 0 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const {
const assert = require('internal/assert');
const {
UV_EADDRINUSE,
UV_EBADF,
UV_EINVAL,
UV_ENOTCONN,
UV_ECANCELED,
Expand Down Expand Up @@ -358,6 +359,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);
Expand Down Expand Up @@ -473,6 +475,10 @@ function Socket(options) {
this[kSetNoDelay] = Boolean(options.noDelay);
this[kSetKeepAlive] = Boolean(options.keepAlive);
this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000);
if (options.tos !== undefined) {
validateInt32(options.tos, 'options.tos', 0, 255);
}
this[kSetTOS] = options.tos;
Comment thread
amyssnippet marked this conversation as resolved.
Outdated

// Shut down the socket when we're finished with it.
this.on('end', onReadableStreamEnd);
Expand Down Expand Up @@ -652,6 +658,52 @@ 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) {
this[kSetTOS] = tos;
return this;
}
Comment thread
amyssnippet marked this conversation as resolved.

if (this._handle.setTOS && tos !== this[kSetTOS]) {
Comment thread
amyssnippet marked this conversation as resolved.
Outdated
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 cached value if set, otherwise default to 0
return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
}

if (!this._handle.getTOS) {
return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
}
Comment thread
amyssnippet marked this conversation as resolved.

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();
};
Expand Down Expand Up @@ -1619,6 +1671,13 @@ function afterConnect(status, handle, req, readable, writable) {
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
}

if (self[kSetTOS] !== undefined && self._handle.setTOS) {
const err = self._handle.setTOS(self[kSetTOS]);
if (err && err !== UV_EBADF) {
self.emit('error', new ErrnoException(err, 'setTOS'));
}
}

self.emit('connect');
self.emit('ready');

Expand Down
144 changes: 141 additions & 3 deletions src/tcp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "tcp_wrap.h"

#include "connect_wrap.h"
#include "connection_wrap.h"
#include "env-inl.h"
Expand All @@ -32,9 +31,15 @@
#include "stream_base-inl.h"
#include "stream_wrap.h"
#include "util-inl.h"

#include <cerrno>
#include <cstdlib>
Comment thread
amyssnippet marked this conversation as resolved.

#ifdef _WIN32
#include <ws2tcpip.h>
#endif
#ifndef _WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#endif

namespace node {

Expand Down Expand Up @@ -106,6 +111,8 @@ void TCPWrap::Initialize(Local<Object> target,
GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
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
Expand Down Expand Up @@ -145,6 +152,8 @@ void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
registry->Register(SetNoDelay);
registry->Register(SetKeepAlive);
registry->Register(SetTOS);
registry->Register(GetTOS);
registry->Register(Reset);
#ifdef _WIN32
registry->Register(SetSimultaneousAccepts);
Expand Down Expand Up @@ -208,6 +217,135 @@ void TCPWrap::SetKeepAlive(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(err);
}

void TCPWrap::SetTOS(const FunctionCallbackInfo<Value>& args) {
TCPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
Environment* env = wrap->env();

int tos = 0;
if (!args[0]->Int32Value(env->context()).To(&tos)) return;

uv_os_fd_t fd;
int err = uv_fileno(reinterpret_cast<uv_handle_t*>(&wrap->handle_), &fd);
if (err != 0) {
args.GetReturnValue().Set(err);
return;
}

// 1. Detect the socket family (IPv4 vs IPv6)
sockaddr_storage storage;
int addrlen = sizeof(storage);
int sock_err = uv_tcp_getsockname(&wrap->handle_,
reinterpret_cast<sockaddr*>(&storage),
&addrlen);

// If we can't determine the family (e.g. closed socket), fail gracefully.
if (sock_err != 0) {
args.GetReturnValue().Set(sock_err);
return;
}

// 2. Select the correct protocol level and option name
int level;
int option;

if (storage.ss_family == AF_INET) {
level = IPPROTO_IP;
option = IP_TOS;
} else if (storage.ss_family == AF_INET6) {
level = IPPROTO_IPV6;
option = IPV6_TCLASS;
} else {
// Unsupported socket family (e.g. AF_UNIX)
args.GetReturnValue().Set(UV_EINVAL);
return;
}

// 3. Perform the system call (Platform specific casting)
#ifdef _WIN32
if (setsockopt(reinterpret_cast<SOCKET>(fd),
level,
option,
reinterpret_cast<const char*>(&tos),
static_cast<int>(sizeof(tos))) == 0) {
args.GetReturnValue().Set(0);
} else {
args.GetReturnValue().Set(uv_translate_sys_error(WSAGetLastError()));
}
#else
if (setsockopt(fd, level, option, &tos, sizeof(tos)) == 0) {
args.GetReturnValue().Set(0);
} else {
args.GetReturnValue().Set(uv_translate_sys_error(errno));
}
#endif
}

void TCPWrap::GetTOS(const FunctionCallbackInfo<Value>& args) {
TCPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));

uv_os_fd_t fd;
int err = uv_fileno(reinterpret_cast<uv_handle_t*>(&wrap->handle_), &fd);
if (err != 0) {
args.GetReturnValue().Set(err);
return;
}

// Detect socket family explicitly
sockaddr_storage storage;
int addrlen = sizeof(storage);
int sock_err = uv_tcp_getsockname(&wrap->handle_,
reinterpret_cast<sockaddr*>(&storage),
&addrlen);

int level;
int option;

// Select the correct constant based on family
if (sock_err == 0) {
if (storage.ss_family == AF_INET) {
level = IPPROTO_IP;
option = IP_TOS;
} else if (storage.ss_family == AF_INET6) {
level = IPPROTO_IPV6;
option = IPV6_TCLASS;
} else {
// Unknown or unsupported family
args.GetReturnValue().Set(UV_EINVAL);
return;
}
} else {
// If we can't determine the family, we can't safely get the TOS
args.GetReturnValue().Set(sock_err);
return;
}

int tos = 0;

// Perform the system call with platform-specific casting
#ifdef _WIN32
int len = sizeof(tos);
if (getsockopt(reinterpret_cast<SOCKET>(fd),
level,
option,
reinterpret_cast<char*>(&tos),
&len) == 0) {
args.GetReturnValue().Set(tos);
} else {
args.GetReturnValue().Set(uv_translate_sys_error(WSAGetLastError()));
}
#else
socklen_t len = sizeof(tos);
if (getsockopt(fd, level, option, &tos, &len) == 0) {
args.GetReturnValue().Set(tos);
} else {
args.GetReturnValue().Set(uv_translate_sys_error(errno));
}
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say this needs to block this PR, but upstreaming these additions to https://github.com/libuv/libuv might be a good follow-up change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even i was thinking the same, if internal members of nodejs will work and do the changes over there to libuv, then additions will be fast, rather than i do.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can open an issue there pointing to this PR, that will help and maybe someone picks it up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@santigimeno i already made an issue there, and a member of libuv is working on that, untill that, we could make a todo here in the cpp code to track the isssue and its relevant pr, and will update the libuv api, once it is rolled out. i looked a long on recent issues, prs whch got merged and i saw that it took a lot of time, but as we need to roll out this feature soon in the next minor version, we could consider forward looking at this pr so that we could easily bring the feature

}

#ifdef _WIN32
void TCPWrap::SetSimultaneousAccepts(const FunctionCallbackInfo<Value>& args) {
Expand Down
2 changes: 2 additions & 0 deletions src/tcp_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t> {
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetNoDelay(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetKeepAlive(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetTOS(const v8::FunctionCallbackInfo<v8::Value>& args);
Comment thread
amyssnippet marked this conversation as resolved.
Outdated
static void GetTOS(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Bind(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Bind6(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Listen(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand Down
Loading