bug 347307 - make pac myIPAddress() more accurate r=biesi
☠☠ backed out by e59b9b887c25 ☠ ☠
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 13 Sep 2012 15:22:56 -0400
changeset 106999 3182f9d08c2dc22f4234616169366a887b800cf3
parent 106998 2a30593cca79a4d74f9c385d962ff11ded35ce0c
child 107000 08dc8d488f5dd79f5200f6b18acfb672e82c42e9
push id14810
push usermcmanus@ducksong.com
push dateThu, 13 Sep 2012 19:26:11 +0000
treeherdermozilla-inbound@3182f9d08c2d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbiesi
bugs347307
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 347307 - make pac myIPAddress() more accurate r=biesi
netwerk/base/src/ProxyAutoConfig.cpp
netwerk/base/src/ProxyAutoConfig.h
netwerk/test/unit/test_protocolproxyservice.js
--- a/netwerk/base/src/ProxyAutoConfig.cpp
+++ b/netwerk/base/src/ProxyAutoConfig.cpp
@@ -1,24 +1,25 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * 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"
 #include "nsIDNSService.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "nsIConsoleService.h"
 #include "nsJSUtils.h"
+#include "prnetdb.h"
+#include "nsITimer.h"
 
 namespace mozilla {
 namespace net {
 
 // These are some global helper symbols the PAC format requires that we provide that
 // are initialized as part of the global javascript context used for PAC evaluations.
 // Additionally dnsResolve(host) and myIpAddress() are supplied in the same context
 // but are implemented as c++ helpers. proxyAlert(msg) is similarly defined, but that
@@ -233,42 +234,64 @@ static const char *sPacUtils =
   "        date.setHours(date.getUTCHours());\n"
   "        date.setMinutes(date.getUTCMinutes());\n"
   "        date.setSeconds(date.getUTCSeconds());\n"
   "    }\n"
   "    return ((date1 <= date) && (date <= date2));\n"
   "}\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
 
   PACResolver()
     : mStatus(NS_ERROR_FAILURE)
   {
   }
 
+  // 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)
 {
   nsCOMPtr<nsIConsoleService> consoleService =
     do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   if (!consoleService)
     return;
@@ -283,88 +306,125 @@ PACErrorReporter(JSContext *cx, const ch
   nsString formattedMessage(NS_LITERAL_STRING("PAC Execution Error: "));
   formattedMessage += report->ucmessage;
   formattedMessage += NS_LITERAL_STRING(" [");
   formattedMessage += report->uclinebuf;
   formattedMessage += NS_LITERAL_STRING("]");
   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.
   while (helper->mRequest)
     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)
 {
   if (NS_IsMainThread()) {
     NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
     return false;
   }
 
   JSString *arg1 = nullptr;
   if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "S", &arg1))
     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());
   JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(dottedDecimalString));
   return true;
 }
 
 // myIpAddress() javascript implementation
 static
 JSBool PACMyIpAddress(JSContext *cx, unsigned int argc, jsval *vp)
 {
   if (NS_IsMainThread()) {
     NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
     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
 static
 JSBool PACProxyAlert(JSContext *cx, unsigned int argc, jsval *vp)
 {
   JSString *arg1 = nullptr;
   if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "S", &arg1))
@@ -493,28 +553,28 @@ JSClass JSRuntimeWrapper::sGlobalClass =
 nsresult
 ProxyAutoConfig::Init(const nsCString &aPACURI,
                       const nsCString &aPACScript)
 {
   mPACURI = aPACURI;
   mPACScript = sPacUtils;
   mPACScript.Append(aPACScript);
 
-  if (!mRunning)
+  if (!sRunning)
     return SetupJS();
 
   mJSNeedsSetup = true;
   return NS_OK;
 }
 
 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;
 
   if (mPACScript.IsEmpty())
     return NS_ERROR_FAILURE;
 
   mJSRuntime = JSRuntimeWrapper::Create();
@@ -555,19 +615,20 @@ ProxyAutoConfig::GetProxyForURI(const ns
     SetupJS();
 
   if (!mJSRuntime || !mJSRuntime->IsOK())
     return NS_ERROR_NOT_AVAILABLE;
 
   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()));
   JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get()));
 
   if (uriString && hostString) {
     JS::RootedValue uriValue(cx, STRING_TO_JSVAL(uriString));
     JS::RootedValue hostValue(cx, STRING_TO_JSVAL(hostString));
@@ -580,17 +641,19 @@ ProxyAutoConfig::GetProxyForURI(const ns
     if (ok && rval.isString()) {
       nsDependentJSString pacString;
       if (pacString.init(cx, rval.toString())) {
         CopyUTF16toUTF8(pacString, result);
         rv = NS_OK;
       }
     }
   }
-  mRunning = false;
+
+  mRunningHost.Truncate();
+  sRunning = nullptr;
   return rv;
 }
 
 void
 ProxyAutoConfig::GC()
 {
   if (!mJSRuntime || !mJSRuntime->IsOK())
     return;
@@ -606,18 +669,122 @@ ProxyAutoConfig::~ProxyAutoConfig()
                "should have been deleted on pac thread");
 }
 
 void
 ProxyAutoConfig::Shutdown()
 {
   NS_ABORT_IF_FALSE(!NS_IsMainThread(), "wrong thread for shutdown");
 
-  if (mRunning || mShutdown)
+  if (sRunning || mShutdown)
     return;
 
   mShutdown = true;
   delete mJSRuntime;
   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
--- a/netwerk/base/src/ProxyAutoConfig.h
+++ b/netwerk/base/src/ProxyAutoConfig.h
@@ -3,41 +3,47 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ProxyAutoConfig_h__
 #define ProxyAutoConfig_h__
 
 #include "nsString.h"
+#include "jsapi.h"
+#include "prio.h"
+#include "nsITimer.h"
+#include "nsAutoPtr.h"
 
 namespace mozilla { namespace net {
 
 class JSRuntimeWrapper;
 
 // The ProxyAutoConfig class is meant to be created and run on a
 // non main thread. It synchronously resolves PAC files by blocking that
 // thread and running nested event loops. GetProxyForURI is not re-entrant.
 
 class ProxyAutoConfig  {
 public:
   ProxyAutoConfig()
     : mJSRuntime(nullptr)
-    , mRunning(false)
     , mJSNeedsSetup(false)
     , mShutdown(false)
   {
     MOZ_COUNT_CTOR(ProxyAutoConfig);
   }
   ~ProxyAutoConfig();
 
   nsresult Init(const nsCString &aPACURI,
                 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
    * given by the following:
    *
    *   result      = proxy-spec *( proxy-sep proxy-spec )
    *   proxy-spec  = direct-type | proxy-type LWS proxy-host [":" proxy-port]
    *   direct-type = "DIRECT"
@@ -68,22 +74,29 @@ public:
    * @param result
    *        result string as defined above.
    */
   nsresult GetProxyForURI(const nsCString &aTestURI,
                           const nsCString &aTestHost,
                           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
 
 #endif  // ProxyAutoConfig_h__
--- a/netwerk/test/unit/test_protocolproxyservice.js
+++ b/netwerk/test/unit/test_protocolproxyservice.js
@@ -9,16 +9,17 @@
 // These are the major sub tests:
 // run_filter_test();
 // run_filter_test2()
 // run_filter_test3()
 // run_pref_test();
 // 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);
 var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
                     .getService();
 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                      .getService(Components.interfaces.nsIPrefBranch);
 
@@ -552,16 +553,55 @@ function host_filters_3()
 }
 
 function host_filters_4()
 {
   // Cleanup
   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();
 }
 
 function run_deprecated_sync_test()
 {
   var uri = ios.newURI("http://www.mozilla.org/", null, null);
 
   pps.QueryInterface(Components.interfaces.nsIProtocolProxyService2);