From 9b9ae0b4f156f29545c5fac76c2412f8f0c54d6e Mon Sep 17 00:00:00 2001 From: Steve Workman Date: Thu, 6 Feb 2014 11:51:38 -0800 Subject: [PATCH] Bug 444328 - Add support for TCP keepalive in the Socket Transport Service r=mcmanus --- modules/libpref/src/init/all.js | 16 + netwerk/base/public/nsASocketHandler.h | 5 + netwerk/base/public/nsISocketTransport.idl | 9 +- .../public/nsPISocketTransportService.idl | 17 +- netwerk/base/src/nsSocketTransport2.cpp | 389 ++++++++++++++++++ netwerk/base/src/nsSocketTransport2.h | 20 + .../base/src/nsSocketTransportService2.cpp | 111 +++++ netwerk/base/src/nsSocketTransportService2.h | 35 +- 8 files changed, 599 insertions(+), 3 deletions(-) diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index f6234fc4ca56..9f3d30845c2e 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -4059,6 +4059,22 @@ pref("stagefright.disabled", false); // The default TCP send window on Windows is too small, and autotuning only occurs on receive pref("network.tcp.sendbuffer", 131072); #endif +// TCP Keepalive +pref("network.tcp.keepalive.enabled", true); +// Default idle time before first TCP keepalive probe; same time for interval +// between successful probes. Can be overridden in socket transport API. +// Win, Linux and Mac. +pref("network.tcp.keepalive.idle_time", 600); // seconds; 10 mins +// Default timeout for retransmission of unack'd keepalive probes. +// Win and Linux only; not configurable on Mac. +#if defined(XP_UNIX) && !defined(XP_MACOSX) || defined(XP_WIN) +pref("network.tcp.keepalive.retry_interval", 1); // seconds +#endif +// Default maximum probe retransmissions. +// Linux only; not configurable on Win and Mac; fixed at 10 and 8 respectively. +#ifdef XP_UNIX && !defined(XP_MACOSX) +pref("network.tcp.keepalive.probe_count", 4); +#endif // Whether to disable acceleration for all widgets. pref("layers.acceleration.disabled", false); diff --git a/netwerk/base/public/nsASocketHandler.h b/netwerk/base/public/nsASocketHandler.h index 56f01aaa31b2..c15daecd8572 100644 --- a/netwerk/base/public/nsASocketHandler.h +++ b/netwerk/base/public/nsASocketHandler.h @@ -81,6 +81,11 @@ public: *aKeepWhenOffline = false; } + // + // called when global pref for keepalive has changed. + // + virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) { } + // // returns the number of bytes sent/transmitted over the socket // diff --git a/netwerk/base/public/nsISocketTransport.idl b/netwerk/base/public/nsISocketTransport.idl index f225f4e27d7b..8b75e39ccc70 100644 --- a/netwerk/base/public/nsISocketTransport.idl +++ b/netwerk/base/public/nsISocketTransport.idl @@ -27,7 +27,7 @@ native NetAddr(mozilla::net::NetAddr); * NOTE: This is a free-threaded interface, meaning that the methods on * this interface may be called from any thread. */ -[scriptable, uuid(A8318027-0DDC-4E60-A89B-D81AFE3B5020)] +[scriptable, uuid(3F41704C-CD5D-4537-8C4C-7B2EBFC5D972)] interface nsISocketTransport : nsITransport { /** @@ -198,4 +198,11 @@ interface nsISocketTransport : nsITransport */ attribute unsigned long recvBufferSize; attribute unsigned long sendBufferSize; + + /** + * TCP keepalive configuration (support varies by platform). + */ + attribute boolean keepaliveEnabled; + void setKeepaliveVals(in long keepaliveIdleTime, + in long keepaliveRetryInterval); }; diff --git a/netwerk/base/public/nsPISocketTransportService.idl b/netwerk/base/public/nsPISocketTransportService.idl index 68bc56b20530..b8ffdb737eb9 100644 --- a/netwerk/base/public/nsPISocketTransportService.idl +++ b/netwerk/base/public/nsPISocketTransportService.idl @@ -10,7 +10,7 @@ * This is a private interface used by the internals of the networking library. * It will never be frozen. Do not use it in external code. */ -[scriptable, uuid(32de7b6e-90c3-11e1-b57e-001fbc092072)] +[scriptable, uuid(bc5869e7-53a6-4195-8ab8-32e7116b87dd)] interface nsPISocketTransportService : nsISocketTransportService { @@ -36,6 +36,21 @@ interface nsPISocketTransportService : nsISocketTransportService * Setting it offline will cause non-local socket detachment. */ attribute boolean offline; + + /** + * Controls the default timeout (in seconds) for sending keepalive probes. + */ + readonly attribute long keepaliveIdleTime; + + /** + * Controls the default interval (in seconds) between retrying keepalive probes. + */ + readonly attribute long keepaliveRetryInterval; + + /** + * Controls the default retransmission count for keepalive probes. + */ + readonly attribute long keepaliveProbeCount; }; %{C++ diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index 15478bcfebdd..e97ab301d238 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -38,10 +38,23 @@ #include "nsICancelable.h" #include +#include "nsPrintfCString.h" + #if defined(XP_WIN) #include "nsNativeConnectionHelper.h" #endif +/* Following inclusions required for keepalive config not supported by NSPR. */ +#include "private/pprio.h" +#if defined(XP_WIN) +#include +#include +#elif defined(XP_UNIX) +#include +#include +#endif +/* End keepalive config inclusions. */ + using namespace mozilla; using namespace mozilla::net; @@ -754,6 +767,10 @@ nsSocketTransport::nsSocketTransport() , mInput(MOZ_THIS_IN_INITIALIZER_LIST()) , mOutput(MOZ_THIS_IN_INITIALIZER_LIST()) , mQoSBits(0x00) + , mKeepaliveEnabled(false) + , mKeepaliveIdleTimeS(-1) + , mKeepaliveRetryIntervalS(-1) + , mKeepaliveProbeCount(-1) { SOCKET_LOG(("creating nsSocketTransport @%p\n", this)); @@ -1542,6 +1559,14 @@ nsSocketTransport::OnSocketConnected() mFDconnected = true; } + // Ensure keepalive is configured correctly if previously enabled. + if (mKeepaliveEnabled) { + nsresult rv = SetKeepaliveEnabledInternal(true); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv)); + } + } + MOZ_EVENT_TRACER_DONE(this, "net::tcp::connect"); SendStatus(NS_NET_STATUS_CONNECTED_TO); @@ -2392,6 +2417,204 @@ nsSocketTransport::SetConnectionFlags(uint32_t value) return NS_OK; } +void +nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // The global pref toggles keepalive as a system feature; it only affects + // an individual socket if keepalive has been specifically enabled for it. + // So, ensure keepalive is configured correctly if previously enabled. + if (mKeepaliveEnabled) { + nsresult rv = SetKeepaliveEnabledInternal(aEnabled); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%x]", + aEnabled ? "enable" : "disable", rv)); + } + } +} + +nsresult +nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) +{ + MOZ_ASSERT(mKeepaliveIdleTimeS > 0 || + mKeepaliveIdleTimeS <= kMaxTCPKeepIdle); + MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 || + mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl); + MOZ_ASSERT(mKeepaliveProbeCount > 0 || + mKeepaliveProbeCount <= kMaxTCPKeepCount); + + PRFileDescAutoLock fd(this); + if (NS_WARN_IF(!fd.IsInitialized())) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Only enable if keepalives are globally enabled, but ensure other + // options are set correctly on the fd. + bool enable = aEnable && gSocketTransportService->IsKeepaliveEnabled(); + nsresult rv = fd.SetKeepaliveVals(enable, + mKeepaliveIdleTimeS, + mKeepaliveRetryIntervalS, + mKeepaliveProbeCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveVals failed rv[0x%x]", rv)); + return rv; + } + rv = fd.SetKeepaliveEnabled(enable); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%x]", rv)); + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetKeepaliveEnabled(bool *aResult) +{ + MOZ_ASSERT(aResult); + + *aResult = mKeepaliveEnabled; + return NS_OK; +} + +nsresult +nsSocketTransport::EnsureKeepaliveValsAreInitialized() +{ + nsresult rv = NS_OK; + int32_t val = -1; + if (mKeepaliveIdleTimeS == -1) { + rv = gSocketTransportService->GetKeepaliveIdleTime(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveIdleTimeS = val; + } + if (mKeepaliveRetryIntervalS == -1) { + rv = gSocketTransportService->GetKeepaliveRetryInterval(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveRetryIntervalS = val; + } + if (mKeepaliveProbeCount == -1) { + rv = gSocketTransportService->GetKeepaliveProbeCount(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveProbeCount = val; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetKeepaliveEnabled(bool aEnable) +{ +#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (aEnable == mKeepaliveEnabled) { + SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", + this, aEnable ? "enabled" : "disabled")); + return NS_OK; + } + + nsresult rv = NS_OK; + if (aEnable) { + rv = EnsureKeepaliveValsAreInitialized(); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabled [%p] " + "error [0x%x] initializing keepalive vals", + this, rv)); + return rv; + } + } + SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] " + "%s, idle time[%ds] retry interval[%ds] packet count[%d]: " + "globally %s.", + this, aEnable ? "enabled" : "disabled", + mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS, + mKeepaliveProbeCount, + gSocketTransportService->IsKeepaliveEnabled() ? + "enabled" : "disabled")); + + // Set mKeepaliveEnabled here so that state is maintained; it is possible + // that we're in between fds, e.g. the 1st IP address failed, so we're about + // to retry on a 2nd from the DNS record. + mKeepaliveEnabled = aEnable; + + rv = SetKeepaliveEnabledInternal(aEnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv)); + return rv; + } + + return NS_OK; +#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */ + SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform")); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, + int32_t aRetryInterval) +{ +#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aRetryInterval <= 0 || + kMaxTCPKeepIntvl < aRetryInterval)) { + return NS_ERROR_INVALID_ARG; + } + + if (aIdleTime == mKeepaliveIdleTimeS && + aRetryInterval == mKeepaliveRetryIntervalS) { + SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] idle time " + "already %ds and retry interval already %ds.", + this, mKeepaliveIdleTimeS, + mKeepaliveRetryIntervalS)); + return NS_OK; + } + mKeepaliveIdleTimeS = aIdleTime; + mKeepaliveRetryIntervalS = aRetryInterval; + + nsresult rv = NS_OK; + if (mKeepaliveProbeCount == -1) { + int32_t val = -1; + nsresult rv = gSocketTransportService->GetKeepaliveProbeCount(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveProbeCount = val; + } + + SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] " + "keepalive %s, idle time[%ds] retry interval[%ds] " + "packet count[%d]", + this, mKeepaliveEnabled ? "enabled" : "disabled", + mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS, + mKeepaliveProbeCount)); + + PRFileDescAutoLock fd(this); + if (NS_WARN_IF(!fd.IsInitialized())) { + return NS_ERROR_NULL_POINTER; + } + + rv = fd.SetKeepaliveVals(mKeepaliveEnabled, + mKeepaliveIdleTimeS, + mKeepaliveRetryIntervalS, + mKeepaliveProbeCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +#else + SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform")); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} #ifdef ENABLE_SOCKET_TRACING @@ -2465,3 +2688,169 @@ nsSocketTransport::TraceOutBuf(const char *buf, int32_t n) } #endif + +static void LogNSPRError(const char* aPrefix, const void *aObjPtr) +{ +#if defined(PR_LOGGING) && defined(DEBUG) + PRErrorCode errCode = PR_GetError(); + int errLen = PR_GetErrorTextLength(); + nsAutoCString errStr; + if (errLen > 0) { + errStr.SetLength(errLen); + PR_GetErrorText(errStr.BeginWriting()); + } + NS_WARNING(nsPrintfCString( + "%s [%p] NSPR error[0x%x] %s.", + aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode, + errLen > 0 ? errStr.BeginReading() : "").get()); +#endif +} + +nsresult +nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(bool aEnable) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()), + "Cannot enable keepalive if global pref is disabled!"); + if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + PRSocketOptionData opt; + + opt.option = PR_SockOpt_Keepalive; + opt.value.keep_alive = aEnable; + PRStatus status = PR_SetSocketOption(mFd, &opt); + if (NS_WARN_IF(status != PR_SUCCESS)) { + LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled", + mSocketTransport); + return ErrorAccordingToNSPR(PR_GetError()); + } + return NS_OK; +} + +static void LogOSError(const char *aPrefix, const void *aObjPtr) +{ +#if defined(PR_LOGGING) && defined(DEBUG) + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + +#ifdef XP_WIN + DWORD errCode = WSAGetLastError(); + LPVOID errMessage; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &errMessage, + 0, NULL); +#else + int errCode = errno; + char *errMessage = strerror(errno); +#endif + NS_WARNING(nsPrintfCString( + "%s [%p] OS error[0x%x] %s", + aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode, + errMessage ? errMessage : "").get()); +#ifdef XP_WIN + LocalFree(errMessage); +#endif +#endif +} + +/* XXX PR_SetSockOpt does not support setting keepalive values, so native + * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this + * file. Requires inclusion of NSPR private/pprio.h, and platform headers. + */ + +nsresult +nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(bool aEnabled, + int aIdleTime, + int aRetryInterval, + int aProbeCount) +{ +#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aRetryInterval <= 0 || + kMaxTCPKeepIntvl < aRetryInterval)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) { + return NS_ERROR_INVALID_ARG; + } + + PROsfd sock = PR_FileDesc2NativeHandle(mFd); + if (NS_WARN_IF(sock == -1)) { + LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals", + mSocketTransport); + return ErrorAccordingToNSPR(PR_GetError()); + } +#endif + +#if defined(XP_WIN) + // Windows allows idle time and retry interval to be set; NOT ping count. + struct tcp_keepalive keepalive_vals = { + (int)aEnabled, + // Windows uses msec. + aIdleTime * 1000, + aRetryInterval * 1000 + }; + DWORD bytes_returned; + int err = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals, + sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, + NULL); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + +#elif defined(XP_MACOSX) + // OS X uses sec; only supports idle time being set. + int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, + &aIdleTime, sizeof(aIdleTime)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + +#elif defined(XP_UNIX) + // Linux uses sec; supports idle time, retry interval and ping count. + int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, + &aIdleTime, sizeof(aIdleTime)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + + // Linux also has a few extra knobs to tweak + err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, + &aRetryInterval, sizeof(aRetryInterval)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + + err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, + &aProbeCount, sizeof(aProbeCount)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +#else + MOZ_ASSERT(false, "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals " + "called on unsupported platform!"); + return NS_ERROR_UNEXPECTED; +#endif +} diff --git a/netwerk/base/src/nsSocketTransport2.h b/netwerk/base/src/nsSocketTransport2.h index 5f8ae5f48554..427f9061184c 100644 --- a/netwerk/base/src/nsSocketTransport2.h +++ b/netwerk/base/src/nsSocketTransport2.h @@ -139,6 +139,7 @@ public: void OnSocketReady(PRFileDesc *, int16_t outFlags); void OnSocketDetached(PRFileDesc *); void IsLocal(bool *aIsLocal); + void OnKeepaliveEnabledPrefChange(bool aEnabled) MOZ_OVERRIDE MOZ_FINAL; // called when a socket event is handled void OnSocketEvent(uint32_t type, nsresult status, nsISupports *param); @@ -205,8 +206,12 @@ private: operator PRFileDesc*() { return mFd; } + nsresult SetKeepaliveEnabled(bool aEnable); + nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime, + int aRetryInterval, int aProbeCount); private: operator PRFileDescAutoLock*() { return nullptr; } + // Weak ptr to nsSocketTransport since this is a stack class only. nsSocketTransport *mSocketTransport; PRFileDesc *mFd; @@ -386,6 +391,21 @@ private: void TraceInBuf(const char *buf, int32_t n); void TraceOutBuf(const char *buf, int32_t n); #endif + + // Reads prefs to get default keepalive config. + nsresult EnsureKeepaliveValsAreInitialized(); + + // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals. + nsresult SetKeepaliveEnabledInternal(bool aEnable); + + // True if keepalive has been enabled by the socket owner. Note: Keepalive + // must also be enabled globally for it to be enabled in TCP. + bool mKeepaliveEnabled; + + // Keepalive config (support varies by platform). + int32_t mKeepaliveIdleTimeS; + int32_t mKeepaliveRetryIntervalS; + int32_t mKeepaliveProbeCount; }; #endif // !nsSocketTransport_h__ diff --git a/netwerk/base/src/nsSocketTransportService2.cpp b/netwerk/base/src/nsSocketTransportService2.cpp index 195c687eadc4..15755b01b0c6 100644 --- a/netwerk/base/src/nsSocketTransportService2.cpp +++ b/netwerk/base/src/nsSocketTransportService2.cpp @@ -35,6 +35,10 @@ nsSocketTransportService *gSocketTransportService = nullptr; PRThread *gSocketThread = nullptr; #define SEND_BUFFER_PREF "network.tcp.sendbuffer" +#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled" +#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time" +#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval" +#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count" #define SOCKET_LIMIT_TARGET 550U #define SOCKET_LIMIT_MIN 50U #define BLIP_INTERVAL_PREF "network.activity.blipIntervalMilliseconds" @@ -61,6 +65,10 @@ nsSocketTransportService::nsSocketTransportService() , mSentBytesCount(0) , mReceivedBytesCount(0) , mSendBufferSize(0) + , mKeepaliveIdleTimeS(600) + , mKeepaliveRetryIntervalS(1) + , mKeepaliveProbeCount(kDefaultTCPKeepCount) + , mKeepaliveEnabledPref(false) , mProbedMaxCount(false) { #if defined(PR_LOGGING) @@ -463,6 +471,10 @@ nsSocketTransportService::Init() nsCOMPtr tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID); if (tmpPrefService) { tmpPrefService->AddObserver(SEND_BUFFER_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_ENABLED_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_IDLE_TIME_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_RETRY_INTERVAL_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_PROBE_COUNT_PREF, this, false); } UpdatePrefs(); @@ -553,6 +565,39 @@ nsSocketTransportService::SetOffline(bool offline) return NS_OK; } +NS_IMETHODIMP +nsSocketTransportService::GetKeepaliveIdleTime(int32_t *aKeepaliveIdleTimeS) +{ + MOZ_ASSERT(aKeepaliveIdleTimeS); + if (NS_WARN_IF(!aKeepaliveIdleTimeS)) { + return NS_ERROR_NULL_POINTER; + } + *aKeepaliveIdleTimeS = mKeepaliveIdleTimeS; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetKeepaliveRetryInterval(int32_t *aKeepaliveRetryIntervalS) +{ + MOZ_ASSERT(aKeepaliveRetryIntervalS); + if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) { + return NS_ERROR_NULL_POINTER; + } + *aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetKeepaliveProbeCount(int32_t *aKeepaliveProbeCount) +{ + MOZ_ASSERT(aKeepaliveProbeCount); + if (NS_WARN_IF(!aKeepaliveProbeCount)) { + return NS_ERROR_NULL_POINTER; + } + *aKeepaliveProbeCount = mKeepaliveProbeCount; + return NS_OK; +} + NS_IMETHODIMP nsSocketTransportService::CreateTransport(const char **types, uint32_t typeCount, @@ -909,11 +954,77 @@ nsSocketTransportService::UpdatePrefs() nsresult rv = tmpPrefService->GetIntPref(SEND_BUFFER_PREF, &bufferSize); if (NS_SUCCEEDED(rv) && bufferSize > 0) mSendBufferSize = bufferSize; + + // Default TCP Keepalive Values. + int32_t keepaliveIdleTimeS; + rv = tmpPrefService->GetIntPref(KEEPALIVE_IDLE_TIME_PREF, + &keepaliveIdleTimeS); + if (NS_SUCCEEDED(rv)) + mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS, + 1, kMaxTCPKeepIdle); + + int32_t keepaliveRetryIntervalS; + rv = tmpPrefService->GetIntPref(KEEPALIVE_RETRY_INTERVAL_PREF, + &keepaliveRetryIntervalS); + if (NS_SUCCEEDED(rv)) + mKeepaliveRetryIntervalS = clamped(keepaliveRetryIntervalS, + 1, kMaxTCPKeepIntvl); + + int32_t keepaliveProbeCount; + rv = tmpPrefService->GetIntPref(KEEPALIVE_PROBE_COUNT_PREF, + &keepaliveProbeCount); + if (NS_SUCCEEDED(rv)) + mKeepaliveProbeCount = clamped(keepaliveProbeCount, + 1, kMaxTCPKeepCount); + bool keepaliveEnabled = false; + rv = tmpPrefService->GetBoolPref(KEEPALIVE_ENABLED_PREF, + &keepaliveEnabled); + if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) { + mKeepaliveEnabledPref = keepaliveEnabled; + OnKeepaliveEnabledPrefChange(); + } } return NS_OK; } +void +nsSocketTransportService::OnKeepaliveEnabledPrefChange() +{ + // Dispatch to socket thread if we're not executing there. + if (PR_GetCurrentThread() != gSocketThread) { + gSocketTransportService->Dispatch( + NS_NewRunnableMethod( + this, &nsSocketTransportService::OnKeepaliveEnabledPrefChange), + NS_DISPATCH_NORMAL); + return; + } + + SOCKET_LOG(("nsSocketTransportService::OnKeepaliveEnabledPrefChange %s", + mKeepaliveEnabledPref ? "enabled" : "disabled")); + + // Notify each socket that keepalive has been en/disabled globally. + for (int32_t i = mActiveCount - 1; i >= 0; --i) { + NotifyKeepaliveEnabledPrefChange(&mActiveList[i]); + } + for (int32_t i = mIdleCount - 1; i >= 0; --i) { + NotifyKeepaliveEnabledPrefChange(&mIdleList[i]); + } +} + +void +nsSocketTransportService::NotifyKeepaliveEnabledPrefChange(SocketContext *sock) +{ + MOZ_ASSERT(sock, "SocketContext cannot be null!"); + MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!"); + + if (!sock || !sock->mHandler) { + return; + } + + sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref); +} + NS_IMETHODIMP nsSocketTransportService::Observe(nsISupports *subject, const char *topic, diff --git a/netwerk/base/src/nsSocketTransportService2.h b/netwerk/base/src/nsSocketTransportService2.h index 80eb4ea31cae..d8386fe61dbc 100644 --- a/netwerk/base/src/nsSocketTransportService2.h +++ b/netwerk/base/src/nsSocketTransportService2.h @@ -38,6 +38,25 @@ extern PRLogModuleInfo *gSocketTransportLog; //----------------------------------------------------------------------------- +namespace mozilla { +namespace net { +// These maximums are borrowed from the linux kernel. +static const int32_t kMaxTCPKeepIdle = 32767; // ~9 hours. +static const int32_t kMaxTCPKeepIntvl = 32767; +static const int32_t kMaxTCPKeepCount = 127; +static const int32_t kDefaultTCPKeepCount = +#if defined (XP_WIN) + 10; // Hardcoded in Windows. +#elif defined (XP_MACOSX) + 8; // Hardcoded in OSX. +#else + 4; // Specifiable in Linux. +#endif +} +} + +//----------------------------------------------------------------------------- + class nsSocketTransportService : public nsPISocketTransportService , public nsIEventTarget , public nsIThreadObserver @@ -79,6 +98,9 @@ public: void GetSocketConnections(nsTArray *); uint64_t GetSentBytes() { return mSentBytesCount; } uint64_t GetReceivedBytes() { return mReceivedBytesCount; } + + // Returns true if keepalives are enabled in prefs. + bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; } protected: virtual ~nsSocketTransportService(); @@ -183,9 +205,20 @@ private: nsEventQueue mPendingSocketQ; // queue of nsIRunnable objects - // Preference Monitor for SendBufferSize + // Preference Monitor for SendBufferSize and Keepalive prefs. nsresult UpdatePrefs(); int32_t mSendBufferSize; + // Number of seconds of connection is idle before first keepalive ping. + int32_t mKeepaliveIdleTimeS; + // Number of seconds between retries should keepalive pings fail. + int32_t mKeepaliveRetryIntervalS; + // Number of keepalive probes to send. + int32_t mKeepaliveProbeCount; + // True if TCP keepalive is enabled globally. + bool mKeepaliveEnabledPref; + + void OnKeepaliveEnabledPrefChange(); + void NotifyKeepaliveEnabledPrefChange(SocketContext *sock); // Socket thread only for dynamically adjusting max socket size #if defined(XP_WIN)