diff --git a/doc/api/dgram.md b/doc/api/dgram.md index a16a4b7c054236..20561ce883a734 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -227,6 +227,20 @@ never have reason to call this. If `multicastInterface` is not specified, the operating system will attempt to drop membership on all valid interfaces. +### socket.getRecvBufferSize(size) + + +* Returns {number} the `SO_RCVBUF` socket receive buffer size in bytes. + +### socket.getSendBufferSize(size) + + +* Returns {number} the `SO_SNDBUF` socket send buffer size in bytes. + ### socket.ref() + +* `size` {number} Integer + +Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer +in bytes. + +### socket.setSendBufferSize(size) + + +* `size` {number} Integer + +Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer +in bytes. + ### socket.setTTL(ttl) * `options` {Object} Available options are: @@ -547,6 +584,8 @@ changes: * `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the address, even if another process has already bound a socket on it. Optional. Defaults to `false`. + * `recvBufferSize` {number} - Optional. Sets the `SO_RCVBUF` socket value. + * `sendBufferSize` {number} - Optional. Sets the `SO_SNDBUF` socket value. * `lookup` {Function} Custom lookup function. Defaults to [`dns.lookup()`][]. Optional. * `callback` {Function} Attached as a listener for `'message'` events. Optional. diff --git a/lib/dgram.js b/lib/dgram.js index 7c73c713e21abe..8a14fcede68112 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -98,15 +98,19 @@ function _createSocketHandle(address, port, addressType, fd, flags) { return handle; } +const kOptionSymbol = Symbol('options symbol'); function Socket(type, listener) { EventEmitter.call(this); var lookup; + this[kOptionSymbol] = {}; if (type !== null && typeof type === 'object') { var options = type; type = options.type; lookup = options.lookup; + this[kOptionSymbol].recvBufferSize = options.recvBufferSize; + this[kOptionSymbol].sendBufferSize = options.sendBufferSize; } var handle = newHandle(type, lookup); @@ -141,6 +145,12 @@ function startListening(socket) { socket._bindState = BIND_STATE_BOUND; socket.fd = -42; // compatibility hack + if (socket[kOptionSymbol].recvBufferSize) + bufferSize(socket, socket[kOptionSymbol].recvBufferSize, 'recv'); + + if (socket[kOptionSymbol].sendBufferSize) + bufferSize(socket, socket[kOptionSymbol].sendBufferSize, 'send'); + socket.emit('listening'); } @@ -157,6 +167,20 @@ function replaceHandle(self, newHandle) { self._handle = newHandle; } +function bufferSize(self, size, buffer) { + if (size >>> 0 !== size) + throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE'); + + try { + if (buffer === 'recv') + return self._handle.bufferSize(size, 0); + else + return self._handle.bufferSize(size, 1); + } catch (e) { + throw new errors.Error('ERR_SOCKET_BUFFER_SIZE', e); + } +} + Socket.prototype.bind = function(port_, address_ /*, callback*/) { let port = port_; @@ -651,6 +675,27 @@ Socket.prototype.unref = function() { return this; }; + +Socket.prototype.setRecvBufferSize = function(size) { + bufferSize(this, size, 'recv'); +}; + + +Socket.prototype.setSendBufferSize = function(size) { + bufferSize(this, size, 'send'); +}; + + +Socket.prototype.getRecvBufferSize = function() { + return bufferSize(this, 0, 'recv'); +}; + + +Socket.prototype.getSendBufferSize = function() { + return bufferSize(this, 0, 'send'); +}; + + module.exports = { _createSocketHandle, createSocket, diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 3c11075485c592..5c66adf74d2d28 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -242,6 +242,9 @@ E('ERR_SOCKET_BAD_TYPE', 'Bad socket type specified. Valid types are: udp4, udp6'); E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data'); E('ERR_SOCKET_BAD_PORT', 'Port should be > 0 and < 65536'); +E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer'); +E('ERR_SOCKET_BUFFER_SIZE', + (reason) => `Could not get or set buffer size: ${reason}`); E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running'); E('ERR_OUTOFMEMORY', 'Out of memory'); E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed'); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 4890dc08c8ee54..e7a655298ef723 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -46,6 +46,7 @@ using v8::Object; using v8::PropertyAttribute; using v8::PropertyCallbackInfo; using v8::String; +using v8::Uint32; using v8::Undefined; using v8::Value; @@ -135,6 +136,7 @@ void UDPWrap::Initialize(Local target, env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback); env->SetProtoMethod(t, "setBroadcast", SetBroadcast); env->SetProtoMethod(t, "setTTL", SetTTL); + env->SetProtoMethod(t, "bufferSize", BufferSize); env->SetProtoMethod(t, "ref", HandleWrap::Ref); env->SetProtoMethod(t, "unref", HandleWrap::Unref); @@ -223,6 +225,43 @@ void UDPWrap::Bind6(const FunctionCallbackInfo& args) { } +void UDPWrap::BufferSize(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + UDPWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, + args.Holder(), + args.GetReturnValue().Set(UV_EBADF)); + + CHECK(args[0]->IsUint32()); + CHECK(args[1]->IsUint32()); + int size = static_cast(args[0].As()->Value()); + + if (size != args[0].As()->Value()) { + if (args[1].As()->Value() == 0) + return env->ThrowUVException(EINVAL, "uv_recv_buffer_size"); + else + return env->ThrowUVException(EINVAL, "uv_send_buffer_size"); + } + + int err; + if (args[1].As()->Value() == 0) { + err = uv_recv_buffer_size(reinterpret_cast(&wrap->handle_), + &size); + } else { + err = uv_send_buffer_size(reinterpret_cast(&wrap->handle_), + &size); + } + + if (err != 0) { + if (args[1].As()->Value() == 0) + return env->ThrowUVException(err, "uv_recv_buffer_size"); + else + return env->ThrowUVException(err, "uv_send_buffer_size"); + } + args.GetReturnValue().Set(size); +} + + #define X(name, fn) \ void UDPWrap::name(const FunctionCallbackInfo& args) { \ UDPWrap* wrap = Unwrap(args.Holder()); \ diff --git a/src/udp_wrap.h b/src/udp_wrap.h index b93cb3056b80b5..5be603d62e31e2 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -57,6 +57,7 @@ class UDPWrap: public HandleWrap { const v8::FunctionCallbackInfo& args); static void SetBroadcast(const v8::FunctionCallbackInfo& args); static void SetTTL(const v8::FunctionCallbackInfo& args); + static void BufferSize(const v8::FunctionCallbackInfo& args); static v8::Local Instantiate(Environment* env, AsyncWrap* parent); uv_udp_t* UVHandle(); diff --git a/test/parallel/test-dgram-createSocket-type.js b/test/parallel/test-dgram-createSocket-type.js index 125a9d0be5cc1c..7ff79b7b3418fd 100644 --- a/test/parallel/test-dgram-createSocket-type.js +++ b/test/parallel/test-dgram-createSocket-type.js @@ -39,3 +39,23 @@ validTypes.forEach((validType) => { socket.close(); }); }); + +// Ensure buffer sizes can be set +{ + const socket = dgram.createSocket({ + type: 'udp4', + recvBufferSize: 10000, + sendBufferSize: 15000 + }); + + socket.bind(common.mustCall(() => { + // note: linux will double the buffer size + assert.ok(socket.getRecvBufferSize() === 10000 || + socket.getRecvBufferSize() === 20000, + 'SO_RCVBUF not 1300 or 2600'); + assert.ok(socket.getSendBufferSize() === 15000 || + socket.getSendBufferSize() === 30000, + 'SO_SNDBUF not 1800 or 3600'); + socket.close(); + })); +} diff --git a/test/parallel/test-dgram-socket-buffer-size.js b/test/parallel/test-dgram-socket-buffer-size.js new file mode 100644 index 00000000000000..13fbd4fb4be601 --- /dev/null +++ b/test/parallel/test-dgram-socket-buffer-size.js @@ -0,0 +1,74 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +{ + // Should throw error if the socket is never bound. + const errorObj = { + code: 'ERR_SOCKET_BUFFER_SIZE', + type: Error, + message: /^Could not get or set buffer size:.*$/ + }; + + const socket = dgram.createSocket('udp4'); + + assert.throws(() => { + socket.setRecvBufferSize(8192); + }, common.expectsError(errorObj)); + + assert.throws(() => { + socket.setSendBufferSize(8192); + }, common.expectsError(errorObj)); + + assert.throws(() => { + socket.getRecvBufferSize(); + }, common.expectsError(errorObj)); + + assert.throws(() => { + socket.getSendBufferSize(); + }, common.expectsError(errorObj)); +} + +{ + // Should throw error if invalid buffer size is specified + const errorObj = { + code: 'ERR_SOCKET_BAD_BUFFER_SIZE', + type: TypeError, + message: /^Buffer size must be a positive integer$/ + }; + + const badBufferSizes = [-1, Infinity, 'Doh!']; + + const socket = dgram.createSocket('udp4'); + + socket.bind(common.mustCall(() => { + badBufferSizes.forEach((badBufferSize) => { + assert.throws(() => { + socket.setRecvBufferSize(badBufferSize); + }, common.expectsError(errorObj)); + + assert.throws(() => { + socket.setSendBufferSize(badBufferSize); + }, common.expectsError(errorObj)); + }); + socket.close(); + })); +} + +{ + // Can set and get buffer sizes after binding the socket. + const socket = dgram.createSocket('udp4'); + + socket.bind(common.mustCall(() => { + socket.setRecvBufferSize(10000); + socket.setSendBufferSize(10000); + + // note: linux will double the buffer size + const expectedBufferSize = common.isLinux ? 20000 : 10000; + assert.strictEqual(socket.getRecvBufferSize(), expectedBufferSize); + assert.strictEqual(socket.getSendBufferSize(), expectedBufferSize); + socket.close(); + })); +}