Skip to content

Commit eb53d09

Browse files
committed
net: add setTOS and getTOS to Socket
1 parent 5d39030 commit eb53d09

File tree

4 files changed

+187
-1
lines changed

4 files changed

+187
-1
lines changed

lib/net.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ const kBytesWritten = Symbol('kBytesWritten');
358358
const kSetNoDelay = Symbol('kSetNoDelay');
359359
const kSetKeepAlive = Symbol('kSetKeepAlive');
360360
const kSetKeepAliveInitialDelay = Symbol('kSetKeepAliveInitialDelay');
361+
const kSetTOS = Symbol('kSetTOS');
361362

362363
function Socket(options) {
363364
if (!(this instanceof Socket)) return new Socket(options);
@@ -473,6 +474,7 @@ function Socket(options) {
473474
this[kSetNoDelay] = Boolean(options.noDelay);
474475
this[kSetKeepAlive] = Boolean(options.keepAlive);
475476
this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000);
477+
this[kSetTOS] = options.TOS;
476478

477479
// Shut down the socket when we're finished with it.
478480
this.on('end', onReadableStreamEnd);
@@ -652,6 +654,46 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
652654
};
653655

654656

657+
Socket.prototype.setTOS = function(tos) {
658+
if (NumberIsNaN(tos)) {
659+
throw new ERR_INVALID_ARG_TYPE('tos', 'number', tos);
660+
}
661+
validateInt32(tos, 'tos', 0, 255);
662+
663+
if (!this._handle) {
664+
this[kSetTOS] = tos;
665+
return this;
666+
}
667+
668+
if (this._handle.setTOS && tos !== this[kSetTOS]) {
669+
this[kSetTOS] = tos;
670+
const err = this._handle.setTOS(tos);
671+
if (err) {
672+
throw new ErrnoException(err, 'setTOS');
673+
}
674+
}
675+
676+
return this;
677+
};
678+
679+
680+
Socket.prototype.getTOS = function() {
681+
if (!this._handle) {
682+
return this[kSetTOS];
683+
}
684+
685+
if (!this._handle.getTOS) {
686+
return this[kSetTOS];
687+
}
688+
689+
const res = this._handle.getTOS();
690+
if (typeof res === 'number' && res < 0) {
691+
throw new ErrnoException(res, 'getTOS');
692+
}
693+
return res;
694+
};
695+
696+
655697
Socket.prototype.address = function() {
656698
return this._getsockname();
657699
};
@@ -1619,6 +1661,10 @@ function afterConnect(status, handle, req, readable, writable) {
16191661
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
16201662
}
16211663

1664+
if (self[kSetTOS] !== undefined && self._handle.setTOS) {
1665+
self._handle.setTOS(self[kSetTOS]);
1666+
}
1667+
16221668
self.emit('connect');
16231669
self.emit('ready');
16241670

src/tcp_wrap.cc

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
#include "stream_wrap.h"
3434
#include "util-inl.h"
3535

36-
#include <cstdlib>
36+
#ifndef _WIN32
37+
#include <sys/socket.h>
38+
#include <netinet/in.h>
39+
#endif
3740

3841

3942
namespace node {
@@ -106,6 +109,8 @@ void TCPWrap::Initialize(Local<Object> target,
106109
GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
107110
SetProtoMethod(isolate, t, "setNoDelay", SetNoDelay);
108111
SetProtoMethod(isolate, t, "setKeepAlive", SetKeepAlive);
112+
SetProtoMethod(isolate, t, "setTOS", SetTOS);
113+
SetProtoMethod(isolate, t, "getTOS", GetTOS);
109114
SetProtoMethod(isolate, t, "reset", Reset);
110115

111116
#ifdef _WIN32
@@ -145,6 +150,8 @@ void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
145150
registry->Register(GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
146151
registry->Register(SetNoDelay);
147152
registry->Register(SetKeepAlive);
153+
registry->Register(SetTOS);
154+
registry->Register(GetTOS);
148155
registry->Register(Reset);
149156
#ifdef _WIN32
150157
registry->Register(SetSimultaneousAccepts);
@@ -209,6 +216,78 @@ void TCPWrap::SetKeepAlive(const FunctionCallbackInfo<Value>& args) {
209216
}
210217

211218

219+
void TCPWrap::SetTOS(const FunctionCallbackInfo<Value>& args) {
220+
TCPWrap* wrap;
221+
ASSIGN_OR_RETURN_UNWRAP(
222+
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
223+
Environment* env = wrap->env();
224+
int tos;
225+
if (!args[0]->Int32Value(env->context()).To(&tos)) return;
226+
227+
int fd;
228+
int err = uv_fileno(reinterpret_cast<uv_handle_t*>(&wrap->handle_), &fd);
229+
if (err != 0) {
230+
args.GetReturnValue().Set(err);
231+
return;
232+
}
233+
234+
#ifdef _WIN32
235+
args.GetReturnValue().Set(UV_ENOSYS);
236+
#else
237+
// Try IPv4 first
238+
if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == 0) {
239+
args.GetReturnValue().Set(0);
240+
return;
241+
}
242+
243+
// If IPv4 failed, try IPv6
244+
if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == 0) {
245+
args.GetReturnValue().Set(0);
246+
return;
247+
}
248+
249+
// If both failed, return the negative errno
250+
args.GetReturnValue().Set(-errno);
251+
#endif
252+
}
253+
254+
255+
void TCPWrap::GetTOS(const FunctionCallbackInfo<Value>& args) {
256+
TCPWrap* wrap;
257+
ASSIGN_OR_RETURN_UNWRAP(
258+
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
259+
260+
int fd;
261+
int err = uv_fileno(reinterpret_cast<uv_handle_t*>(&wrap->handle_), &fd);
262+
if (err != 0) {
263+
args.GetReturnValue().Set(err);
264+
return;
265+
}
266+
267+
int tos = 0;
268+
socklen_t len = sizeof(tos);
269+
270+
#ifdef _WIN32
271+
args.GetReturnValue().Set(UV_ENOSYS);
272+
#else
273+
// Try IPv4 first
274+
if (getsockopt(fd, IPPROTO_IP, IP_TOS, &tos, &len) == 0) {
275+
args.GetReturnValue().Set(tos);
276+
return;
277+
}
278+
279+
// If IPv4 failed, try IPv6
280+
if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, &len) == 0) {
281+
args.GetReturnValue().Set(tos);
282+
return;
283+
}
284+
285+
// If both failed, return the negative errno
286+
args.GetReturnValue().Set(-errno);
287+
#endif
288+
}
289+
290+
212291
#ifdef _WIN32
213292
void TCPWrap::SetSimultaneousAccepts(const FunctionCallbackInfo<Value>& args) {
214293
TCPWrap* wrap;

src/tcp_wrap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t> {
7474
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
7575
static void SetNoDelay(const v8::FunctionCallbackInfo<v8::Value>& args);
7676
static void SetKeepAlive(const v8::FunctionCallbackInfo<v8::Value>& args);
77+
static void SetTOS(const v8::FunctionCallbackInfo<v8::Value>& args);
78+
static void GetTOS(const v8::FunctionCallbackInfo<v8::Value>& args);
7779
static void Bind(const v8::FunctionCallbackInfo<v8::Value>& args);
7880
static void Bind6(const v8::FunctionCallbackInfo<v8::Value>& args);
7981
static void Listen(const v8::FunctionCallbackInfo<v8::Value>& args);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const net = require('net');
5+
6+
// 1. Check if the platform supports TOS
7+
// Your implementation returns UV_ENOSYS on Windows, so we expect an error there.
8+
const isWindows = common.isWindows;
9+
10+
const server = net.createServer(common.mustCall((socket) => {
11+
socket.end();
12+
}));
13+
14+
server.listen(0, common.mustCall(() => {
15+
const port = server.address().port;
16+
const client = net.connect(port);
17+
18+
client.on('connect', common.mustCall(() => {
19+
// TEST 1: setTOS validation
20+
// Should throw if value is not a number or out of range
21+
assert.throws(() => client.setTOS('invalid'), {
22+
code: 'ERR_INVALID_ARG_TYPE'
23+
});
24+
assert.throws(() => client.setTOS(NaN), {
25+
code: 'ERR_INVALID_ARG_TYPE'
26+
});
27+
assert.throws(() => client.setTOS(256), {
28+
code: 'ERR_OUT_OF_RANGE'
29+
});
30+
assert.throws(() => client.setTOS(-1), {
31+
code: 'ERR_OUT_OF_RANGE'
32+
});
33+
34+
// TEST 2: setting and getting TOS
35+
const tosValue = 0x10; // IPTOS_LOWDELAY (16)
36+
37+
if (isWindows) {
38+
// On Windows, your implementation returns UV_ENOSYS, which throws in JS
39+
assert.throws(() => client.setTOS(tosValue), {
40+
code: 'ENOSYS'
41+
});
42+
} else {
43+
// On POSIX (Linux/macOS), this should succeed
44+
client.setTOS(tosValue);
45+
46+
// Verify values
47+
// Note: Some OSs might mask the value (e.g. Linux sometimes masks ECN bits),
48+
// but usually 0x10 should return 0x10.
49+
const got = client.getTOS();
50+
assert.strictEqual(got, tosValue, `Expected TOS ${tosValue}, got ${got}`);
51+
}
52+
53+
client.end();
54+
}));
55+
56+
client.on('end', () => {
57+
server.close();
58+
});
59+
}));

0 commit comments

Comments
 (0)