diff --git a/lib/net.js b/lib/net.js index d2b510c64bbb..6c076f79881a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -63,6 +63,7 @@ const { const assert = require('internal/assert'); const { UV_EADDRINUSE, + UV_EAFNOSUPPORT, UV_EBADF, UV_EINVAL, UV_ENOTCONN, @@ -2264,6 +2265,19 @@ function createServerHandle(address, port, addressType, fd, flags) { } } else if (addressType === 6) { err = handle.bind6(address, port, flags); + // On IPv6-only systems, setting IPV6_V6ONLY to false can fail with + // EAFNOSUPPORT because the IPv4 address family is unavailable. In that + // case, retry with an IPv6-only socket before falling back to IPv4. + if (err === UV_EAFNOSUPPORT && + !(flags & TCPConstants.UV_TCP_IPV6ONLY)) { + handle.close(); + handle = new TCP(TCPConstants.SERVER); + err = handle.bind6( + address, + port, + flags | TCPConstants.UV_TCP_IPV6ONLY, + ); + } } else { err = handle.bind(address, port, flags); } diff --git a/test/sequential/test-net-server-listen-ipv6-only-system.js b/test/sequential/test-net-server-listen-ipv6-only-system.js new file mode 100644 index 000000000000..117d8d96067a --- /dev/null +++ b/test/sequential/test-net-server-listen-ipv6-only-system.js @@ -0,0 +1,38 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const { internalBinding } = require('internal/test/binding'); +const { TCP, constants: TCPConstants } = internalBinding('tcp_wrap'); +const { UV_EAFNOSUPPORT } = internalBinding('uv'); + +const probe = new TCP(TCPConstants.SOCKET); +if (probe.bind6('::1', 0, TCPConstants.UV_TCP_IPV6ONLY) !== 0) { + probe.close(); + common.skip('no IPv6 support'); +} +probe.close(); + +const bind6 = TCP.prototype.bind6; +let firstBind = true; + +TCP.prototype.bind6 = function(...args) { + if (firstBind && args[2] === 0) { + firstBind = false; + return UV_EAFNOSUPPORT; + } + return bind6.apply(this, args); +}; + +const server = net.createServer(); +server.on('error', common.mustNotCall()); + +server.listen({ port: 0 }, common.mustCall(() => { + assert.strictEqual(server.address().family, 'IPv6'); + + server.close(common.mustCall(() => { + TCP.prototype.bind6 = bind6; + })); +}));