Bug 435028 - IPv6 sites not reachable when using IPv4 SOCKS proxy. r=biesi

This commit is contained in:
Masatoshi Kimura 2012-07-20 20:19:37 -04:00
parent 30fbf181d9
commit 5fd3e2bc43
3 changed files with 127 additions and 14 deletions

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nspr.h"
#include "private/pprio.h"
#include "nsString.h"
#include "nsCRT.h"
@ -22,6 +23,7 @@
static PRDescIdentity nsSOCKSIOLayerIdentity;
static PRIOMethods nsSOCKSIOLayerMethods;
static bool firstTime = true;
static bool ipv6Supported = true;
#if defined(PR_LOGGING)
static PRLogModuleInfo *gSOCKSLog;
@ -66,6 +68,7 @@ public:
NS_DECL_NSIDNSLISTENER
void Init(PRInt32 version,
PRInt32 family,
const char *proxyHost,
PRInt32 proxyPort,
const char *destinationHost,
@ -80,6 +83,7 @@ private:
void HandshakeFinished(PRErrorCode err = 0);
PRStatus StartDNS(PRFileDesc *fd);
PRStatus ConnectToProxy(PRFileDesc *fd);
void FixupAddressFamily(PRFileDesc *fd, PRNetAddr *proxy);
PRStatus ContinueConnectingToProxy(PRFileDesc *fd, PRInt16 oflags);
PRStatus WriteV4ConnectRequest();
PRStatus ReadV4ConnectResponse();
@ -123,6 +127,7 @@ private:
nsCString mProxyHost;
PRInt32 mProxyPort;
PRInt32 mVersion; // SOCKS version 4 or 5
PRInt32 mDestinationFamily;
PRUint32 mFlags;
PRNetAddr mInternalProxyAddr;
PRNetAddr mExternalProxyAddr;
@ -138,6 +143,7 @@ nsSOCKSSocketInfo::nsSOCKSSocketInfo()
, mAmountToRead(0)
, mProxyPort(-1)
, mVersion(-1)
, mDestinationFamily(PR_AF_INET)
, mFlags(0)
, mTimeout(PR_INTERVAL_NO_TIMEOUT)
{
@ -148,9 +154,10 @@ nsSOCKSSocketInfo::nsSOCKSSocketInfo()
}
void
nsSOCKSSocketInfo::Init(PRInt32 version, const char *proxyHost, PRInt32 proxyPort, const char *host, PRUint32 flags)
nsSOCKSSocketInfo::Init(PRInt32 version, PRInt32 family, const char *proxyHost, PRInt32 proxyPort, const char *host, PRUint32 flags)
{
mVersion = version;
mDestinationFamily = family;
mProxyHost = proxyHost;
mProxyPort = proxyPort;
mDestinationHost = host;
@ -285,6 +292,12 @@ nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc *fd)
return PR_FAILURE;
}
// Try socks5 if the destination addrress is IPv6
if (mVersion == 4 &&
PR_NetAddrFamily(&mDestinationAddr) == PR_AF_INET6) {
mVersion = 5;
}
PRInt32 addresses = 0;
do {
if (addresses++)
@ -304,8 +317,9 @@ nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc *fd)
LOGDEBUG(("socks: trying proxy server, %s:%hu",
buf, PR_ntohs(PR_NetAddrInetPort(&mInternalProxyAddr))));
#endif
status = fd->lower->methods->connect(fd->lower,
&mInternalProxyAddr, mTimeout);
PRNetAddr proxy = mInternalProxyAddr;
FixupAddressFamily(fd, &proxy);
status = fd->lower->methods->connect(fd->lower, &proxy, mTimeout);
if (status != PR_SUCCESS) {
PRErrorCode c = PR_GetError();
// If EINPROGRESS, return now and check back later after polling
@ -322,6 +336,59 @@ nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc *fd)
return WriteV5AuthRequest();
}
void
nsSOCKSSocketInfo::FixupAddressFamily(PRFileDesc *fd, PRNetAddr *proxy)
{
PRInt32 proxyFamily = PR_NetAddrFamily(&mInternalProxyAddr);
// Do nothing if the address family is already matched
if (proxyFamily == mDestinationFamily) {
return;
}
// If the system does not support IPv6 and the proxy address is IPv6,
// We can do nothing here.
if (proxyFamily == PR_AF_INET6 && !ipv6Supported) {
return;
}
// If the system does not support IPv6 and the destination address is
// IPv6, convert IPv4 address to IPv4-mapped IPv6 address to satisfy
// the emulation layer
if (mDestinationFamily == PR_AF_INET6 && !ipv6Supported) {
proxy->ipv6.family = PR_AF_INET6;
proxy->ipv6.port = mInternalProxyAddr.inet.port;
PRUint8 *proxyp = proxy->ipv6.ip.pr_s6_addr;
memset(proxyp, 0, 10);
memset(proxyp + 10, 0xff, 2);
memcpy(proxyp + 12,(char *) &mInternalProxyAddr.inet.ip, 4);
// mDestinationFamily should not be updated
return;
}
// Get an OS native handle from a specified FileDesc
PROsfd osfd = PR_FileDesc2NativeHandle(fd);
if (osfd == -1) {
return;
}
// Create a new FileDesc with a specified family
PRFileDesc *tmpfd = PR_OpenTCPSocket(proxyFamily);
if (!tmpfd) {
return;
}
PROsfd newsd = PR_FileDesc2NativeHandle(tmpfd);
if (newsd == -1) {
PR_Close(tmpfd);
return;
}
// Must succeed because PR_FileDesc2NativeHandle succeeded
fd = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
MOZ_ASSERT(fd);
// Swap OS native handles
PR_ChangeFileDescNativeHandle(fd, newsd);
PR_ChangeFileDescNativeHandle(tmpfd, osfd);
// Close temporary FileDesc which is now associated with
// old OS native handle
PR_Close(tmpfd);
mDestinationFamily = proxyFamily;
}
PRStatus
nsSOCKSSocketInfo::ContinueConnectingToProxy(PRFileDesc *fd, PRInt16 oflags)
{
@ -1136,6 +1203,18 @@ nsSOCKSIOLayerAddToSocket(PRInt32 family,
if (firstTime)
{
//XXX hack until NSPR provides an official way to detect system IPv6
// support (bug 388519)
PRFileDesc *tmpfd = PR_OpenTCPSocket(PR_AF_INET6);
if (!tmpfd) {
ipv6Supported = false;
} else {
// If the system does not support IPv6, NSPR will push
// IPv6-to-IPv4 emulation layer onto the native layer
ipv6Supported = PR_GetIdentitiesLayer(tmpfd, PR_NSPR_IO_LAYER) == tmpfd;
PR_Close(tmpfd);
}
nsSOCKSIOLayerIdentity = PR_GetUniqueIdentity("SOCKS layer");
nsSOCKSIOLayerMethods = *PR_GetDefaultIOMethods();
@ -1180,7 +1259,7 @@ nsSOCKSIOLayerAddToSocket(PRInt32 family,
}
NS_ADDREF(infoObject);
infoObject->Init(socksVersion, proxyHost, proxyPort, host, flags);
infoObject->Init(socksVersion, family, proxyHost, proxyPort, host, flags);
layer->secret = (PRFilePrivate*) infoObject;
rv = PR_PushIOLayer(fd, PR_GetLayersIdentity(fd), layer);

View File

@ -38,7 +38,7 @@ function launchConnection(socks_vers, socks_port, dest_host, dest_port, dns)
for each (var arg in arguments) {
print('client: running test', arg);
test = arg.split(':');
test = arg.split('|');
launchConnection(test[0], parseInt(test[1]), test[2],
parseInt(test[3]), test[4]);
}

View File

@ -57,8 +57,26 @@ function runScriptSubprocess(script, args)
function buf2ip(buf)
{
// XXX this doesn't work with IPv6
return buf.join('.');
if (buf.length == 16) {
var ip = (buf[0] << 4 | buf[1]).toString(16) + ':' +
(buf[2] << 4 | buf[3]).toString(16) + ':' +
(buf[4] << 4 | buf[5]).toString(16) + ':' +
(buf[6] << 4 | buf[7]).toString(16) + ':' +
(buf[8] << 4 | buf[9]).toString(16) + ':' +
(buf[10] << 4 | buf[11]).toString(16) + ':' +
(buf[12] << 4 | buf[13]).toString(16) + ':' +
(buf[14] << 4 | buf[15]).toString(16);
for (var i = 8; i >= 2; i--) {
var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
var shortip = ip.replace(re, '::');
if (shortip != ip) {
return shortip;
}
}
return ip;
} else {
return buf.join('.');
}
}
function buf2int(buf)
@ -325,8 +343,13 @@ SocksClient.prototype = {
sendSocks5Response: function()
{
// send a successful response with the address, 127.0.0.1:80
this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
if (this.dest_addr.length == 16) {
// send a successful response with the address, [::1]:80
this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80';
} else {
// send a successful response with the address, 127.0.0.1:80
this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
}
this.sendPing();
},
@ -395,7 +418,7 @@ SocksTestServer.prototype = {
print('server: test finished', test.port);
do_check_true(test != null);
do_check_eq(test.type, client.type);
do_check_eq(test.expectedType || test.type, client.type);
do_check_eq(test.port, port_id);
if (test.remote_dns)
@ -414,11 +437,11 @@ SocksTestServer.prototype = {
{
var argv = [];
// marshaled: socks_ver:server_port:dest_host:dest_port:remote|local
// marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
for each (var test in this.test_cases) {
var arg = test.type + ':' +
String(socks_listen_port) + ':' +
test.host + ':' + test.port + ':';
var arg = test.type + '|' +
String(socks_listen_port) + '|' +
test.host + '|' + test.port + '|';
if (test.remote_dns)
arg += 'remote';
else
@ -484,6 +507,12 @@ function run_test()
host: '12345.xxx',
remote_dns: true,
});
socks_test_server.addTestCase({
type: "socks4",
expectedType: "socks",
host: '::1',
remote_dns: false,
});
socks_test_server.addTestCase({
type: "socks",
host: '127.0.0.1',
@ -494,6 +523,11 @@ function run_test()
host: 'abcdefg.xxx',
remote_dns: true,
});
socks_test_server.addTestCase({
type: "socks",
host: '::1',
remote_dns: false,
});
socks_test_server.runClientSubprocess();
do_timeout(120 * 1000, test_timeout);