bug 621558 - Disable IPv6 for the 300ms-delayed backup connection to improve
authorChristian Biesinger <cbiesinger@gmail.com>
Mon, 04 Jul 2011 11:47:09 +0200
changeset 72963 06815ff84678e97420eca4890f28fe517396a548
parent 72962 df4ec91d0b0aea3801cc08b088f264f4b30f2c84
child 72977 fb1291a31b54914cd97cc5a8f9e8413c4d5fa921
push id45
push userffxbld
push dateThu, 22 Sep 2011 17:29:26 +0000
treeherdermozilla-release@b3273da80b44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs621558
milestone7.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 621558 - Disable IPv6 for the 300ms-delayed backup connection to improve the user experience of people with broken IPv6 connectivity (i.e. implement Chrome's "Happy Eyeballs" strategy) r=mcmanus
netwerk/base/public/nsISocketTransport.idl
netwerk/base/src/nsSocketTransport2.cpp
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsDNSService2.h
netwerk/dns/nsIDNSService.idl
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
--- a/netwerk/base/public/nsISocketTransport.idl
+++ b/netwerk/base/public/nsISocketTransport.idl
@@ -133,17 +133,17 @@ interface nsISocketTransport : nsITransp
     const unsigned long STATUS_CONNECTING_TO  = 0x804b0007;
     const unsigned long STATUS_CONNECTED_TO   = 0x804b0004;
     const unsigned long STATUS_SENDING_TO     = 0x804b0005;
     const unsigned long STATUS_WAITING_FOR    = 0x804b000a;
     const unsigned long STATUS_RECEIVING_FROM = 0x804b0006;
 
     /**
      * connectionFlags is a bitmask that can be used to modify underlying 
-     * behavior of the socket connection.
+     * behavior of the socket connection. See the flags below.
      */
     attribute unsigned long connectionFlags;
 
     /**
      * Values for the connectionFlags
      *
      * When making a new connection BYPASS_CACHE will force the Necko DNS 
      * cache entry to be refreshed with a new call to NSPR if it is set before
@@ -155,16 +155,22 @@ interface nsISocketTransport : nsITransp
      * When setting this flag, the socket will not apply any
      * credentials when establishing a connection. For example,
      * an SSL connection would not send any client-certificates
      * if this flag is set.
      */
     const unsigned long ANONYMOUS_CONNECT = (1 << 1);
 
     /**
+     * If set, we will skip all IPv6 addresses the host may have and only
+     * connect to IPv4 ones.
+     */
+    const unsigned long DISABLE_IPV6 = (1 << 2);
+
+    /**
      * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
      * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported
      * services require expedited-forwarding).
      * Not setting this value will leave the socket with the default
      * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly
      * IPTOS_PREC_ROUTINE).
      */
     attribute octet QoSBits;
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -937,16 +937,18 @@ nsSocketTransport::ResolveHost()
     nsCOMPtr<nsIDNSService> dns = do_GetService(kDNSServiceCID, &rv);
     if (NS_FAILED(rv)) return rv;
 
     mResolving = PR_TRUE;
 
     PRUint32 dnsFlags = 0;
     if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE)
         dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+    if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6)
+        dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
 
     SendStatus(STATUS_RESOLVING);
     rv = dns->AsyncResolve(SocketHost(), dnsFlags, this, nsnull,
                            getter_AddRefs(mDNSRequest));
     if (NS_SUCCEEDED(rv)) {
         SOCKET_LOG(("  advancing to STATE_RESOLVING\n"));
         mState = STATE_RESOLVING;
     }
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -457,17 +457,17 @@ nsDNSService::AsyncResolve(const nsACStr
                                   NS_GET_IID(nsIDNSListener),
                                   listener,
                                   NS_PROXY_ASYNC | NS_PROXY_ALWAYS,
                                   getter_AddRefs(listenerProxy));
         if (NS_FAILED(rv)) return rv;
         listener = listenerProxy;
     }
 
-    PRUint16 af = GetAFForLookup(*hostPtr);
+    PRUint16 af = GetAFForLookup(*hostPtr, flags);
 
     nsDNSAsyncRequest *req =
             new nsDNSAsyncRequest(res, *hostPtr, listener, flags, af);
     if (!req)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(*result = req);
 
     // addref for resolver; will be released when OnLookupComplete is called.
@@ -515,17 +515,17 @@ nsDNSService::Resolve(const nsACString &
     
     PRMonitor *mon = PR_NewMonitor();
     if (!mon)
         return NS_ERROR_OUT_OF_MEMORY;
 
     PR_EnterMonitor(mon);
     nsDNSSyncRequest syncReq(mon);
 
-    PRUint16 af = GetAFForLookup(*hostPtr);
+    PRUint16 af = GetAFForLookup(*hostPtr, flags);
 
     rv = res->ResolveHost(PromiseFlatCString(*hostPtr).get(), flags, af, &syncReq);
     if (NS_SUCCEEDED(rv)) {
         // wait for result
         while (!syncReq.mDone)
             PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
 
         if (NS_FAILED(syncReq.mStatus))
@@ -575,19 +575,19 @@ nsDNSService::Observe(nsISupports *subje
     if (mResolver) {
         Shutdown();
     }
     Init();
     return NS_OK;
 }
 
 PRUint16
-nsDNSService::GetAFForLookup(const nsACString &host)
+nsDNSService::GetAFForLookup(const nsACString &host, PRUint32 flags)
 {
-    if (mDisableIPv6)
+    if (mDisableIPv6 || (flags & RESOLVE_DISABLE_IPV6))
         return PR_AF_INET;
 
     MutexAutoLock lock(mLock);
 
     PRUint16 af = PR_AF_UNSPEC;
 
     if (!mIPv4OnlyDomains.IsEmpty()) {
         const char *domain, *domainEnd, *end;
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -50,17 +50,17 @@ public:
     NS_DECL_NSPIDNSSERVICE
     NS_DECL_NSIDNSSERVICE
     NS_DECL_NSIOBSERVER
 
     nsDNSService();
     ~nsDNSService();
 
 private:
-    PRUint16 GetAFForLookup(const nsACString &host);
+    PRUint16 GetAFForLookup(const nsACString &host, PRUint32 flags);
 
     nsRefPtr<nsHostResolver>  mResolver;
     nsCOMPtr<nsIIDNService>   mIDN;
 
     // mLock protects access to mResolver and mIPv4OnlyDomains
     mozilla::Mutex            mLock;
 
     // mIPv4OnlyDomains is a comma-separated list of domains for which only
--- a/netwerk/dns/nsIDNSService.idl
+++ b/netwerk/dns/nsIDNSService.idl
@@ -115,9 +115,14 @@ interface nsIDNSService : nsISupports
     const unsigned long RESOLVE_PRIORITY_MEDIUM = (1 << 2);
     const unsigned long RESOLVE_PRIORITY_LOW    = (1 << 3);
 
     /**
      * if set, indicates request is speculative. Speculative requests 
      * return errors if prefetching is disabled by configuration.
      */
     const unsigned long RESOLVE_SPECULATE = (1 << 4);
+
+    /**
+     * If set, only IPv4 addresses will be returned from resolve/asyncResolve.
+     */
+    const unsigned long RESOLVE_DISABLE_IPV6 = (1 << 5);
 };
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1286,17 +1286,18 @@ nsHttpConnectionMgr::nsHalfOpenSocket::~
         mEnt->mHalfOpens.RemoveElement(this);
     }
 }
 
 nsresult
 nsHttpConnectionMgr::
 nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
                                nsIAsyncInputStream **instream,
-                               nsIAsyncOutputStream **outstream)
+                               nsIAsyncOutputStream **outstream,
+                               PRBool isBackup)
 {
     nsresult rv;
 
     const char* types[1];
     types[0] = (mEnt->mConnInfo->UsingSSL()) ?
         "ssl" : gHttpHandler->DefaultSocketType();
     PRUint32 typeCount = (types[0] != nsnull);
 
@@ -1315,16 +1316,24 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     
     PRUint32 tmpFlags = 0;
     if (mTransaction->Caps() & NS_HTTP_REFRESH_DNS)
         tmpFlags = nsISocketTransport::BYPASS_CACHE;
 
     if (mTransaction->Caps() & NS_HTTP_LOAD_ANONYMOUS)
         tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
 
+    // For backup connections, we disable IPv6. That's because some users have
+    // broken IPv6 connectivity (leading to very long timeouts), and disabling
+    // IPv6 on the backup connection gives them a much better user experience
+    // with dual-stack hosts, though they still pay the 250ms delay for each new
+    // connection. This strategy is also known as "happy eyeballs".
+    if (isBackup)
+        tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+
     socketTransport->SetConnectionFlags(tmpFlags);
 
     socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
 
     rv = socketTransport->SetEventSink(this, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = socketTransport->SetSecurityCallbacks(this);
@@ -1353,17 +1362,18 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
 {
     nsresult rv = SetupStreams(getter_AddRefs(mSocketTransport),
                                getter_AddRefs(mStreamIn),
-                               getter_AddRefs(mStreamOut));
+                               getter_AddRefs(mStreamOut),
+                               PR_FALSE);
     LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mStreamOut)
             mStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mStreamOut = nsnull;
         mStreamIn = nsnull;
         mSocketTransport = nsnull;
@@ -1371,17 +1381,18 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
 {
     nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
                                getter_AddRefs(mBackupStreamIn),
-                               getter_AddRefs(mBackupStreamOut));
+                               getter_AddRefs(mBackupStreamOut),
+                               PR_TRUE);
     LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mBackupStreamOut)
             mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mBackupStreamOut = nsnull;
         mBackupStreamIn = nsnull;
         mBackupTransport = nsnull;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -206,17 +206,18 @@ private:
         NS_DECL_NSITIMERCALLBACK
 
         nsHalfOpenSocket(nsConnectionEntry *ent,
                          nsHttpTransaction *trans);
         ~nsHalfOpenSocket();
         
         nsresult SetupStreams(nsISocketTransport **,
                               nsIAsyncInputStream **,
-                              nsIAsyncOutputStream **);
+                              nsIAsyncOutputStream **,
+                              PRBool isBackup);
         nsresult SetupPrimaryStreams();
         nsresult SetupBackupStreams();
         void     SetupBackupTimer();
         void     Abandon();
         
         nsHttpTransaction *Transaction() { return mTransaction; }
 
     private: