bug 347307 - make pac myIPAddress() more accurate r=biesi

This commit is contained in:
Patrick McManus 2012-09-13 15:22:56 -04:00
parent 055e38ba48
commit 03e8a539c1
3 changed files with 251 additions and 31 deletions

View File

@ -5,7 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ProxyAutoConfig.h"
#include "jsapi.h"
#include "nsICancelable.h"
#include "nsIDNSListener.h"
#include "nsIDNSRecord.h"
@ -14,6 +13,8 @@
#include "nsThreadUtils.h"
#include "nsIConsoleService.h"
#include "nsJSUtils.h"
#include "prnetdb.h"
#include "nsITimer.h"
namespace mozilla {
namespace net {
@ -238,8 +239,14 @@ static const char *sPacUtils =
"}\n"
"";
// sRunning is defined for the helper functions only while the
// Javascript engine is running and the PAC object cannot be deleted
// or reset.
static ProxyAutoConfig *sRunning = nullptr;
// The PACResolver is used for dnsResolve()
class PACResolver MOZ_FINAL : public nsIDNSListener
, public nsITimerCallback
{
public:
NS_DECL_ISUPPORTS
@ -249,21 +256,37 @@ public:
{
}
// nsIDNSListener
NS_IMETHODIMP OnLookupComplete(nsICancelable *request,
nsIDNSRecord *record,
nsresult status)
{
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
mRequest = nullptr;
mStatus = status;
mResponse = record;
return NS_OK;
}
// nsITimerCallback
NS_IMETHODIMP Notify(nsITimer *timer)
{
if (mRequest)
mRequest->Cancel(NS_ERROR_NET_TIMEOUT);
mTimer = nullptr;
return NS_OK;
}
nsresult mStatus;
nsCOMPtr<nsICancelable> mRequest;
nsCOMPtr<nsIDNSRecord> mResponse;
nsCOMPtr<nsITimer> mTimer;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(PACResolver, nsIDNSListener)
NS_IMPL_THREADSAFE_ISUPPORTS2(PACResolver, nsIDNSListener, nsITimerCallback)
static
void PACLogToConsole(nsString &aMessage)
@ -288,16 +311,45 @@ PACErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
PACLogToConsole(formattedMessage);
}
// timeout of 0 means the normal necko timeout strategy, otherwise the dns request
// will be canceled after aTimeout milliseconds
static
JSBool PACResolve(const nsCString &aHostName, nsCString &aDottedDecimal)
JSBool PACResolve(const nsCString &aHostName, PRNetAddr *aNetAddr,
unsigned int aTimeout)
{
if (!sRunning) {
NS_WARNING("PACResolve without a running ProxyAutoConfig object");
return false;
}
return sRunning->ResolveAddress(aHostName, aNetAddr, aTimeout);
}
bool
ProxyAutoConfig::ResolveAddress(const nsCString &aHostName,
PRNetAddr *aNetAddr,
unsigned int aTimeout)
{
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
nsCOMPtr<PACResolver> helper = new PACResolver();
if (!dns || NS_FAILED(dns->AsyncResolve(aHostName, 0, helper,
NS_GetCurrentThread(),
getter_AddRefs(helper->mRequest))))
if (!dns)
return false;
nsRefPtr<PACResolver> helper = new PACResolver();
if (NS_FAILED(dns->AsyncResolve(aHostName, 0, helper,
NS_GetCurrentThread(),
getter_AddRefs(helper->mRequest))))
return false;
if (aTimeout && helper->mRequest) {
if (!mTimer)
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (mTimer) {
mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
helper->mTimer = mTimer;
}
}
// Spin the event loop of the pac thread until lookup is complete.
// nsPACman is responsible for keeping a queue and only allowing
// one PAC execution at a time even when it is called re-entrantly.
@ -305,11 +357,28 @@ JSBool PACResolve(const nsCString &aHostName, nsCString &aDottedDecimal)
NS_ProcessNextEvent(NS_GetCurrentThread());
if (NS_FAILED(helper->mStatus) ||
NS_FAILED(helper->mResponse->GetNextAddrAsString(aDottedDecimal)))
NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr)))
return false;
return true;
}
static
bool PACResolveToString(const nsCString &aHostName,
nsCString &aDottedDecimal,
unsigned int aTimeout)
{
PRNetAddr netAddr;
if (!PACResolve(aHostName, &netAddr, aTimeout))
return false;
char dottedDecimal[128];
if (PR_NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS)
return false;
aDottedDecimal.Assign(dottedDecimal);
return true;
}
// dnsResolve(host) javascript implementation
static
JSBool PACDnsResolve(JSContext *cx, unsigned int argc, jsval *vp)
@ -324,11 +393,11 @@ JSBool PACDnsResolve(JSContext *cx, unsigned int argc, jsval *vp)
return false;
nsDependentJSString hostName;
nsCString dottedDecimal;
nsAutoCString dottedDecimal;
if (!hostName.init(cx, arg1))
return false;
if (!PACResolve(NS_ConvertUTF16toUTF8(hostName), dottedDecimal))
if (!PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0))
return false;
JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
@ -345,21 +414,12 @@ JSBool PACMyIpAddress(JSContext *cx, unsigned int argc, jsval *vp)
return false;
}
nsCString hostName;
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
if (!dns || NS_FAILED(dns->GetMyHostName(hostName))) {
hostName.AssignLiteral("127.0.0.1");
if (!sRunning) {
NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
return JS_FALSE;
}
nsCString dottedDecimal;
if (!PACResolve(hostName, dottedDecimal)) {
dottedDecimal.AssignLiteral("127.0.0.1");
}
JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(dottedDecimalString));
return true;
return sRunning->MyIPAddress(vp);
}
// proxyAlert(msg) javascript implementation
@ -498,7 +558,7 @@ ProxyAutoConfig::Init(const nsCString &aPACURI,
mPACScript = sPacUtils;
mPACScript.Append(aPACScript);
if (!mRunning)
if (!sRunning)
return SetupJS();
mJSNeedsSetup = true;
@ -509,7 +569,7 @@ nsresult
ProxyAutoConfig::SetupJS()
{
mJSNeedsSetup = false;
NS_ABORT_IF_FALSE(!mRunning, "JIT is running");
NS_ABORT_IF_FALSE(!sRunning, "JIT is running");
delete mJSRuntime;
mJSRuntime = nullptr;
@ -560,9 +620,10 @@ ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI,
JSContext *cx = mJSRuntime->Context();
JSAutoRequest ar(cx);
// the mRunning flag keeps a new PAC file from being installed
// the sRunning flag keeps a new PAC file from being installed
// while the event loop is spinning on a DNS function. Don't early return.
mRunning = true;
sRunning = this;
mRunningHost = aTestHost;
nsresult rv = NS_ERROR_FAILURE;
JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, aTestURI.get()));
@ -585,7 +646,9 @@ ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI,
}
}
}
mRunning = false;
mRunningHost.Truncate();
sRunning = nullptr;
return rv;
}
@ -611,7 +674,7 @@ ProxyAutoConfig::Shutdown()
{
NS_ABORT_IF_FALSE(!NS_IsMainThread(), "wrong thread for shutdown");
if (mRunning || mShutdown)
if (sRunning || mShutdown)
return;
mShutdown = true;
@ -619,5 +682,109 @@ ProxyAutoConfig::Shutdown()
mJSRuntime = nullptr;
}
bool
ProxyAutoConfig::SrcAddress(const PRNetAddr *remoteAddress, nsCString &localAddress)
{
PRFileDesc *fd;
fd = PR_OpenUDPSocket(remoteAddress->raw.family);
if (!fd)
return false;
if (PR_Connect(fd, remoteAddress, 0) != PR_SUCCESS) {
PR_Close(fd);
return false;
}
PRNetAddr localName;
if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
PR_Close(fd);
return false;
}
PR_Close(fd);
char dottedDecimal[128];
if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS)
return false;
localAddress.Assign(dottedDecimal);
return true;
}
// hostName is run through a dns lookup and then a udp socket is connected
// to the result. If that all works, the local IP address of the socket is
// returned to the javascript caller and true is returned from this function.
// otherwise false is returned.
bool
ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName,
unsigned int timeout,
jsval *vp)
{
PRNetAddr remoteAddress;
nsAutoCString localDottedDecimal;
JSContext *cx = mJSRuntime->Context();
if (PACResolve(hostName, &remoteAddress, timeout) &&
SrcAddress(&remoteAddress, localDottedDecimal)) {
JSString *dottedDecimalString =
JS_NewStringCopyZ(cx, localDottedDecimal.get());
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(dottedDecimalString));
return true;
}
return false;
}
bool
ProxyAutoConfig::MyIPAddress(jsval *vp)
{
nsAutoCString remoteDottedDecimal;
nsAutoCString localDottedDecimal;
JSContext *cx = mJSRuntime->Context();
// first, lookup the local address of a socket connected
// to the host of uri being resolved by the pac file. This is
// v6 safe.. but is the last step like that
if (MyIPAddressTryHost(mRunningHost, kTimeout, vp))
return true;
// next, look for a route to a public internet address that doesn't need DNS.
// This is the google anycast dns address, but it doesn't matter if it
// remains operable (as we don't contact it) as long as the address stays
// in commonly routed IP address space.
remoteDottedDecimal.AssignLiteral("8.8.8.8");
if (MyIPAddressTryHost(remoteDottedDecimal, 0, vp))
return true;
// next, use the old algorithm based on the local hostname
nsAutoCString hostName;
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
PACResolveToString(hostName, localDottedDecimal, kTimeout)) {
JSString *dottedDecimalString =
JS_NewStringCopyZ(cx, localDottedDecimal.get());
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(dottedDecimalString));
return true;
}
// next try a couple RFC 1918 variants.. maybe there is a
// local route
remoteDottedDecimal.AssignLiteral("192.168.0.1");
if (MyIPAddressTryHost(remoteDottedDecimal, 0, vp))
return true;
// more RFC 1918
remoteDottedDecimal.AssignLiteral("10.0.0.1");
if (MyIPAddressTryHost(remoteDottedDecimal, 0, vp))
return true;
// who knows? let's fallback to localhost
localDottedDecimal.AssignLiteral("127.0.0.1");
JSString *dottedDecimalString =
JS_NewStringCopyZ(cx, localDottedDecimal.get());
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(dottedDecimalString));
return true;
}
} // namespace mozilla
} // namespace mozilla::net

View File

@ -8,6 +8,10 @@
#define ProxyAutoConfig_h__
#include "nsString.h"
#include "jsapi.h"
#include "prio.h"
#include "nsITimer.h"
#include "nsAutoPtr.h"
namespace mozilla { namespace net {
@ -21,7 +25,6 @@ class ProxyAutoConfig {
public:
ProxyAutoConfig()
: mJSRuntime(nullptr)
, mRunning(false)
, mJSNeedsSetup(false)
, mShutdown(false)
{
@ -33,6 +36,9 @@ public:
const nsCString &aPACScript);
void Shutdown();
void GC();
bool MyIPAddress(jsval *vp);
bool ResolveAddress(const nsCString &aHostName,
PRNetAddr *aNetAddr, unsigned int aTimeout);
/**
* Get the proxy string for the specified URI. The proxy string is
@ -73,15 +79,22 @@ public:
nsACString &result);
private:
const static unsigned int kTimeout = 1000; // ms to allow for myipaddress dns queries
// used to compile the PAC file and setup the execution context
nsresult SetupJS();
bool SrcAddress(const PRNetAddr *remoteAddress, nsCString &localAddress);
bool MyIPAddressTryHost(const nsCString &hostName, unsigned int timeout,
jsval *vp);
JSRuntimeWrapper *mJSRuntime;
bool mRunning;
bool mJSNeedsSetup;
bool mShutdown;
nsCString mPACScript;
nsCString mPACURI;
nsCString mRunningHost;
nsCOMPtr<nsITimer> mTimer;
};
}} // namespace mozilla::net

View File

@ -14,6 +14,7 @@
// run_pac_test();
// run_pac_cancel_test();
// run_proxy_host_filters_test();
// run_myipaddress_test();
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
@ -557,6 +558,45 @@ function host_filters_4()
prefs.setCharPref("network.proxy.no_proxies_on", "");
do_check_eq(prefs.getCharPref("network.proxy.no_proxies_on"), "");
run_myipaddress_test();
}
function run_myipaddress_test()
{
// This test makes sure myIpAddress() comes up with some valid
// IP address other than localhost. The DUT must be configured with
// an Internet route for this to work - though no Internet traffic
// should be created.
var pac = 'data:text/plain,' +
'function FindProxyForURL(url, host) {' +
' return "PROXY " + myIpAddress() + ":1234";' +
'}';
// no traffic to this IP is ever sent, it is just a public IP that
// does not require DNS to determine a route.
var uri = ios.newURI("http://192.0.43.10/", null, null);
prefs.setIntPref("network.proxy.type", 2);
prefs.setCharPref("network.proxy.autoconfig_url", pac);
var cb = new resolveCallback();
cb.nextFunction = myipaddress_callback;
var req = pps.asyncResolve(uri, 0, cb);
}
function myipaddress_callback(pi)
{
do_check_neq(pi, null);
do_check_eq(pi.type, "http");
do_check_eq(pi.port, 1234);
// make sure we didn't return localhost
do_check_neq(pi.host, null);
do_check_neq(pi.host, "127.0.0.1");
do_check_neq(pi.host, "::1");
prefs.setIntPref("network.proxy.type", 0);
do_test_finished();
}