Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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()`
Copy link
Member

Choose a reason for hiding this comment

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

Our API names in Node.js are generally more verbose than the corresponding OS APIs – would there be anything speaking against calling these socket.getTypeOfService() and socket.setTypeOfService()?

Copy link
Author

Choose a reason for hiding this comment

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

that makes sense and i agree to that, i will update to use full names


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

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

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

`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;

// 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;
}

if (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 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;
}

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
146 changes: 144 additions & 2 deletions src/tcp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "tcp_wrap.h"

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include "connect_wrap.h"
#include "connection_wrap.h"
#include "env-inl.h"
Expand All @@ -32,9 +35,15 @@
#include "stream_base-inl.h"
#include "stream_wrap.h"
#include "util-inl.h"

#include <cerrno>
#include <cstdlib>

#ifndef _WIN32
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <sys/socket.h>
#endif

namespace node {

Expand Down Expand Up @@ -106,6 +115,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 +156,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 +221,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
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
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.

}

#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);
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