diff --git a/netwerk/base/public/nsISocketTransport.idl b/netwerk/base/public/nsISocketTransport.idl index cf43807f5b84..0c8c585aadd1 100644 --- a/netwerk/base/public/nsISocketTransport.idl +++ b/netwerk/base/public/nsISocketTransport.idl @@ -163,6 +163,12 @@ interface nsISocketTransport : nsITransport */ const unsigned long NO_PERMANENT_STORAGE = (1 << 3); + /** + * If set, we will skip all IPv4 addresses the host may have and only + * connect to IPv6 ones. + */ + const unsigned long DISABLE_IPV4 = (1 << 4); + /** * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index 0396e75b99d1..7dd872ad19c4 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -926,6 +926,12 @@ nsSocketTransport::ResolveHost() dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE; if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; + + NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) || + !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), + "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4"); SendStatus(NS_NET_STATUS_RESOLVING_HOST); rv = dns->AsyncResolve(SocketHost(), dnsFlags, this, nullptr, @@ -1267,12 +1273,12 @@ nsSocketTransport::RecoverFromError() bool tryAgain = false; - if (mConnectionFlags & DISABLE_IPV6 && + if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4) && mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING && !mProxyTransparentResolvesHost) { SOCKET_LOG((" trying lookup again with both ipv4/ipv6 enabled\n")); - mConnectionFlags &= ~DISABLE_IPV6; + mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); tryAgain = true; } @@ -1285,14 +1291,15 @@ nsSocketTransport::RecoverFromError() SOCKET_LOG((" trying again with next ip address\n")); tryAgain = true; } - else if (mConnectionFlags & DISABLE_IPV6) { + else if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4)) { // Drop state to closed. This will trigger new round of DNS // resolving bellow. - // XXX Here should idealy be set now non-existing flag DISABLE_IPV4 - SOCKET_LOG((" failed to connect all ipv4 hosts," + // XXX Could be optimized to only switch the flags to save duplicate + // connection attempts. + SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only hosts," " trying lookup/connect again with both ipv4/ipv6\n")); mState = STATE_CLOSED; - mConnectionFlags &= ~DISABLE_IPV6; + mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); tryAgain = true; } } diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index f1fe85b2460a..05be514c72f1 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -850,6 +850,9 @@ nsDNSService::GetAFForLookup(const nsACString &host, uint32_t flags) } while (*end); } + if ((af != PR_AF_INET) && (flags & RESOLVE_DISABLE_IPV4)) + af = PR_AF_INET6; + return af; } diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl index 6b06a194f83e..183ca0044225 100644 --- a/netwerk/dns/nsIDNSService.idl +++ b/netwerk/dns/nsIDNSService.idl @@ -134,4 +134,9 @@ interface nsIDNSService : nsISupports * asyncResolve. */ const unsigned long RESOLVE_OFFLINE = (1 << 6); + + /** + * If set, only IPv6 addresses will be returned from resolve/asyncResolve. + */ + const unsigned long RESOLVE_DISABLE_IPV4 = (1 << 7); }; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 4eca5e109cd9..d15af44d21e6 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -4484,8 +4484,10 @@ nsHttpChannel::BeginConnect() // Force-Reload should reset the persistent connection pool for this host if (mLoadFlags & LOAD_FRESH_CONNECTION) { // just the initial document resets the whole pool - if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) + if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) { gHttpHandler->ConnMgr()->ClosePersistentConnections(); + gHttpHandler->ConnMgr()->ResetIPFamillyPreference(mConnectionInfo); + } // each sub resource gets a fresh connection mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING); } diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index e079b15e6bd6..4e6efd9e7446 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -2521,8 +2521,13 @@ nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport, // 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 && gHttpHandler->FastFallbackToIPv4()) + if (mEnt->mPreferIPv6) { + tmpFlags |= nsISocketTransport::DISABLE_IPV4; + } + else if (mEnt->mPreferIPv4 || + (isBackup && gHttpHandler->FastFallbackToIPv4())) { tmpFlags |= nsISocketTransport::DISABLE_IPV6; + } socketTransport->SetConnectionFlags(tmpFlags); @@ -2714,6 +2719,7 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) LOG(("nsHalfOpenSocket::OnOutputStreamReady " "Created new nshttpconnection %p\n", conn.get())); + PRNetAddr peeraddr; nsCOMPtr callbacks; mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks)); if (out == mStreamOut) { @@ -2724,6 +2730,9 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) callbacks, PR_MillisecondsToInterval(rtt.ToMilliseconds())); + if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) + mEnt->RecordIPFamilyPreference(peeraddr.raw.family); + // The nsHttpConnection object now owns these streams and sockets mStreamOut = nullptr; mStreamIn = nullptr; @@ -2737,6 +2746,9 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) callbacks, PR_MillisecondsToInterval(rtt.ToMilliseconds())); + if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr))) + mEnt->RecordIPFamilyPreference(peeraddr.raw.family); + // The nsHttpConnection object now owns these streams and sockets mBackupStreamOut = nullptr; mBackupStreamIn = nullptr; @@ -2949,6 +2961,8 @@ nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci) , mUsingSpdy(false) , mTestedSpdy(false) , mSpdyPreferred(false) + , mPreferIPv4(false) + , mPreferIPv6(false) { NS_ADDREF(mConnInfo); if (gHttpHandler->GetPipelineAggressive()) { @@ -3104,7 +3118,8 @@ nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn) } void -nsHttpConnectionMgr::nsConnectionEntry::OnYellowComplete() +nsHttpConnectionMgr:: +nsConnectionEntry::OnYellowComplete() { if (mPipelineState == PS_YELLOW) { if (mYellowGoodEvents && !mYellowBadEvents) { @@ -3127,7 +3142,8 @@ nsHttpConnectionMgr::nsConnectionEntry::OnYellowComplete() } void -nsHttpConnectionMgr::nsConnectionEntry::CreditPenalty() +nsHttpConnectionMgr:: +nsConnectionEntry::CreditPenalty() { if (mLastCreditTime.IsNull()) return; @@ -3223,8 +3239,17 @@ nsHttpConnectionMgr::GetConnectionData(nsTArray *aA return true; } +void +nsHttpConnectionMgr::ResetIPFamillyPreference(nsHttpConnectionInfo *ci) +{ + nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr); + if (ent) + ent->ResetIPFamilyPreference(); +} + uint32_t -nsHttpConnectionMgr::nsConnectionEntry::UnconnectedHalfOpens() +nsHttpConnectionMgr:: +nsConnectionEntry::UnconnectedHalfOpens() { uint32_t unconnectedHalfOpens = 0; for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) { @@ -3250,3 +3275,22 @@ nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen) // altering the pending q vector from an arbitrary stack gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); } + +void +nsHttpConnectionMgr:: +nsConnectionEntry::RecordIPFamilyPreference(uint16_t family) +{ + if (family == PR_AF_INET && !mPreferIPv6) + mPreferIPv4 = true; + + if (family == PR_AF_INET6 && !mPreferIPv4) + mPreferIPv6 = true; +} + +void +nsHttpConnectionMgr:: +nsConnectionEntry::ResetIPFamilyPreference() +{ + mPreferIPv4 = false; + mPreferIPv6 = false; +} diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index a27b9598959e..30d917bc6eae 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -225,6 +225,9 @@ public: bool SupportsPipelining(nsHttpConnectionInfo *); bool GetConnectionData(nsTArray *); + + void ResetIPFamillyPreference(nsHttpConnectionInfo *); + private: virtual ~nsHttpConnectionMgr(); @@ -343,6 +346,20 @@ private: bool mTestedSpdy; bool mSpdyPreferred; + + // Flags to remember our happy-eyeballs decision. + // Reset only by Ctrl-F5 reload. + // True when we've first connected an IPv4 server for this host, + // initially false. + bool mPreferIPv4 : 1; + // True when we've first connected an IPv6 server for this host, + // initially false. + bool mPreferIPv6 : 1; + + // Set the IP family preference flags according the connected family + void RecordIPFamilyPreference(uint16_t family); + // Resets all flags to their default values + void ResetIPFamilyPreference(); }; // nsConnectionHandle