Repository: nodejs/node
Pull request: net: add setTypeOfService and getTypeOfService to Socket

this PR implements `socket.setTypeOfService(tos)` and `socket.getTypeOfService()` in `net.Socket`. it needed this to support DSCP tagging (QoS) for traffic prioritization, which wasn't previously exposed in the JS API. for the implementation:
- I added the logic in `tcp_wrap.cc` to attempt `IP_TOS` first, and fallback to `IPV6_TCLASS` if that fails. This handles both IPv4 and IPv6 sockets automatically without needing a separate flag.
- Windows returns `UV_ENOSYS` for now since the headers/implementation differ there.
- Added a new test file (`test-net-socket-tos.js`) to verify input validation and ensure the values are actually set on supported platforms.

Fixes: #61489

Changed files:
filename: doc/api/net.md
patch: @@ -747,6 +747,9 @@ it to interact with the client.
 <!-- YAML
 added: v0.3.4
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/61503
+    description: Added `typeOfService` option.
   - version: v15.14.0
     pr-url: https://github.com/nodejs/node/pull/37735
     description: AbortSignal support was added.
@@ -788,6 +791,7 @@ changes:
     otherwise ignored. **Default:** `false`.
   * `signal` {AbortSignal} An Abort signal that may be used to destroy the
     socket.
+  * `typeOfService` {number} The initial Type of Service (TOS) value.
   * `writable` {boolean} Allow writes on the socket when an `fd` is passed,
     otherwise ignored. **Default:** `false`.
 * Returns: {net.Socket}
@@ -1461,6 +1465,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.getTypeOfService()`
+
+<!-- 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.
+
+`setTypeOfService()` may be called before the socket is connected; the value
+will be cached and applied when the socket establishes a connection.
+`getTypeOfService()` 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.setTypeOfService(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.
+
+`setTypeOfService()` may be called before the socket is connected; the value
+will be cached and applied when the socket establishes a connection.
+`getTypeOfService()` 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

filename: lib/net.js
patch: @@ -57,6 +57,7 @@ const {
 const assert = require('internal/assert');
 const {
   UV_EADDRINUSE,
+  UV_EBADF,
   UV_EINVAL,
   UV_ENOTCONN,
   UV_ECANCELED,
@@ -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);
@@ -473,6 +475,10 @@ function Socket(options) {
   this[kSetNoDelay] = Boolean(options.noDelay);
   this[kSetKeepAlive] = Boolean(options.keepAlive);
   this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000);
+  if (options.typeOfService !== undefined) {
+    validateInt32(options.typeOfService, 'options.typeOfService', 0, 255);
+  }
+  this[kSetTOS] = options.typeOfService;
 
   // Shut down the socket when we're finished with it.
   this.on('end', onReadableStreamEnd);
@@ -652,6 +658,59 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
 };
 
 
+Socket.prototype.setTypeOfService = 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.setTypeOfService) {
+    this[kSetTOS] = tos;
+    return this;
+  }
+
+  if (tos !== this[kSetTOS]) {
+    this[kSetTOS] = tos;
+    const err = this._handle.setTypeOfService(tos);
+    // On Windows, setting TOS is often restricted or returns error codes even if partially applied.
+    // We treat this as a "best effort" operation and do not throw on Windows.
+    if (err && !isWindows) {
+      throw new ErrnoException(err, 'setTypeOfService');
+    }
+  }
+
+  return this;
+};
+
+
+Socket.prototype.getTypeOfService = function() {
+  if (!this._handle) {
+    // Return cached value if set, otherwise default to 0
+    return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
+  }
+
+  if (!this._handle.getTypeOfService) {
+    return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
+  }
+
+  const res = this._handle.getTypeOfService();
+  if (typeof res === 'number' && res < 0) {
+    // On Windows, getsockopt(IP_TOS) often fails. In that case, fall back
+    // to the cached value we attempted to set, or 0.
+    if (isWindows) {
+      return this[kSetTOS] !== undefined ? this[kSetTOS] : 0;
+    }
+    throw new ErrnoException(res, 'getTypeOfService');
+  }
+  return res;
+};
+
+
 Socket.prototype.address = function() {
   return this._getsockname();
 };
@@ -1619,6 +1678,15 @@ function afterConnect(status, handle, req, readable, writable) {
       self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
     }
 
+    if (self[kSetTOS] !== undefined && self._handle.setTypeOfService) {
+      const err = self._handle.setTypeOfService(self[kSetTOS]);
+      // On Windows, setting TOS is best-effort. If it fails, we shouldn't destroy
+      // the connection or emit an error, as the socket is otherwise healthy.
+      if (err && err !== UV_EBADF && !isWindows) {
+        self.emit('error', new ErrnoException(err, 'setTypeOfService'));
+      }
+    }
+
     self.emit('connect');
     self.emit('ready');
 

filename: src/tcp_wrap.cc
patch: @@ -20,7 +20,12 @@
 // USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 #include "tcp_wrap.h"
-
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <cerrno>
+#include <cstdlib>
 #include "connect_wrap.h"
 #include "connection_wrap.h"
 #include "env-inl.h"
@@ -33,8 +38,12 @@
 #include "stream_wrap.h"
 #include "util-inl.h"
 
-#include <cstdlib>
-
+#ifndef _WIN32
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <sys/socket.h>
+#endif
 
 namespace node {
 
@@ -71,7 +80,6 @@ MaybeLocal<Object> TCPWrap::Instantiate(Environment* env,
       constructor->NewInstance(env->context(), 1, &type_value));
 }
 
-
 void TCPWrap::Initialize(Local<Object> target,
                          Local<Value> unused,
                          Local<Context> context,
@@ -106,6 +114,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, "setTypeOfService", SetTypeOfService);
+  SetProtoMethod(isolate, t, "getTypeOfService", GetTypeOfService);
   SetProtoMethod(isolate, t, "reset", Reset);
 
 #ifdef _WIN32
@@ -127,9 +137,7 @@ void TCPWrap::Initialize(Local<Object> target,
   NODE_DEFINE_CONSTANT(constants, SERVER);
   NODE_DEFINE_CONSTANT(constants, UV_TCP_IPV6ONLY);
   NODE_DEFINE_CONSTANT(constants, UV_TCP_REUSEPORT);
-  target->Set(context,
-              env->constants_string(),
-              constants).Check();
+  target->Set(context, env->constants_string(), constants).Check();
 }
 
 void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
@@ -145,6 +153,8 @@ void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
   registry->Register(GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
   registry->Register(SetNoDelay);
   registry->Register(SetKeepAlive);
+  registry->Register(SetTypeOfService);
+  registry->Register(GetTypeOfService);
   registry->Register(Reset);
 #ifdef _WIN32
   registry->Register(SetSimultaneousAccepts);
@@ -177,15 +187,13 @@ void TCPWrap::New(const FunctionCallbackInfo<Value>& args) {
   new TCPWrap(env, args.This(), provider);
 }
 
-
 TCPWrap::TCPWrap(Environment* env, Local<Object> object, ProviderType provider)
     : ConnectionWrap(env, object, provider) {
   int r = uv_tcp_init(env->event_loop(), &handle_);
   CHECK_EQ(r, 0);  // How do we proxy this error up to javascript?
                    // Suggestion: uv_tcp_init() returns void.
 }
 
-
 void TCPWrap::SetNoDelay(const FunctionCallbackInfo<Value>& args) {
   TCPWrap* wrap;
   ASSIGN_OR_RETURN_UNWRAP(
@@ -195,7 +203,6 @@ void TCPWrap::SetNoDelay(const FunctionCallbackInfo<Value>& args) {
   args.GetReturnValue().Set(err);
 }
 
-
 void TCPWrap::SetKeepAlive(const FunctionCallbackInfo<Value>& args) {
   TCPWrap* wrap;
   ASSIGN_OR_RETURN_UNWRAP(
@@ -208,6 +215,138 @@ void TCPWrap::SetKeepAlive(const FunctionCallbackInfo<Value>& args) {
   args.GetReturnValue().Set(err);
 }
 
+// TODO(amyssnippet): This implementation uses raw setsockopt/getsockopt calls
+// as a temporary measure. Upstream support is tracked in
+// https://github.com/libuv/libuv/issues/5011.
+// Once uv_tcp_set_tos and uv_tcp_get_tos are available in libuv,
+// this should be refactored to use those APIs.
+void TCPWrap::SetTypeOfService(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::GetTypeOfService(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
+}
 
 #ifdef _WIN32
 void TCPWrap::SetSimultaneousAccepts(const FunctionCallbackInfo<Value>& args) {
@@ -220,7 +359,6 @@ void TCPWrap::SetSimultaneousAccepts(const FunctionCallbackInfo<Value>& args) {
 }
 #endif
 
-
 void TCPWrap::Open(const FunctionCallbackInfo<Value>& args) {
   TCPWrap* wrap;
   ASSIGN_OR_RETURN_UNWRAP(
@@ -231,8 +369,7 @@ void TCPWrap::Open(const FunctionCallbackInfo<Value>& args) {
   int fd = static_cast<int>(val);
   int err = uv_tcp_open(&wrap->handle_, fd);
 
-  if (err == 0)
-    wrap->set_fd(fd);
+  if (err == 0) wrap->set_fd(fd);
 
   args.GetReturnValue().Set(err);
 }
@@ -265,9 +402,8 @@ void TCPWrap::Bind(
   int err = uv_ip_addr(*ip_address, port, &addr);
 
   if (err == 0) {
-    err = uv_tcp_bind(&wrap->handle_,
-                      reinterpret_cast<const sockaddr*>(&addr),
-                      flags);
+    err = uv_tcp_bind(
+        &wrap->handle_, reinterpret_cast<const sockaddr*>(&addr), flags);
   }
   args.GetReturnValue().Set(err);
 }
@@ -276,12 +412,10 @@ void TCPWrap::Bind(const FunctionCallbackInfo<Value>& args) {
   Bind<sockaddr_in>(args, AF_INET, uv_ip4_addr);
 }
 
-
 void TCPWrap::Bind6(const FunctionCallbackInfo<Value>& args) {
   Bind<sockaddr_in6>(args, AF_INET6, uv_ip6_addr);
 }
 
-
 void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
   TCPWrap* wrap;
   ASSIGN_OR_RETURN_UNWRAP(
@@ -292,37 +426,34 @@ void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
 
   THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");
 
-  int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
-                      backlog,
-                      OnConnection);
+  int err = uv_listen(
+      reinterpret_cast<uv_stream_t*>(&wrap->handle_), backlog, OnConnection);
   args.GetReturnValue().Set(err);
 }
 
-
 void TCPWrap::Connect(const FunctionCallbackInfo<Value>& args) {
   CHECK(args[2]->IsUint32());
   // explicit cast to fit to libuv's type expectation
   int port = static_cast<int>(args[2].As<Uint32>()->Value());
-  Connect<sockaddr_in>(args,
-                       [port](const char* ip_address, sockaddr_in* addr) {
-      return uv_ip4_addr(ip_address, port, addr);
+  Connect<sockaddr_in>(args, [port](const char* ip_address, sockaddr_in* addr) {
+    return uv_ip4_addr(ip_address, port, addr);
   });
 }
 
-
 void TCPWrap::Connect6(const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);
   CHECK(args[2]->IsUint32());
   int port;
   if (!args[2]->Int32Value(env->context()).To(&port)) return;
   Connect<sockaddr_in6>(args,
                         [port](const char* ip_address, sockaddr_in6* addr) {
-      return uv_ip6_addr(ip_address, port, addr);
-  });
+                          return uv_ip6_addr(ip_address, port, addr);
+                        });
 }
 
 template <typename T>
-void TCPWrap::Connect(const FunctionCallbackInfo<Value>& args,
+void TCPWrap::Connect(
+    const FunctionCallbackInfo<Value>& args,
     std::function<int(const char* ip_address, T* addr)> uv_ip_addr) {
   Environment* env = Environment::GetCurrent(args);
 
@@ -410,70 +541,68 @@ MaybeLocal<Object> AddressToJS(Environment* env,
   }
 
   switch (addr->sa_family) {
-  case AF_INET6:
-    a6 = reinterpret_cast<const sockaddr_in6*>(addr);
-    uv_inet_ntop(AF_INET6, &a6->sin6_addr, ip, sizeof ip);
-    // Add an interface identifier to a link local address.
-    if (IN6_IS_ADDR_LINKLOCAL(&a6->sin6_addr) && a6->sin6_scope_id > 0) {
-      const size_t addrlen = strlen(ip);
-      CHECK_LT(addrlen, sizeof(ip));
-      ip[addrlen] = '%';
-      size_t scopeidlen = sizeof(ip) - addrlen - 1;
-      CHECK_GE(scopeidlen, UV_IF_NAMESIZE);
-      const int r = uv_if_indextoiid(a6->sin6_scope_id,
-                                     ip + addrlen + 1,
-                                     &scopeidlen);
-      if (r) {
-        env->ThrowUVException(r, "uv_if_indextoiid");
+    case AF_INET6:
+      a6 = reinterpret_cast<const sockaddr_in6*>(addr);
+      uv_inet_ntop(AF_INET6, &a6->sin6_addr, ip, sizeof ip);
+      // Add an interface identifier to a link local address.
+      if (IN6_IS_ADDR_LINKLOCAL(&a6->sin6_addr) && a6->sin6_scope_id > 0) {
+        const size_t addrlen = strlen(ip);
+        CHECK_LT(addrlen, sizeof(ip));
+        ip[addrlen] = '%';
+        size_t scopeidlen = sizeof(ip) - addrlen - 1;
+        CHECK_GE(scopeidlen, UV_IF_NAMESIZE);
+        const int r =
+            uv_if_indextoiid(a6->sin6_scope_id, ip + addrlen + 1, &scopeidlen);
+        if (r) {
+          env->ThrowUVException(r, "uv_if_indextoiid");
+          return {};
+        }
+      }
+      port = ntohs(a6->sin6_port);
+      if (info->Set(env->context(),
+                    env->address_string(),
+                    OneByteString(env->isolate(), ip))
+              .IsNothing() ||
+          info->Set(env->context(), env->family_string(), env->ipv6_string())
+              .IsNothing() ||
+          info->Set(env->context(),
+                    env->port_string(),
+                    Integer::New(env->isolate(), port))
+              .IsNothing()) {
+        return {};
+      }
+      break;
+
+    case AF_INET:
+      a4 = reinterpret_cast<const sockaddr_in*>(addr);
+      uv_inet_ntop(AF_INET, &a4->sin_addr, ip, sizeof ip);
+      port = ntohs(a4->sin_port);
+      if (info->Set(env->context(),
+                    env->address_string(),
+                    OneByteString(env->isolate(), ip))
+              .IsNothing() ||
+          info->Set(env->context(), env->family_string(), env->ipv4_string())
+              .IsNothing() ||
+          info->Set(env->context(),
+                    env->port_string(),
+                    Integer::New(env->isolate(), port))
+              .IsNothing()) {
+        return {};
+      }
+      break;
+
+    default:
+      if (info->Set(env->context(),
+                    env->address_string(),
+                    String::Empty(env->isolate()))
+              .IsNothing()) {
         return {};
       }
-    }
-    port = ntohs(a6->sin6_port);
-    if (info->Set(env->context(),
-                  env->address_string(),
-                  OneByteString(env->isolate(), ip))
-            .IsNothing() ||
-        info->Set(env->context(), env->family_string(), env->ipv6_string())
-            .IsNothing() ||
-        info->Set(env->context(),
-                  env->port_string(),
-                  Integer::New(env->isolate(), port))
-            .IsNothing()) {
-      return {};
-    }
-    break;
-
-  case AF_INET:
-    a4 = reinterpret_cast<const sockaddr_in*>(addr);
-    uv_inet_ntop(AF_INET, &a4->sin_addr, ip, sizeof ip);
-    port = ntohs(a4->sin_port);
-    if (info->Set(env->context(),
-                  env->address_string(),
-                  OneByteString(env->isolate(), ip))
-            .IsNothing() ||
-        info->Set(env->context(), env->family_string(), env->ipv4_string())
-            .IsNothing() ||
-        info->Set(env->context(),
-                  env->port_string(),
-                  Integer::New(env->isolate(), port))
-            .IsNothing()) {
-      return {};
-    }
-    break;
-
-  default:
-    if (info->Set(env->context(),
-                  env->address_string(),
-                  String::Empty(env->isolate()))
-            .IsNothing()) {
-      return {};
-    }
   }
 
   return scope.Escape(info);
 }
 
-
 }  // namespace node
 
 NODE_BINDING_CONTEXT_AWARE_INTERNAL(tcp_wrap, node::TCPWrap::Initialize)

filename: src/tcp_wrap.h
patch: @@ -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 SetTypeOfService(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void GetTypeOfService(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);

filename: test/parallel/test-net-socket-tos.js
patch: @@ -0,0 +1,100 @@
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+const net = require('net');
+
+const server = net.createServer(
+  common.mustCall((socket) => {
+    socket.end();
+  }),
+);
+
+server.listen(
+  0,
+  common.mustCall(() => {
+    const port = server.address().port;
+    const client = new net.Socket();
+
+    // Set TOS before connection to test caching behavior
+    client.setTypeOfService(0x10);
+    client.connect(port);
+
+    client.on(
+      'connect',
+      common.mustCall(() => {
+        // TEST 1: setTypeOfService validation
+        // Should throw if value is not a number, is NaN, or is out of range (0-255)
+        assert.throws(() => client.setTypeOfService('invalid'), {
+          code: 'ERR_INVALID_ARG_TYPE',
+        });
+        assert.throws(() => client.setTypeOfService(NaN), {
+          code: 'ERR_INVALID_ARG_TYPE',
+        });
+        assert.throws(() => client.setTypeOfService(256), {
+          code: 'ERR_OUT_OF_RANGE',
+        });
+        assert.throws(() => client.setTypeOfService(-1), {
+          code: 'ERR_OUT_OF_RANGE',
+        });
+
+        // TEST 2a: Verify deferred application
+        // Check if the TOS value set before connect() was cached and applied.
+        // We mask with 0xFC to check only the high 6 bits (DSCP),
+        // ignoring the lowest 2 bits (ECN) which the OS may modify or zero out.
+        const mask = 0xFC;
+        const preConnectGot = client.getTypeOfService();
+
+        // Windows often resets TOS or ignores it without admin/registry tweaks.
+        // We only assert strict equality on non-Windows platforms.
+        if (!common.isWindows) {
+          assert.strictEqual(
+            preConnectGot & mask,
+            0x10 & mask,
+            `Pre-connect TOS should be ${0x10 & mask}, got ${preConnectGot & mask}`,
+          );
+        }
+
+        // TEST 2b: Setting and getting TOS on an active connection
+        const tosValue = 0x10; // IPTOS_LOWDELAY (16)
+
+        // On all platforms, this should succeed (tries both IPv4 and IPv6)
+        client.setTypeOfService(tosValue);
+
+        // Verify values
+        const got = client.getTypeOfService();
+
+        if (!common.isWindows) {
+          assert.strictEqual(
+            got & mask,
+            tosValue & mask,
+            `Expected TOS ${tosValue & mask}, got ${got & mask}`,
+          );
+        }
+
+        // TEST 3: Boundary values
+        // Check min (0x00), max (0xFF), and arbitrary intermediate values
+        for (const boundaryValue of [0x00, 0xFF, 0x3F]) {
+          client.setTypeOfService(boundaryValue);
+          const gotBoundary = client.getTypeOfService();
+
+          if (!common.isWindows) {
+            assert.strictEqual(
+              gotBoundary & mask,
+              boundaryValue & mask,
+              `Expected TOS ${boundaryValue & mask}, got ${gotBoundary & mask}`,
+            );
+          }
+        }
+
+        client.end();
+      }),
+    );
+
+    client.on(
+      'end',
+      common.mustCall(() => {
+        server.close();
+      }),
+    );
+  }),
+);