GeezerVison is showing github.com — net,dgram: add ipv6Only option for net and dgram · nodejs/node@a1254a3
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/dgram.md +6Lines changed: 6 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -601,6 +601,9 @@ changes: | 601 | 601 | pr-url: https://github.com/nodejs/node/pull/13623 | 602 | 602 | description: The `recvBufferSize` and `sendBufferSize` options are | 603 | 603 | supported now. | | 604 | + - version: REPLACEME | | 605 | + pr-url: https://github.com/nodejs/node/pull/23798 | | 606 | + description: The `ipv6Only` option is supported. | 604 | 607 | --> | 605 | 608 |
| 606 | 609 | * `options` {Object} Available options are: | @@ -609,6 +612,9 @@ changes: | 609 | 612 | * `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the | 610 | 613 | address, even if another process has already bound a socket on it. | 611 | 614 | **Default:** `false`. | | 615 | + * `ipv6Only` {boolean} Setting `ipv6Only` to `true` will | | 616 | + disable dual-stack support, i.e., binding to address `::` won't make | | 617 | + `0.0.0.0` be bound. **Default:** `false`. | 612 | 618 | * `recvBufferSize` {number} - Sets the `SO_RCVBUF` socket value. | 613 | 619 | * `sendBufferSize` {number} - Sets the `SO_SNDBUF` socket value. | 614 | 620 | * `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][]. | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/net.md +7Lines changed: 7 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -252,6 +252,10 @@ Listening on a file descriptor is not supported on Windows. | 252 | 252 | #### server.listen(options[, callback]) | 253 | 253 | <!-- YAML | 254 | 254 | added: v0.11.14 | | 255 | +changes: | | 256 | + - version: REPLACEME | | 257 | + pr-url: https://github.com/nodejs/node/pull/23798 | | 258 | + description: The `ipv6Only` option is supported. | 255 | 259 | --> | 256 | 260 |
| 257 | 261 | * `options` {Object} Required. Supports the following properties: | @@ -266,6 +270,9 @@ added: v0.11.14 | 266 | 270 | for all users. **Default:** `false` | 267 | 271 | * `writableAll` {boolean} For IPC servers makes the pipe writable | 268 | 272 | for all users. **Default:** `false` | | 273 | + * `ipv6Only` {boolean} For TCP servers, setting `ipv6Only` to `true` will | | 274 | + disable dual-stack support, i.e., binding to host `::` won't make | | 275 | + `0.0.0.0` be bound. **Default:** `false`. | 269 | 276 | * `callback` {Function} Common parameter of [`server.listen()`][] | 270 | 277 | functions. | 271 | 278 | * Returns: {net.Server} | |
Collapse file Copy file name to clipboardExpand all lines: lib/dgram.js +8-1Lines changed: 8 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -54,7 +54,11 @@ const { | 54 | 54 | } = require('internal/async_hooks'); | 55 | 55 | const { UV_UDP_REUSEADDR } = internalBinding('constants').os; | 56 | 56 |
| 57 | | -const { UDP, SendWrap } = internalBinding('udp_wrap'); | | 57 | +const { | | 58 | + constants: { UV_UDP_IPV6ONLY }, | | 59 | + UDP, | | 60 | + SendWrap | | 61 | +} = internalBinding('udp_wrap'); | 58 | 62 |
| 59 | 63 | const BIND_STATE_UNBOUND = 0; | 60 | 64 | const BIND_STATE_BINDING = 1; | @@ -99,6 +103,7 @@ function Socket(type, listener) { | 99 | 103 | bindState: BIND_STATE_UNBOUND, | 100 | 104 | queue: undefined, | 101 | 105 | reuseAddr: options && options.reuseAddr, // Use UV_UDP_REUSEADDR if true. | | 106 | + ipv6Only: options && options.ipv6Only, | 102 | 107 | recvBufferSize, | 103 | 108 | sendBufferSize | 104 | 109 | }; | @@ -270,6 +275,8 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { | 270 | 275 | var flags = 0; | 271 | 276 | if (state.reuseAddr) | 272 | 277 | flags |= UV_UDP_REUSEADDR; | | 278 | + if (state.ipv6Only) | | 279 | + flags |= UV_UDP_IPV6ONLY; | 273 | 280 |
| 274 | 281 | if (cluster.isWorker && !exclusive) { | 275 | 282 | bindServerHandle(this, { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/cluster/round_robin_handle.js +10-4Lines changed: 10 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -3,10 +3,11 @@ const assert = require('assert'); | 3 | 3 | const net = require('net'); | 4 | 4 | const { sendHelper } = require('internal/cluster/utils'); | 5 | 5 | const uv = internalBinding('uv'); | | 6 | +const { constants } = internalBinding('tcp_wrap'); | 6 | 7 |
| 7 | 8 | module.exports = RoundRobinHandle; | 8 | 9 |
| 9 | | -function RoundRobinHandle(key, address, port, addressType, fd) { | | 10 | +function RoundRobinHandle(key, address, port, addressType, fd, flags) { | 10 | 11 | this.key = key; | 11 | 12 | this.all = new Map(); | 12 | 13 | this.free = []; | @@ -16,9 +17,14 @@ function RoundRobinHandle(key, address, port, addressType, fd) { | 16 | 17 |
| 17 | 18 | if (fd >= 0) | 18 | 19 | this.server.listen({ fd }); | 19 | | - else if (port >= 0) | 20 | | - this.server.listen(port, address); | 21 | | - else | | 20 | + else if (port >= 0) { | | 21 | + this.server.listen({ | | 22 | + port, | | 23 | + host: address, | | 24 | + // Currently, net module only supports `ipv6Only` option in `flags`. | | 25 | + ipv6Only: Boolean(flags & constants.UV_TCP_IPV6ONLY), | | 26 | + }); | | 27 | + } else | 22 | 28 | this.server.listen(address); // UNIX socket path. | 23 | 29 |
| 24 | 30 | this.server.once('listening', () => { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/cluster/shared_handle.js +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -15,7 +15,7 @@ function SharedHandle(key, address, port, addressType, fd, flags) { | 15 | 15 | if (addressType === 'udp4' || addressType === 'udp6') | 16 | 16 | rval = dgram._createSocketHandle(address, port, addressType, fd, flags); | 17 | 17 | else | 18 | | - rval = net._createServerHandle(address, port, addressType, fd); | | 18 | + rval = net._createServerHandle(address, port, addressType, fd, flags); | 19 | 19 |
| 20 | 20 | if (typeof rval === 'number') | 21 | 21 | this.errno = rval; | |
Collapse file Copy file name to clipboardExpand all lines: lib/net.js +20-15Lines changed: 20 additions & 15 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -97,6 +97,10 @@ const { | 97 | 97 |
| 98 | 98 | function noop() {} | 99 | 99 |
| | 100 | +function getFlags(ipv6Only) { | | 101 | + return ipv6Only === true ? TCPConstants.UV_TCP_IPV6ONLY : 0; | | 102 | +} | | 103 | + | 100 | 104 | function createHandle(fd, is_server) { | 101 | 105 | validateInt32(fd, 'fd', 0); | 102 | 106 | const type = TTYWrap.guessHandleType(fd); | @@ -798,7 +802,7 @@ function checkBindError(err, port, handle) { | 798 | 802 |
| 799 | 803 |
| 800 | 804 | function internalConnect( | 801 | | - self, address, port, addressType, localAddress, localPort) { | | 805 | + self, address, port, addressType, localAddress, localPort, flags) { | 802 | 806 | // TODO return promise from Socket.prototype.connect which | 803 | 807 | // wraps _connectReq. | 804 | 808 |
| @@ -812,7 +816,7 @@ function internalConnect( | 812 | 816 | err = self._handle.bind(localAddress, localPort); | 813 | 817 | } else { // addressType === 6 | 814 | 818 | localAddress = localAddress || '::'; | 815 | | - err = self._handle.bind6(localAddress, localPort); | | 819 | + err = self._handle.bind6(localAddress, localPort, flags); | 816 | 820 | } | 817 | 821 | debug('binding to localAddress: %s and localPort: %d (addressType: %d)', | 818 | 822 | localAddress, localPort, addressType); | @@ -1148,7 +1152,7 @@ util.inherits(Server, EventEmitter); | 1148 | 1152 | function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; } | 1149 | 1153 |
| 1150 | 1154 | // Returns handle if it can be created, or error code if it can't | 1151 | | -function createServerHandle(address, port, addressType, fd) { | | 1155 | +function createServerHandle(address, port, addressType, fd, flags) { | 1152 | 1156 | var err = 0; | 1153 | 1157 | // assign handle in listen, and clean up if bind or listen fails | 1154 | 1158 | var handle; | @@ -1187,14 +1191,14 @@ function createServerHandle(address, port, addressType, fd) { | 1187 | 1191 | debug('bind to', address || 'any'); | 1188 | 1192 | if (!address) { | 1189 | 1193 | // Try binding to ipv6 first | 1190 | | - err = handle.bind6('::', port); | | 1194 | + err = handle.bind6('::', port, flags); | 1191 | 1195 | if (err) { | 1192 | 1196 | handle.close(); | 1193 | 1197 | // Fallback to ipv4 | 1194 | 1198 | return createServerHandle('0.0.0.0', port); | 1195 | 1199 | } | 1196 | 1200 | } else if (addressType === 6) { | 1197 | | - err = handle.bind6(address, port); | | 1201 | + err = handle.bind6(address, port, flags); | 1198 | 1202 | } else { | 1199 | 1203 | err = handle.bind(address, port); | 1200 | 1204 | } | @@ -1208,7 +1212,7 @@ function createServerHandle(address, port, addressType, fd) { | 1208 | 1212 | return handle; | 1209 | 1213 | } | 1210 | 1214 |
| 1211 | | -function setupListenHandle(address, port, addressType, backlog, fd) { | | 1215 | +function setupListenHandle(address, port, addressType, backlog, fd, flags) { | 1212 | 1216 | debug('setupListenHandle', address, port, addressType, backlog, fd); | 1213 | 1217 |
| 1214 | 1218 | // If there is not yet a handle, we need to create one and bind. | @@ -1222,7 +1226,7 @@ function setupListenHandle(address, port, addressType, backlog, fd) { | 1222 | 1226 |
| 1223 | 1227 | // Try to bind to the unspecified IPv6 address, see if IPv6 is available | 1224 | 1228 | if (!address && typeof fd !== 'number') { | 1225 | | - rval = createServerHandle('::', port, 6, fd); | | 1229 | + rval = createServerHandle('::', port, 6, fd, flags); | 1226 | 1230 |
| 1227 | 1231 | if (typeof rval === 'number') { | 1228 | 1232 | rval = null; | @@ -1235,7 +1239,7 @@ function setupListenHandle(address, port, addressType, backlog, fd) { | 1235 | 1239 | } | 1236 | 1240 |
| 1237 | 1241 | if (rval === null) | 1238 | | - rval = createServerHandle(address, port, addressType, fd); | | 1242 | + rval = createServerHandle(address, port, addressType, fd, flags); | 1239 | 1243 |
| 1240 | 1244 | if (typeof rval === 'number') { | 1241 | 1245 | var error = uvExceptionWithHostPort(rval, 'listen', address, port); | @@ -1294,7 +1298,7 @@ function emitListeningNT(self) { | 1294 | 1298 |
| 1295 | 1299 |
| 1296 | 1300 | function listenInCluster(server, address, port, addressType, | 1297 | | - backlog, fd, exclusive) { | | 1301 | + backlog, fd, exclusive, flags) { | 1298 | 1302 | exclusive = !!exclusive; | 1299 | 1303 |
| 1300 | 1304 | if (cluster === undefined) cluster = require('cluster'); | @@ -1303,7 +1307,7 @@ function listenInCluster(server, address, port, addressType, | 1303 | 1307 | // Will create a new handle | 1304 | 1308 | // _listen2 sets up the listened handle, it is still named like this | 1305 | 1309 | // to avoid breaking code that wraps this method | 1306 | | - server._listen2(address, port, addressType, backlog, fd); | | 1310 | + server._listen2(address, port, addressType, backlog, fd, flags); | 1307 | 1311 | return; | 1308 | 1312 | } | 1309 | 1313 |
| @@ -1312,7 +1316,7 @@ function listenInCluster(server, address, port, addressType, | 1312 | 1316 | port: port, | 1313 | 1317 | addressType: addressType, | 1314 | 1318 | fd: fd, | 1315 | | - flags: 0 | | 1319 | + flags, | 1316 | 1320 | }; | 1317 | 1321 |
| 1318 | 1322 | // Get the master's server handle, and listen on it | @@ -1330,7 +1334,7 @@ function listenInCluster(server, address, port, addressType, | 1330 | 1334 | server._handle = handle; | 1331 | 1335 | // _listen2 sets up the listened handle, it is still named like this | 1332 | 1336 | // to avoid breaking code that wraps this method | 1333 | | - server._listen2(address, port, addressType, backlog, fd); | | 1337 | + server._listen2(address, port, addressType, backlog, fd, flags); | 1334 | 1338 | } | 1335 | 1339 | } | 1336 | 1340 |
| @@ -1353,6 +1357,7 @@ Server.prototype.listen = function(...args) { | 1353 | 1357 | toNumber(args.length > 2 && args[2]); // (port, host, backlog) | 1354 | 1358 |
| 1355 | 1359 | options = options._handle || options.handle || options; | | 1360 | + const flags = getFlags(options.ipv6Only); | 1356 | 1361 | // (handle[, backlog][, cb]) where handle is an object with a handle | 1357 | 1362 | if (options instanceof TCP) { | 1358 | 1363 | this._handle = options; | @@ -1387,7 +1392,7 @@ Server.prototype.listen = function(...args) { | 1387 | 1392 | // start TCP server listening on host:port | 1388 | 1393 | if (options.host) { | 1389 | 1394 | lookupAndListen(this, options.port | 0, options.host, backlog, | 1390 | | - options.exclusive); | | 1395 | + options.exclusive, flags); | 1391 | 1396 | } else { // Undefined host, listens on unspecified address | 1392 | 1397 | // Default addressType 4 will be used to search for master server | 1393 | 1398 | listenInCluster(this, null, options.port | 0, 4, | @@ -1434,15 +1439,15 @@ Server.prototype.listen = function(...args) { | 1434 | 1439 | throw new ERR_INVALID_OPT_VALUE('options', util.inspect(options)); | 1435 | 1440 | }; | 1436 | 1441 |
| 1437 | | -function lookupAndListen(self, port, address, backlog, exclusive) { | | 1442 | +function lookupAndListen(self, port, address, backlog, exclusive, flags) { | 1438 | 1443 | if (dns === undefined) dns = require('dns'); | 1439 | 1444 | dns.lookup(address, function doListen(err, ip, addressType) { | 1440 | 1445 | if (err) { | 1441 | 1446 | self.emit('error', err); | 1442 | 1447 | } else { | 1443 | 1448 | addressType = ip ? addressType : 4; | 1444 | 1449 | listenInCluster(self, ip, port, addressType, | 1445 | | - backlog, undefined, exclusive); | | 1450 | + backlog, undefined, exclusive, flags); | 1446 | 1451 | } | 1447 | 1452 | }); | 1448 | 1453 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/tcp_wrap.cc +4-1Lines changed: 4 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -127,6 +127,7 @@ void TCPWrap::Initialize(Local<Object> target, | 127 | 127 | Local<Object> constants = Object::New(env->isolate()); | 128 | 128 | NODE_DEFINE_CONSTANT(constants, SOCKET); | 129 | 129 | NODE_DEFINE_CONSTANT(constants, SERVER); | | 130 | + NODE_DEFINE_CONSTANT(constants, UV_TCP_IPV6ONLY); | 130 | 131 | target->Set(context, | 131 | 132 | env->constants_string(), | 132 | 133 | constants).FromJust(); | @@ -252,13 +253,15 @@ void TCPWrap::Bind6(const FunctionCallbackInfo<Value>& args) { | 252 | 253 | Environment* env = wrap->env(); | 253 | 254 | node::Utf8Value ip6_address(env->isolate(), args[0]); | 254 | 255 | int port; | | 256 | + unsigned int flags; | 255 | 257 | if (!args[1]->Int32Value(env->context()).To(&port)) return; | | 258 | + if (!args[2]->Uint32Value(env->context()).To(&flags)) return; | 256 | 259 | sockaddr_in6 addr; | 257 | 260 | int err = uv_ip6_addr(*ip6_address, port, &addr); | 258 | 261 | if (err == 0) { | 259 | 262 | err = uv_tcp_bind(&wrap->handle_, | 260 | 263 | reinterpret_cast<const sockaddr*>(&addr), | 261 | | - 0); | | 264 | + flags); | 262 | 265 | } | 263 | 266 | args.GetReturnValue().Set(err); | 264 | 267 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/udp_wrap.cc +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -149,6 +149,12 @@ void UDPWrap::Initialize(Local<Object> target, | 149 | 149 | target->Set(env->context(), | 150 | 150 | sendWrapString, | 151 | 151 | swt->GetFunction(env->context()).ToLocalChecked()).FromJust(); | | 152 | + | | 153 | + Local<Object> constants = Object::New(env->isolate()); | | 154 | + NODE_DEFINE_CONSTANT(constants, UV_UDP_IPV6ONLY); | | 155 | + target->Set(context, | | 156 | + env->constants_string(), | | 157 | + constants).FromJust(); | 152 | 158 | } | 153 | 159 |
| 154 | 160 |
| |
Collapse file Copy file name to clipboard +51Lines changed: 51 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const common = require('../common'); | | 4 | +if (!common.hasIPv6) | | 5 | + common.skip('no IPv6 support'); | | 6 | +if (common.isWindows) | | 7 | + common.skip('dgram clustering is currently not supported on windows.'); | | 8 | + | | 9 | +const assert = require('assert'); | | 10 | +const cluster = require('cluster'); | | 11 | +const dgram = require('dgram'); | | 12 | + | | 13 | +// This test ensures that the `ipv6Only` option in `dgram.createSock()` | | 14 | +// works as expected. | | 15 | +if (cluster.isMaster) { | | 16 | + cluster.fork().on('exit', common.mustCall((code) => { | | 17 | + assert.strictEqual(code, 0); | | 18 | + })); | | 19 | +} else { | | 20 | + let waiting = 2; | | 21 | + function close() { | | 22 | + if (--waiting === 0) | | 23 | + cluster.worker.disconnect(); | | 24 | + } | | 25 | + | | 26 | + const socket1 = dgram.createSocket({ | | 27 | + type: 'udp6', | | 28 | + ipv6Only: true | | 29 | + }); | | 30 | + const socket2 = dgram.createSocket({ | | 31 | + type: 'udp4', | | 32 | + }); | | 33 | + socket1.on('error', common.mustNotCall()); | | 34 | + socket2.on('error', common.mustNotCall()); | | 35 | + | | 36 | + socket1.bind({ | | 37 | + port: 0, | | 38 | + address: '::', | | 39 | + }, common.mustCall(() => { | | 40 | + const { port } = socket1.address(); | | 41 | + socket2.bind({ | | 42 | + port, | | 43 | + address: '0.0.0.0', | | 44 | + }, common.mustCall(() => { | | 45 | + process.nextTick(() => { | | 46 | + socket1.close(close); | | 47 | + socket2.close(close); | | 48 | + }); | | 49 | + })); | | 50 | + })); | | 51 | +} |
|