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
80 changes: 78 additions & 2 deletions src/tcp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
#include "stream_base-inl.h"
#include "stream_wrap.h"
#include "util-inl.h"

#include <cerrno>
#include <cstdlib>

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

namespace node {

Expand Down Expand Up @@ -106,6 +109,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 +150,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 +215,75 @@ 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;

int fd;
int err = uv_fileno(reinterpret_cast<uv_handle_t*>(&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<Value>& args) {
TCPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));

int 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
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<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();
}),
);
}),
);