Bug 1770019 - Make httpd.js support both IPv4 and IPv6, r=necko-reviewers,dragana

Differential Revision: https://phabricator.services.mozilla.com/D148696
This commit is contained in:
Kershaw Chang 2022-07-04 11:04:02 +00:00
parent 5f8570e826
commit 5e9a1264ff
6 changed files with 106 additions and 4 deletions

View File

@ -66,6 +66,13 @@ interface nsIServerSocket : nsISupports
in boolean aLoopbackOnly,
in long aBackLog);
/**
* Similar to init(), but initializes a server socket that supports
* both IPv4 and IPv6.
*/
void initDualStack(in long aPort,
in long aBackLog);
/**
* initSpecialConnection
*

View File

@ -14,8 +14,19 @@
#include "mozilla/Attributes.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/net/DNS.h"
#include "mozilla/Unused.h"
#include "nsServiceManagerUtils.h"
#include "nsIFile.h"
#if defined(XP_WIN)
# include "private/pprio.h"
# include <Winsock2.h>
# include <mstcpip.h>
# ifndef IPV6_V6ONLY
# define IPV6_V6ONLY 27
# endif
#endif
namespace mozilla {
namespace net {
@ -259,6 +270,16 @@ nsServerSocket::InitIPv6(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) {
return InitWithAddress(&addr, aBackLog);
}
NS_IMETHODIMP
nsServerSocket::InitDualStack(int32_t aPort, int32_t aBackLog) {
if (aPort < 0) {
aPort = 0;
}
PRNetAddr addr;
PR_SetNetAddr(PR_IpAddrAny, PR_AF_INET6, aPort, &addr);
return InitWithAddressInternal(&addr, aBackLog, true);
}
NS_IMETHODIMP
nsServerSocket::InitWithFilename(nsIFile* aPath, uint32_t aPermissions,
int32_t aBacklog) {
@ -329,6 +350,12 @@ nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags,
NS_IMETHODIMP
nsServerSocket::InitWithAddress(const PRNetAddr* aAddr, int32_t aBackLog) {
return InitWithAddressInternal(aAddr, aBackLog);
}
nsresult nsServerSocket::InitWithAddressInternal(const PRNetAddr* aAddr,
int32_t aBackLog,
bool aDualStack) {
NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
@ -342,6 +369,21 @@ nsServerSocket::InitWithAddress(const PRNetAddr* aAddr, int32_t aBackLog) {
return ErrorAccordingToNSPR(PR_GetError());
}
#if defined(XP_WIN)
// https://docs.microsoft.com/en-us/windows/win32/winsock/dual-stack-sockets
// To create a Dual-Stack Socket, we have to disable IPV6_V6ONLY.
if (aDualStack) {
PROsfd osfd = PR_FileDesc2NativeHandle(mFD);
if (osfd != -1) {
int disable = 0;
setsockopt(osfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&disable,
sizeof(disable));
}
}
#else
mozilla::Unused << aDualStack;
#endif
PR_SetFDInheritable(mFD, false);
PRSocketOptionData opt;

View File

@ -50,6 +50,9 @@ class nsServerSocket : public nsASocketHandler, public nsIServerSocket {
// try attaching our socket (mFD) to the STS's poll list.
nsresult TryAttach();
nsresult InitWithAddressInternal(const PRNetAddr* aAddr, int32_t aBackLog,
bool aDualStack = false);
// lock protects access to mListener; so it is not cleared while being used.
mozilla::Mutex mLock MOZ_UNANNOTATED{"nsServerSocket.mLock"};
PRNetAddr mAddr = {.raw = {0, {0}}};

View File

@ -213,6 +213,11 @@ const ServerSocketIPv6 = CC(
"nsIServerSocket",
"initIPv6"
);
const ServerSocketDualStack = CC(
"@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"initDualStack"
);
const ScriptableInputStream = CC(
"@mozilla.org/scriptableinputstream;1",
"nsIScriptableInputStream",
@ -523,7 +528,11 @@ nsHttpServer.prototype = {
this._start(port, "[::1]");
},
_start(port, host) {
start_dualStack(port) {
this._start(port, "[::1]", true);
},
_start(port, host, dualStack) {
if (this._socket) {
throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED);
}
@ -567,7 +576,9 @@ nsHttpServer.prototype = {
var socket;
for (var i = 100; i; i--) {
var temp = null;
if (this._host.includes(":")) {
if (dualStack) {
temp = new ServerSocketDualStack(this._port, maxConnections);
} else if (this._host.includes(":")) {
temp = new ServerSocketIPv6(
this._port,
loopback, // true = localhost, false = everybody
@ -608,7 +619,7 @@ nsHttpServer.prototype = {
socket.asyncListen(this);
this._port = socket.port;
this._identity._initialize(socket.port, host, true);
this._identity._initialize(socket.port, host, true, dualStack);
this._socket = socket;
dumpn(
">>> listening on port " +
@ -1170,7 +1181,7 @@ ServerIdentity.prototype = {
* Initializes the primary name for the corresponding server, based on the
* provided port number.
*/
_initialize(port, host, addSecondaryDefault) {
_initialize(port, host, addSecondaryDefault, dualStack) {
this._host = host;
if (this._primaryPort !== -1) {
this.add("http", host, port);
@ -1183,6 +1194,9 @@ ServerIdentity.prototype = {
if (addSecondaryDefault && host != "127.0.0.1") {
if (host.includes(":")) {
this.add("http", "[::1]", port);
if (dualStack) {
this.add("http", "127.0.0.1", port);
}
} else {
this.add("http", "127.0.0.1", port);
}

View File

@ -60,6 +60,12 @@ interface nsIHttpServer : nsISupports
*/
void start_ipv6(in long port);
/**
* Like the two functions above, but this server supports both IPv6 and
* IPv4 addresses.
*/
void start_dualStack(in long port);
/**
* Shuts down this server if it is running (including the period of time after
* stop() has been called but before the provided callback has been called).

View File

@ -9,6 +9,8 @@
/* import-globals-from head_channels.js */
/* import-globals-from head_servers.js */
const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
function makeChan(uri) {
let chan = NetUtil.newChannel({
uri,
@ -18,6 +20,34 @@ function makeChan(uri) {
return chan;
}
function channelOpenPromise(chan, flags, observer) {
return new Promise(resolve => {
function finish(req, buffer) {
resolve([req, buffer]);
}
chan.asyncOpen(new ChannelListener(finish, null, flags));
});
}
add_task(async function test_dual_stack() {
let httpserv = new HttpServer();
let content = "ok";
httpserv.registerPathHandler("/", function handler(metadata, response) {
response.setHeader("Content-Length", `${content.length}`);
response.bodyOutputStream.write(content, content.length);
});
httpserv.start_dualStack(-1);
let chan = makeChan(`http://127.0.0.1:${httpserv.identity.primaryPort}/`);
let [, response] = await channelOpenPromise(chan);
Assert.equal(response, content);
chan = makeChan(`http://[::1]:${httpserv.identity.primaryPort}/`);
[, response] = await channelOpenPromise(chan);
Assert.equal(response, content);
await new Promise(resolve => httpserv.stop(resolve));
});
add_task(async function test_http() {
let server = new NodeHTTPServer();
await server.start();