Skip to content

Commit 57f36ad

Browse files
feat(net,dgram): add UDP support and fix Windows CI
- Implements dgram.setTOS/getTOS - Fixes implicit bind edge cases - Adds Windows ENOSYS guards - Fixes Dual-Stack priority logic
1 parent d958e8a commit 57f36ad

File tree

10 files changed

+457
-96
lines changed

10 files changed

+457
-96
lines changed

lib/dgram.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,16 @@ const {
7474
} = require('internal/async_hooks');
7575
const { FastBuffer } = require('internal/buffer');
7676
const { UV_UDP_REUSEADDR } = internalBinding('constants').os;
77+
const { UV_EBADF, UV_EINVAL } = internalBinding('uv');
7778

7879
const {
7980
constants: { UV_UDP_IPV6ONLY, UV_UDP_REUSEPORT },
8081
UDP,
8182
SendWrap,
8283
} = internalBinding('udp_wrap');
8384

85+
const kSetTOS = Symbol('kSetTOS');
86+
8487
const dc = require('diagnostics_channel');
8588
const udpSocketChannel = dc.channel('udp.socket');
8689

@@ -201,6 +204,15 @@ function startListening(socket) {
201204
state.receiving = true;
202205
state.bindState = BIND_STATE_BOUND;
203206

207+
// Apply pending TOS if any now that the socket is bound.
208+
if (socket[kSetTOS] !== undefined && state.handle && state.handle.setTOS) {
209+
const err = state.handle.setTOS(socket[kSetTOS]);
210+
if (err && err !== UV_EBADF) {
211+
// Non-fatal: surface error to the socket
212+
socket.emit('error', new ErrnoException(err, 'setTOS'));
213+
}
214+
}
215+
204216
if (state.recvBufferSize)
205217
bufferSize(socket, state.recvBufferSize, RECV_BUFFER);
206218

@@ -226,6 +238,15 @@ function replaceHandle(self, newHandle) {
226238
// Replace the existing handle by the handle we got from primary.
227239
oldHandle.close();
228240
state.handle = newHandle;
241+
242+
// Apply pending TOS if any to the new handle (ignore EBADF; defer)
243+
if (self[kSetTOS] !== undefined && state.handle && state.handle.setTOS) {
244+
const err = state.handle.setTOS(self[kSetTOS]);
245+
if (err && err !== UV_EBADF) {
246+
// Non-fatal: surface error to the socket
247+
self.emit('error', new ErrnoException(err, 'setTOS'));
248+
}
249+
}
229250
}
230251

231252
function bufferSize(self, size, buffer) {
@@ -267,6 +288,9 @@ function bindServerHandle(self, options, errCb) {
267288
Socket.prototype.bind = function(port_, address_ /* , callback */) {
268289
let port = port_;
269290

291+
// Pending TOS will be applied after the socket is successfully bound
292+
// (in startListening). Attempting to apply before bind can result in EBADF.
293+
270294
healthCheck(this);
271295
const state = this[kStateSymbol];
272296

@@ -860,6 +884,46 @@ Socket.prototype.setTTL = function(ttl) {
860884
};
861885

862886

887+
Socket.prototype.setTOS = function(tos) {
888+
validateNumber(tos, 'tos');
889+
890+
// TOS must be an integer in the range 0..255.
891+
if (!Number.isInteger(tos) || tos < 0 || tos > 255) {
892+
throw new ErrnoException(UV_EINVAL, 'setTOS');
893+
}
894+
895+
// Remember the pending value so it can be applied on bind/replaceHandle.
896+
this[kSetTOS] = tos;
897+
898+
const state = this[kStateSymbol];
899+
// Only attempt to apply now if the socket is already bound.
900+
if (state && state.handle && state.bindState === BIND_STATE_BOUND && state.handle.setTOS) {
901+
const err = state.handle.setTOS(tos);
902+
// Ignore EBADF: on some platforms the underlying fd may not be
903+
// fully initialized yet even though a handle exists. In that case
904+
// defer applying the setting to when the socket is actually bound
905+
// (handled by bind/replaceHandle).
906+
if (err && err !== UV_EBADF) throw new ErrnoException(err, 'setTOS');
907+
}
908+
909+
return tos;
910+
};
911+
912+
Socket.prototype.getTOS = function() {
913+
const state = this[kStateSymbol];
914+
// If there is a pending value and the socket is not yet bound, return
915+
// the pending value instead of calling into the native handle.
916+
if (this[kSetTOS] !== undefined && (!state.handle || state.bindState !== BIND_STATE_BOUND)) {
917+
return this[kSetTOS];
918+
}
919+
920+
if (!state.handle || !state.handle.getTOS) return undefined;
921+
const result = state.handle.getTOS();
922+
if (result < 0) throw new ErrnoException(result, 'getTOS');
923+
return result;
924+
};
925+
926+
863927
Socket.prototype.setMulticastTTL = function(ttl) {
864928
validateNumber(ttl, 'ttl');
865929

lib/net.js

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ const {
5454
normalizedArgsSymbol,
5555
makeSyncWrite,
5656
} = require('internal/net');
57+
5758
const assert = require('internal/assert');
5859
const {
5960
UV_EADDRINUSE,
6061
UV_EINVAL,
6162
UV_ENOTCONN,
6263
UV_ECANCELED,
6364
UV_ETIMEDOUT,
65+
UV_EBADF,
6466
} = internalBinding('uv');
6567
const { convertIpv6StringToBuffer } = internalBinding('cares_wrap');
6668

@@ -474,7 +476,15 @@ function Socket(options) {
474476
this[kSetNoDelay] = Boolean(options.noDelay);
475477
this[kSetKeepAlive] = Boolean(options.keepAlive);
476478
this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000);
477-
this[kSetTOS] = options.TOS;
479+
// Optional initial TOS hint (0 - 255)
480+
if (options.tos !== undefined) {
481+
validateNumber(options.tos, 'options.tos');
482+
if (!Number.isInteger(options.tos) || options.tos < 0 || options.tos > 255)
483+
throw new ERR_INVALID_ARG_VALUE('options.tos', options.tos);
484+
this[kSetTOS] = options.tos;
485+
} else {
486+
this[kSetTOS] = undefined;
487+
}
478488

479489
// Shut down the socket when we're finished with it.
480490
this.on('end', onReadableStreamEnd);
@@ -487,6 +497,10 @@ function Socket(options) {
487497
// If we have a handle, then start the flow of data into the
488498
// buffer. if not, then this will happen when we connect
489499
if (this._handle && options.readable !== false) {
500+
// Apply pending TOS if present and supported by the handle
501+
if (this[kSetTOS] !== undefined && this._handle.setTOS)
502+
this._handle.setTOS(this[kSetTOS]);
503+
490504
if (options.pauseOnCreate) {
491505
// Stop the handle from reading and pause the stream
492506
this._handle.reading = false;
@@ -655,43 +669,38 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
655669

656670

657671
Socket.prototype.setTOS = function(tos) {
658-
if (NumberIsNaN(tos)) {
659-
throw new ERR_INVALID_ARG_TYPE('tos', 'number', tos);
672+
validateNumber(tos, 'tos');
673+
674+
// TOS must be an integer in the range 0..255.
675+
if (!Number.isInteger(tos) || tos < 0 || tos > 255) {
676+
throw new ErrnoException(UV_EINVAL, 'setTOS');
660677
}
661-
validateInt32(tos, 'tos', 0, 255);
662678

663679
if (!this._handle) {
664680
this[kSetTOS] = tos;
665681
return this;
666682
}
667683

668-
if (this._handle.setTOS && tos !== this[kSetTOS]) {
684+
if (!this._handle.setTOS) {
685+
// Handle doesn't support TOS; no-op and remember value
669686
this[kSetTOS] = tos;
670-
const err = this._handle.setTOS(tos);
671-
if (err) {
672-
throw new ErrnoException(err, 'setTOS');
673-
}
687+
return this;
674688
}
675689

690+
const err = this._handle.setTOS(tos);
691+
// Ignore EBADF: transient fd-not-ready states are possible; defer to
692+
// after connect when the fd is guaranteed to be ready.
693+
if (err && err !== UV_EBADF) throw new ErrnoException(err, 'setTOS');
694+
this[kSetTOS] = tos;
676695
return this;
677696
};
678697

679-
680698
Socket.prototype.getTOS = function() {
681-
if (!this._handle) {
682-
// Return cached value if set, otherwise default to 0
683-
return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
684-
}
685-
686-
if (!this._handle.getTOS) {
687-
return this[kSetTOS];
688-
}
689-
690-
const res = this._handle.getTOS();
691-
if (typeof res === 'number' && res < 0) {
692-
throw new ErrnoException(res, 'getTOS');
693-
}
694-
return res;
699+
if (!this._handle) throw new ERR_SOCKET_CLOSED();
700+
if (!this._handle.getTOS) return undefined;
701+
const r = this._handle.getTOS();
702+
if (r < 0) throw new ErrnoException(r, 'getTOS');
703+
return r;
695704
};
696705

697706

@@ -1663,7 +1672,12 @@ function afterConnect(status, handle, req, readable, writable) {
16631672
}
16641673

16651674
if (self[kSetTOS] !== undefined && self._handle.setTOS) {
1666-
self._handle.setTOS(self[kSetTOS]);
1675+
const err = self._handle.setTOS(self[kSetTOS]);
1676+
if (err && err !== UV_EBADF) {
1677+
// Non-fatal: surface as an 'error' event to avoid throwing during
1678+
// connect callbacks
1679+
self.emit('error', new ErrnoException(err, 'setTOS'));
1680+
}
16671681
}
16681682

16691683
self.emit('connect');

0 commit comments

Comments
 (0)