Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net,dgram: add ipv6Only option for net and dgram #23798

Closed
wants to merge 1 commit into from
Closed
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
6 changes: 6 additions & 0 deletions doc/api/dgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,9 @@ changes:
pr-url: https://github.com/nodejs/node/pull/13623
description: The `recvBufferSize` and `sendBufferSize` options are
supported now.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/23798
description: The `ipv6Only` option is supported.
-->

* `options` {Object} Available options are:
Expand All @@ -609,6 +612,9 @@ changes:
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
address, even if another process has already bound a socket on it.
**Default:** `false`.
* `ipv6Only` {boolean} Setting `ipv6Only` to `true` will
disable dual-stack support, i.e., binding to address `::` won't make
`0.0.0.0` be bound. **Default:** `false`.
* `recvBufferSize` {number} - Sets the `SO_RCVBUF` socket value.
* `sendBufferSize` {number} - Sets the `SO_SNDBUF` socket value.
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
Expand Down
7 changes: 7 additions & 0 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ Listening on a file descriptor is not supported on Windows.
#### server.listen(options[, callback])
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/23798
description: The `ipv6Only` option is supported.
-->

* `options` {Object} Required. Supports the following properties:
Expand All @@ -266,6 +270,9 @@ added: v0.11.14
for all users. **Default:** `false`
* `writableAll` {boolean} For IPC servers makes the pipe writable
for all users. **Default:** `false`
* `ipv6Only` {boolean} For TCP servers, setting `ipv6Only` to `true` will
disable dual-stack support, i.e., binding to host `::` won't make
`0.0.0.0` be bound. **Default:** `false`.
* `callback` {Function} Common parameter of [`server.listen()`][]
functions.
* Returns: {net.Server}
Expand Down
9 changes: 8 additions & 1 deletion lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ const {
} = require('internal/async_hooks');
const { UV_UDP_REUSEADDR } = internalBinding('constants').os;

const { UDP, SendWrap } = internalBinding('udp_wrap');
const {
constants: { UV_UDP_IPV6ONLY },
UDP,
SendWrap
} = internalBinding('udp_wrap');

const BIND_STATE_UNBOUND = 0;
const BIND_STATE_BINDING = 1;
Expand Down Expand Up @@ -99,6 +103,7 @@ function Socket(type, listener) {
bindState: BIND_STATE_UNBOUND,
queue: undefined,
reuseAddr: options && options.reuseAddr, // Use UV_UDP_REUSEADDR if true.
ipv6Only: options && options.ipv6Only,
recvBufferSize,
sendBufferSize
};
Expand Down Expand Up @@ -270,6 +275,8 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
var flags = 0;
if (state.reuseAddr)
flags |= UV_UDP_REUSEADDR;
if (state.ipv6Only)
flags |= UV_UDP_IPV6ONLY;

if (cluster.isWorker && !exclusive) {
bindServerHandle(this, {
Expand Down
14 changes: 10 additions & 4 deletions lib/internal/cluster/round_robin_handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ const assert = require('assert');
const net = require('net');
const { sendHelper } = require('internal/cluster/utils');
const uv = internalBinding('uv');
const { constants } = internalBinding('tcp_wrap');

module.exports = RoundRobinHandle;

function RoundRobinHandle(key, address, port, addressType, fd) {
function RoundRobinHandle(key, address, port, addressType, fd, flags) {
this.key = key;
this.all = new Map();
this.free = [];
Expand All @@ -16,9 +17,14 @@ function RoundRobinHandle(key, address, port, addressType, fd) {

if (fd >= 0)
this.server.listen({ fd });
else if (port >= 0)
this.server.listen(port, address);
else
else if (port >= 0) {
this.server.listen({
port,
host: address,
// Currently, net module only supports `ipv6Only` option in `flags`.
ipv6Only: Boolean(flags & constants.UV_TCP_IPV6ONLY),
});
} else
this.server.listen(address); // UNIX socket path.

this.server.once('listening', () => {
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/cluster/shared_handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function SharedHandle(key, address, port, addressType, fd, flags) {
if (addressType === 'udp4' || addressType === 'udp6')
rval = dgram._createSocketHandle(address, port, addressType, fd, flags);
else
rval = net._createServerHandle(address, port, addressType, fd);
rval = net._createServerHandle(address, port, addressType, fd, flags);

if (typeof rval === 'number')
this.errno = rval;
Expand Down
35 changes: 20 additions & 15 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ const {

function noop() {}

function getFlags(ipv6Only) {
return ipv6Only === true ? TCPConstants.UV_TCP_IPV6ONLY : 0;
}

function createHandle(fd, is_server) {
validateInt32(fd, 'fd', 0);
const type = TTYWrap.guessHandleType(fd);
Expand Down Expand Up @@ -826,7 +830,7 @@ function checkBindError(err, port, handle) {


function internalConnect(
self, address, port, addressType, localAddress, localPort) {
self, address, port, addressType, localAddress, localPort, flags) {
// TODO return promise from Socket.prototype.connect which
// wraps _connectReq.

Expand All @@ -840,7 +844,7 @@ function internalConnect(
err = self._handle.bind(localAddress, localPort);
} else { // addressType === 6
localAddress = localAddress || '::';
err = self._handle.bind6(localAddress, localPort);
err = self._handle.bind6(localAddress, localPort, flags);
}
debug('binding to localAddress: %s and localPort: %d (addressType: %d)',
localAddress, localPort, addressType);
Expand Down Expand Up @@ -1176,7 +1180,7 @@ util.inherits(Server, EventEmitter);
function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; }

// Returns handle if it can be created, or error code if it can't
function createServerHandle(address, port, addressType, fd) {
function createServerHandle(address, port, addressType, fd, flags) {
var err = 0;
// assign handle in listen, and clean up if bind or listen fails
var handle;
Expand Down Expand Up @@ -1215,14 +1219,14 @@ function createServerHandle(address, port, addressType, fd) {
debug('bind to', address || 'any');
if (!address) {
// Try binding to ipv6 first
err = handle.bind6('::', port);
err = handle.bind6('::', port, flags);
if (err) {
handle.close();
// Fallback to ipv4
return createServerHandle('0.0.0.0', port);
}
} else if (addressType === 6) {
err = handle.bind6(address, port);
err = handle.bind6(address, port, flags);
} else {
err = handle.bind(address, port);
}
Expand All @@ -1236,7 +1240,7 @@ function createServerHandle(address, port, addressType, fd) {
return handle;
}

function setupListenHandle(address, port, addressType, backlog, fd) {
function setupListenHandle(address, port, addressType, backlog, fd, flags) {
debug('setupListenHandle', address, port, addressType, backlog, fd);

// If there is not yet a handle, we need to create one and bind.
Expand All @@ -1250,7 +1254,7 @@ function setupListenHandle(address, port, addressType, backlog, fd) {

// Try to bind to the unspecified IPv6 address, see if IPv6 is available
if (!address && typeof fd !== 'number') {
rval = createServerHandle('::', port, 6, fd);
rval = createServerHandle('::', port, 6, fd, flags);

if (typeof rval === 'number') {
rval = null;
Expand All @@ -1263,7 +1267,7 @@ function setupListenHandle(address, port, addressType, backlog, fd) {
}

if (rval === null)
rval = createServerHandle(address, port, addressType, fd);
rval = createServerHandle(address, port, addressType, fd, flags);

if (typeof rval === 'number') {
var error = uvExceptionWithHostPort(rval, 'listen', address, port);
Expand Down Expand Up @@ -1322,7 +1326,7 @@ function emitListeningNT(self) {


function listenInCluster(server, address, port, addressType,
backlog, fd, exclusive) {
backlog, fd, exclusive, flags) {
exclusive = !!exclusive;

if (cluster === undefined) cluster = require('cluster');
Expand All @@ -1331,7 +1335,7 @@ function listenInCluster(server, address, port, addressType,
// Will create a new handle
// _listen2 sets up the listened handle, it is still named like this
// to avoid breaking code that wraps this method
server._listen2(address, port, addressType, backlog, fd);
server._listen2(address, port, addressType, backlog, fd, flags);
return;
}

Expand All @@ -1340,7 +1344,7 @@ function listenInCluster(server, address, port, addressType,
port: port,
addressType: addressType,
fd: fd,
flags: 0
flags,
};

// Get the master's server handle, and listen on it
Expand All @@ -1358,7 +1362,7 @@ function listenInCluster(server, address, port, addressType,
server._handle = handle;
// _listen2 sets up the listened handle, it is still named like this
// to avoid breaking code that wraps this method
server._listen2(address, port, addressType, backlog, fd);
server._listen2(address, port, addressType, backlog, fd, flags);
}
}

Expand All @@ -1381,6 +1385,7 @@ Server.prototype.listen = function(...args) {
toNumber(args.length > 2 && args[2]); // (port, host, backlog)

options = options._handle || options.handle || options;
const flags = getFlags(options.ipv6Only);
// (handle[, backlog][, cb]) where handle is an object with a handle
if (options instanceof TCP) {
this._handle = options;
Expand Down Expand Up @@ -1415,7 +1420,7 @@ Server.prototype.listen = function(...args) {
// start TCP server listening on host:port
if (options.host) {
lookupAndListen(this, options.port | 0, options.host, backlog,
options.exclusive);
options.exclusive, flags);
} else { // Undefined host, listens on unspecified address
// Default addressType 4 will be used to search for master server
listenInCluster(this, null, options.port | 0, 4,
Expand Down Expand Up @@ -1462,15 +1467,15 @@ Server.prototype.listen = function(...args) {
throw new ERR_INVALID_OPT_VALUE('options', util.inspect(options));
};

function lookupAndListen(self, port, address, backlog, exclusive) {
function lookupAndListen(self, port, address, backlog, exclusive, flags) {
if (dns === undefined) dns = require('dns');
dns.lookup(address, function doListen(err, ip, addressType) {
if (err) {
self.emit('error', err);
} else {
addressType = ip ? addressType : 4;
listenInCluster(self, ip, port, addressType,
backlog, undefined, exclusive);
backlog, undefined, exclusive, flags);
}
});
}
Expand Down
5 changes: 4 additions & 1 deletion src/tcp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ void TCPWrap::Initialize(Local<Object> target,
Local<Object> constants = Object::New(env->isolate());
NODE_DEFINE_CONSTANT(constants, SOCKET);
NODE_DEFINE_CONSTANT(constants, SERVER);
NODE_DEFINE_CONSTANT(constants, UV_TCP_IPV6ONLY);
target->Set(context,
env->constants_string(),
constants).FromJust();
Expand Down Expand Up @@ -252,13 +253,15 @@ void TCPWrap::Bind6(const FunctionCallbackInfo<Value>& args) {
Environment* env = wrap->env();
node::Utf8Value ip6_address(env->isolate(), args[0]);
int port;
unsigned int flags;
if (!args[1]->Int32Value(env->context()).To(&port)) return;
if (!args[2]->Uint32Value(env->context()).To(&flags)) return;
sockaddr_in6 addr;
int err = uv_ip6_addr(*ip6_address, port, &addr);
if (err == 0) {
err = uv_tcp_bind(&wrap->handle_,
reinterpret_cast<const sockaddr*>(&addr),
0);
flags);
}
args.GetReturnValue().Set(err);
}
Expand Down
6 changes: 6 additions & 0 deletions src/udp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ void UDPWrap::Initialize(Local<Object> target,
target->Set(env->context(),
sendWrapString,
swt->GetFunction(env->context()).ToLocalChecked()).FromJust();

Local<Object> constants = Object::New(env->isolate());
NODE_DEFINE_CONSTANT(constants, UV_UDP_IPV6ONLY);
target->Set(context,
env->constants_string(),
constants).FromJust();
}


Expand Down
51 changes: 51 additions & 0 deletions test/parallel/test-cluster-dgram-ipv6only.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const common = require('../common');
if (!common.hasIPv6)
common.skip('no IPv6 support');
if (common.isWindows)
common.skip('dgram clustering is currently not supported on windows.');

const assert = require('assert');
const cluster = require('cluster');
const dgram = require('dgram');

// This test ensures that the `ipv6Only` option in `dgram.createSock()`
// works as expected.
if (cluster.isMaster) {
cluster.fork().on('exit', common.mustCall((code) => {
assert.strictEqual(code, 0);
}));
} else {
let waiting = 2;
function close() {
if (--waiting === 0)
cluster.worker.disconnect();
}

const socket1 = dgram.createSocket({
type: 'udp6',
ipv6Only: true
});
const socket2 = dgram.createSocket({
type: 'udp4',
});
socket1.on('error', common.mustNotCall());
socket2.on('error', common.mustNotCall());

socket1.bind({
port: 0,
address: '::',
}, common.mustCall(() => {
const { port } = socket1.address();
socket2.bind({
port,
address: '0.0.0.0',
}, common.mustCall(() => {
process.nextTick(() => {
socket1.close(close);
socket2.close(close);
});
}));
}));
}
Loading