Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,29 @@ 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()`

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

### `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.

### `socket.timeout`

<!-- YAML
Expand Down
46 changes: 46 additions & 0 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
};
Expand Down Expand Up @@ -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');

Expand Down
112 changes: 109 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>

#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,103 @@ 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;
if (!args[0]->Int32Value(env->context()).To(&tos)) return;

// Use uv_os_fd_t (it handles int vs void* automatically)
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;
}

#ifdef _WIN32
// Windows implementation
// Windows setsockopt expects 'const char*' for value and 'SOCKET' for fd
if (setsockopt(reinterpret_cast<SOCKET>(fd),
IPPROTO_IP,
IP_TOS,
reinterpret_cast<const char*>(&tos),
sizeof(tos)) == 0) {
args.GetReturnValue().Set(0);
return;
}
// Windows does not typically support IPV6_TCLASS in the same way,
// or it might fallback. If you need IPv6 support on Windows,
// you often need explicit version checks or specific headers.
// For now, let's return the error if IPv4 failed.
args.GetReturnValue().Set(UV_EINVAL);
#else
// Linux/macOS implementation
// 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<Value>& args) {
TCPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));

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

int tos = 0;
socklen_t len = sizeof(tos);

#ifdef _WIN32
// Windows implementation
// Windows getsockopt expects 'char*' and 'SOCKET'
if (getsockopt(reinterpret_cast<SOCKET>(fd),
IPPROTO_IP,
IP_TOS,
reinterpret_cast<char*>(&tos),
&len) == 0) {
args.GetReturnValue().Set(tos);
return;
}
args.GetReturnValue().Set(UV_EINVAL);
#else
// Linux/macOS implementation
// 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<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
72 changes: 72 additions & 0 deletions test/parallel/test-net-socket-tos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';
const common = require('../common');
const assert = require('assert');
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',
common.mustCall(() => {
server.close();
}),
);
}),
);