diff --git a/netwerk/base/public/nsINetworkLinkService.idl b/netwerk/base/public/nsINetworkLinkService.idl index 9f3b4a88c32..5c62a03c231 100644 --- a/netwerk/base/public/nsINetworkLinkService.idl +++ b/netwerk/base/public/nsINetworkLinkService.idl @@ -10,7 +10,7 @@ /** * Network link status monitoring service. */ -[scriptable, uuid(f7d3be87-7403-4a1e-b89f-2797776e9b08)] +[scriptable, uuid(2deead82-1d29-45f5-8c59-5dbee477cff8)] interface nsINetworkLinkService : nsISupports { /* Link type constants */ @@ -64,6 +64,11 @@ interface nsINetworkLinkService : nsISupports * isLinkUp is now false, linkStatusKnown is true. */ #define NS_NETWORK_LINK_DATA_DOWN "down" +/** + * isLinkUp is still true, but the network setup is modified. + * linkStatusKnown is true. + */ +#define NS_NETWORK_LINK_DATA_CHANGED "changed" /** * linkStatusKnown is now false. */ diff --git a/netwerk/base/src/nsIOService.cpp b/netwerk/base/src/nsIOService.cpp index de9a726b391..96d526f3160 100644 --- a/netwerk/base/src/nsIOService.cpp +++ b/netwerk/base/src/nsIOService.cpp @@ -60,8 +60,8 @@ using namespace mozilla; nsIOService* gIOService = nullptr; static bool gHasWarnedUploadChannel2; -// A general port blacklist. Connections to these ports will not be allowed unless -// the protocol overrides. +// A general port blacklist. Connections to these ports will not be allowed +// unless the protocol overrides. // // TODO: I am sure that there are more ports to be added. // This cut is based on the classic mozilla codebase @@ -265,10 +265,9 @@ nsIOService::InitializeNetworkLinkService() // so let's cross our fingers! mManageOfflineStatus = false; } - if (mManageOfflineStatus) - TrackNetworkLinkStatusForOffline(); + OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN); else SetOffline(false); @@ -922,7 +921,7 @@ nsIOService::Observe(nsISupports *subject, if (mOfflineForProfileChange) { mOfflineForProfileChange = false; if (!mManageOfflineStatus || - NS_FAILED(TrackNetworkLinkStatusForOffline())) { + NS_FAILED(OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN))) { SetOffline(false); } } @@ -952,11 +951,11 @@ nsIOService::Observe(nsISupports *subject, mProxyService = nullptr; } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { - if (!mOfflineForProfileChange && mManageOfflineStatus) { - TrackNetworkLinkStatusForOffline(); + if (!mOfflineForProfileChange) { + OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get()); } } - + return NS_OK; } @@ -1054,24 +1053,25 @@ nsIOService::NewSimpleNestedURI(nsIURI* aURI, nsIURI** aResult) } NS_IMETHODIMP -nsIOService::SetManageOfflineStatus(bool aManage) { +nsIOService::SetManageOfflineStatus(bool aManage) +{ nsresult rv = NS_OK; // SetManageOfflineStatus must throw when we fail to go from non-managed - // to managed. Usually because there is no link monitoring service - // available. Failure to do this switch is detected by a failure of - // TrackNetworkLinkStatusForOffline(). When there is no network link - // available during call to InitializeNetworkLinkService(), application is - // put to offline mode. And when we change mMangeOfflineStatus to false - // on the next line we get stuck on being offline even though the link - // becomes later available. + // to managed. Usually because there is no link monitoring service + // available. Failure to do this switch is detected by a failure of + // OnNetworkLinkEvent(). When there is no network link available during + // call to InitializeNetworkLinkService(), application is put to offline + // mode. And when we change mMangeOfflineStatus to false on the next line + // we get stuck on being offline even though the link becomes later + // available. bool wasManaged = mManageOfflineStatus; mManageOfflineStatus = aManage; InitializeNetworkLinkService(); if (mManageOfflineStatus && !wasManaged) { - rv = TrackNetworkLinkStatusForOffline(); + rv = OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN); if (NS_FAILED(rv)) mManageOfflineStatus = false; } @@ -1084,41 +1084,57 @@ nsIOService::GetManageOfflineStatus(bool* aManage) { return NS_OK; } +// input argument 'data' is already UTF8'ed nsresult -nsIOService::TrackNetworkLinkStatusForOffline() +nsIOService::OnNetworkLinkEvent(const char *data) { - NS_ASSERTION(mManageOfflineStatus, - "Don't call this unless we're managing the offline status"); if (!mNetworkLinkService) return NS_ERROR_FAILURE; if (mShutdown) return NS_ERROR_NOT_AVAILABLE; - - // check to make sure this won't collide with Autodial - if (mSocketTransportService) { - bool autodialEnabled = false; - mSocketTransportService->GetAutodialEnabled(&autodialEnabled); - // If autodialing-on-link-down is enabled, check if the OS auto dial - // option is set to always autodial. If so, then we are - // always up for the purposes of offline management. - if (autodialEnabled) { + + if (mManageOfflineStatus) + return NS_OK; + + if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) { + // check to make sure this won't collide with Autodial + if (mSocketTransportService) { + bool autodialEnabled = false; + mSocketTransportService->GetAutodialEnabled(&autodialEnabled); + // If autodialing-on-link-down is enabled, check if the OS auto + // dial option is set to always autodial. If so, then we are + // always up for the purposes of offline management. + if (autodialEnabled) { #if defined(XP_WIN) - // On Windows, we should first check with the OS - // to see if autodial is enabled. If it is - // enabled then we are allowed to manage the - // offline state. - if(nsNativeConnectionHelper::IsAutodialEnabled()) - return SetOffline(false); + // On Windows, we should first check with the OS to see if + // autodial is enabled. If it is enabled then we are allowed + // to manage the offline state. + if (nsNativeConnectionHelper::IsAutodialEnabled()) { + return SetOffline(false); + } #else - return SetOffline(false); + return SetOffline(false); #endif + } } } bool isUp; - nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp); - NS_ENSURE_SUCCESS(rv, rv); + if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) { + isUp = false; + } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) { + isUp = true; + } else if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) { + // CHANGED events are handled by others + return NS_OK; + } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) { + nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp); + NS_ENSURE_SUCCESS(rv, rv); + } else { + NS_WARNING("Unhandled network event!"); + return NS_OK; + } return SetOffline(!isUp); } diff --git a/netwerk/base/src/nsIOService.h b/netwerk/base/src/nsIOService.h index 1d91615bcf4..f442fb52a20 100644 --- a/netwerk/base/src/nsIOService.h +++ b/netwerk/base/src/nsIOService.h @@ -81,7 +81,7 @@ private: nsIOService(); ~nsIOService(); - nsresult TrackNetworkLinkStatusForOffline(); + nsresult OnNetworkLinkEvent(const char *data); nsresult GetCachedProtocolHandler(const char *scheme, nsIProtocolHandler* *hdlrResult, diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index 23366ea1f2c..edab9f0d93e 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -30,12 +30,15 @@ #include "nsCharSeparatedTokenizer.h" #include "nsNetAddr.h" #include "nsProxyRelease.h" +#include "nsIObserverService.h" +#include "nsINetworkLinkService.h" #include "mozilla/Attributes.h" #include "mozilla/VisualEventTracer.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/ChildDNSService.h" #include "mozilla/net/DNSListenerProxy.h" +#include "mozilla/Services.h" using namespace mozilla; using namespace mozilla::net; @@ -540,12 +543,13 @@ nsDNSService::Init() prefs->AddObserver("network.proxy.type", this, false); } - nsresult rv; nsCOMPtr observerService = - do_GetService("@mozilla.org/observer-service;1", &rv); - if (NS_SUCCEEDED(rv)) { + mozilla::services::GetObserverService(); + if (observerService) { observerService->AddObserver(this, "last-pb-context-exited", false); + observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); } + } nsDNSPrefetch::Initialize(this); @@ -870,10 +874,21 @@ nsDNSService::GetMyHostName(nsACString &result) NS_IMETHODIMP nsDNSService::Observe(nsISupports *subject, const char *topic, const char16_t *data) { - // we are only getting called if a preference has changed. + // We are only getting called if a preference has changed or there's a + // network link event. NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0 || - strcmp(topic, "last-pb-context-exited") == 0, - "unexpected observe call"); + strcmp(topic, "last-pb-context-exited") == 0 || + strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0, + "unexpected observe call"); + + if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { + nsCString converted = NS_ConvertUTF16toUTF8(data); + const char *state = converted.get(); + if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) { + mResolver->FlushCache(); + } + return NS_OK; + } // // Shutdown and this function are both only called on the UI thread, so we don't diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp index 1808fd1e335..7e91ccc7081 100644 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -478,10 +478,8 @@ nsHostResolver::ClearPendingQueue(PRCList *aPendingQ) } void -nsHostResolver::Shutdown() +nsHostResolver::FlushCache() { - LOG(("Shutting down host resolver.\n")); - PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ; PR_INIT_CLIST(&pendingQHigh); PR_INIT_CLIST(&pendingQMed); @@ -490,23 +488,20 @@ nsHostResolver::Shutdown() { MutexAutoLock lock(mLock); - - mShutdown = true; - MoveCList(mHighQ, pendingQHigh); MoveCList(mMediumQ, pendingQMed); MoveCList(mLowQ, pendingQLow); MoveCList(mEvictionQ, evictionQ); mEvictionQSize = 0; mPendingCount = 0; - + if (mNumIdleThreads) mIdleThreadCV.NotifyAll(); - + // empty host database PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nullptr); } - + ClearPendingQueue(&pendingQHigh); ClearPendingQueue(&pendingQMed); ClearPendingQueue(&pendingQLow); @@ -519,6 +514,18 @@ nsHostResolver::Shutdown() NS_RELEASE(rec); } } +} + +void +nsHostResolver::Shutdown() +{ + LOG(("Shutting down host resolver.\n")); + + { + MutexAutoLock lock(mLock); + mShutdown = true; + } + FlushCache(); #ifdef NS_BUILD_REFCNT_LOGGING diff --git a/netwerk/dns/nsHostResolver.h b/netwerk/dns/nsHostResolver.h index 3c60152eab5..5b86427c78a 100644 --- a/netwerk/dns/nsHostResolver.h +++ b/netwerk/dns/nsHostResolver.h @@ -238,6 +238,11 @@ public: size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + /** + * Flush the DNS cache. + */ + void FlushCache(); + private: explicit nsHostResolver(uint32_t maxCacheEntries = 50, uint32_t maxCacheLifetime = 60, uint32_t lifetimeGracePeriod = 0); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index d0caf21a80e..02225edc36c 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -47,6 +47,7 @@ #include "SpdyZlibReporter.h" #include "nsIMemoryReporter.h" #include "nsIParentalControlsService.h" +#include "nsINetworkLinkService.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/Telemetry.h" @@ -347,6 +348,7 @@ nsHttpHandler::Init() mObserverService->AddObserver(this, "net:failed-to-process-uri-content", true); mObserverService->AddObserver(this, "last-pb-context-exited", true); mObserverService->AddObserver(this, "browser:purge-session-history", true); + mObserverService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); } MakeNewRequestTokenBucket(); @@ -1777,9 +1779,8 @@ nsHttpHandler::Observe(nsISupports *subject, nsCOMPtr prefBranch = do_QueryInterface(subject); if (prefBranch) PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get()); - } - else if (strcmp(topic, "profile-change-net-teardown") == 0 || - strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + } else if (strcmp(topic, "profile-change-net-teardown") == 0 || + strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { mHandlerActive = false; @@ -1799,30 +1800,25 @@ nsHttpHandler::Observe(nsISupports *subject, if (!mDoNotTrackEnabled) { Telemetry::Accumulate(Telemetry::DNT_USAGE, DONOTTRACK_VALUE_UNSET); - } - else { + } else { Telemetry::Accumulate(Telemetry::DNT_USAGE, mDoNotTrackValue); } - } - else if (strcmp(topic, "profile-change-net-restore") == 0) { + } else if (strcmp(topic, "profile-change-net-restore") == 0) { // initialize connection manager InitConnectionMgr(); - } - else if (strcmp(topic, "net:clear-active-logins") == 0) { + } else if (strcmp(topic, "net:clear-active-logins") == 0) { mAuthCache.ClearAll(); mPrivateAuthCache.ClearAll(); - } - else if (strcmp(topic, "net:prune-dead-connections") == 0) { + } else if (strcmp(topic, "net:prune-dead-connections") == 0) { if (mConnMgr) { mConnMgr->PruneDeadConnections(); } - } - else if (strcmp(topic, "net:failed-to-process-uri-content") == 0) { + } else if (strcmp(topic, "net:failed-to-process-uri-content") == 0) { nsCOMPtr uri = do_QueryInterface(subject); - if (uri && mConnMgr) + if (uri && mConnMgr) { mConnMgr->ReportFailedToProcess(uri); - } - else if (strcmp(topic, "last-pb-context-exited") == 0) { + } + } else if (strcmp(topic, "last-pb-context-exited") == 0) { mPrivateAuthCache.ClearAll(); } else if (strcmp(topic, "browser:purge-session-history") == 0) { if (mConnMgr && gSocketTransportService) { @@ -1830,6 +1826,14 @@ nsHttpHandler::Observe(nsISupports *subject, &nsHttpConnectionMgr::ClearConnectionHistory); gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); } + } else if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) { + nsCString converted = NS_ConvertUTF16toUTF8(data); + const char *state = converted.get(); + if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) { + if (mConnMgr) { + mConnMgr->PruneDeadConnections(); + } + } } return NS_OK; diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp index dc19596591a..feef9fb9367 100644 --- a/netwerk/system/win32/nsNotifyAddrListener.cpp +++ b/netwerk/system/win32/nsNotifyAddrListener.cpp @@ -12,6 +12,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include "plstr.h" #include "nsThreadUtils.h" @@ -29,6 +34,26 @@ static HMODULE sNetshell; static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties; +static HMODULE sIphlpapi; +static decltype(NotifyIpInterfaceChange)* sNotifyIpInterfaceChange; +static decltype(CancelMibChangeNotify2)* sCancelMibChangeNotify2; + +static void InitIphlpapi(void) +{ + if (!sIphlpapi) { + sIphlpapi = LoadLibraryW(L"Iphlpapi.dll"); + if (sIphlpapi) { + sNotifyIpInterfaceChange = (decltype(NotifyIpInterfaceChange)*) + GetProcAddress(sIphlpapi, "NotifyIpInterfaceChange"); + sCancelMibChangeNotify2 = (decltype(CancelMibChangeNotify2)*) + GetProcAddress(sIphlpapi, "CancelMibChangeNotify2"); + } else { + NS_WARNING("Failed to load Iphlpapi.dll - cannot detect network" + " changes!"); + } + } +} + static void InitNetshellLibrary(void) { if (!sNetshell) { @@ -47,6 +72,12 @@ static void FreeDynamicLibraries(void) FreeLibrary(sNetshell); sNetshell = nullptr; } + if (sIphlpapi) { + sNotifyIpInterfaceChange = nullptr; + sCancelMibChangeNotify2 = nullptr; + FreeLibrary(sIphlpapi); + sIphlpapi = nullptr; + } } NS_IMPL_ISUPPORTS(nsNotifyAddrListener, @@ -60,6 +91,7 @@ nsNotifyAddrListener::nsNotifyAddrListener() , mCheckAttempted(false) , mShutdownEvent(nullptr) { + InitIphlpapi(); } nsNotifyAddrListener::~nsNotifyAddrListener() @@ -97,36 +129,62 @@ nsNotifyAddrListener::GetLinkType(uint32_t *aLinkType) return NS_OK; } +// Static Callback function for NotifyIpInterfaceChange API. +static void WINAPI OnInterfaceChange(PVOID callerContext, + PMIB_IPINTERFACE_ROW row, + MIB_NOTIFICATION_TYPE notificationType) +{ + nsNotifyAddrListener *notify = static_cast(callerContext); + notify->CheckLinkStatus(); +} + NS_IMETHODIMP nsNotifyAddrListener::Run() { PR_SetCurrentThreadName("Link Monitor"); + if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2) { + // For Windows versions which are older than Vista which lack + // NotifyIpInterfaceChange. Note this means no IPv6 support. + HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); + NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY); - HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); - NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY); + HANDLE handles[2] = { ev, mShutdownEvent }; + OVERLAPPED overlapped = { 0 }; + bool shuttingDown = false; - HANDLE handles[2] = { ev, mShutdownEvent }; - OVERLAPPED overlapped = { 0 }; - bool shuttingDown = false; + overlapped.hEvent = ev; + while (!shuttingDown) { + HANDLE h; + DWORD ret = NotifyAddrChange(&h, &overlapped); - overlapped.hEvent = ev; - while (!shuttingDown) { - HANDLE h; - DWORD ret = NotifyAddrChange(&h, &overlapped); - - if (ret == ERROR_IO_PENDING) { - ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - if (ret == WAIT_OBJECT_0) { - CheckLinkStatus(); + if (ret == ERROR_IO_PENDING) { + ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (ret == WAIT_OBJECT_0) { + CheckLinkStatus(); + } else { + shuttingDown = true; + } } else { shuttingDown = true; } - } else { - shuttingDown = true; } - } - CloseHandle(ev); + CloseHandle(ev); + } else { + // Windows Vista and newer versions. + HANDLE interfacechange; + // The callback will simply invoke CheckLinkStatus() + DWORD ret = sNotifyIpInterfaceChange( + AF_UNSPEC, // IPv4 and IPv6 + (PIPINTERFACE_CHANGE_CALLBACK)OnInterfaceChange, + this, // pass to callback + false, // no initial notification + &interfacechange); + if (ret == NO_ERROR) { + ret = WaitForSingleObject(mShutdownEvent, INFINITE); + } + sCancelMibChangeNotify2(interfacechange); + } return NS_OK; } @@ -189,11 +247,11 @@ nsNotifyAddrListener::Shutdown(void) return rv; } -/* Sends the given event to the UI thread. Assumes aEventID never goes out - * of scope (static strings are ideal). +/* Sends the given event. Assumes aEventID never goes out of scope (static + * strings are ideal). */ nsresult -nsNotifyAddrListener::SendEventToUI(const char *aEventID) +nsNotifyAddrListener::SendEvent(const char *aEventID) { if (!aEventID) return NS_ERROR_NULL_POINTER; @@ -217,8 +275,12 @@ nsNotifyAddrListener::ChangeEvent::Run() return NS_OK; } + +// Bug 465158 features an explanation for this check. ICS being "Internet +// Connection Sharing). The description says it is always IP address +// 192.168.0.1 for this case. bool -nsNotifyAddrListener::CheckIsGateway(PIP_ADAPTER_ADDRESSES aAdapter) +nsNotifyAddrListener::CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter) { if (!aAdapter->FirstUnicastAddress) return false; @@ -320,38 +382,70 @@ nsNotifyAddrListener::CheckAdaptersAddresses(void) { ULONG len = 16384; - PIP_ADAPTER_ADDRESSES addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); - if (!addresses) - return ERROR_OUTOFMEMORY; + PIP_ADAPTER_ADDRESSES adapterList = (PIP_ADAPTER_ADDRESSES) moz_xmalloc(len); - DWORD ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len); + ULONG flags = GAA_FLAG_SKIP_DNS_SERVER|GAA_FLAG_SKIP_MULTICAST| + GAA_FLAG_SKIP_ANYCAST; + + DWORD ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len); if (ret == ERROR_BUFFER_OVERFLOW) { - free(addresses); - addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); - if (!addresses) - return ERROR_BUFFER_OVERFLOW; - ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len); + free(adapterList); + adapterList = static_cast (moz_xmalloc(len)); + + ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len); } if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { - free(addresses); + free(adapterList); return ERROR_NOT_SUPPORTED; } + // + // Since NotifyIpInterfaceChange() signals a change more often than we + // think is a worthy change, we checksum the entire state of all interfaces + // that are UP. If the checksum is the same as previous check, nothing + // of interest changed! + // + ULONG sum = 0; + if (ret == ERROR_SUCCESS) { - PIP_ADAPTER_ADDRESSES ptr; bool linkUp = false; - for (ptr = addresses; !linkUp && ptr; ptr = ptr->Next) { - if (ptr->OperStatus == IfOperStatusUp && - ptr->IfType != IF_TYPE_SOFTWARE_LOOPBACK && - !CheckIsGateway(ptr)) - linkUp = true; + for (PIP_ADAPTER_ADDRESSES adapter = adapterList; adapter; + adapter = adapter->Next) { + if (adapter->OperStatus != IfOperStatusUp || + !adapter->FirstUnicastAddress || + adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK || + CheckICSGateway(adapter) ) { + continue; + } + + // Add chars from AdapterName to the checksum. + for (int i = 0; adapter->AdapterName[i]; ++i) { + sum <<= 2; + sum += adapter->AdapterName[i]; + } + + // Add bytes from each socket address to the checksum. + for (PIP_ADAPTER_UNICAST_ADDRESS pip = adapter->FirstUnicastAddress; + pip; pip = pip->Next) { + SOCKET_ADDRESS *sockAddr = &pip->Address; + for (int i = 0; i < sockAddr->iSockaddrLength; ++i) { + sum += (reinterpret_cast + (sockAddr->lpSockaddr))[i]; + } + } + linkUp = true; } mLinkUp = linkUp; mStatusKnown = true; } - free(addresses); + free(adapterList); + + if (mLinkUp) { + /* Store the checksum only if one or more interfaces are up */ + mIPInterfaceChecksum = sum; + } CoUninitialize(); @@ -368,24 +462,45 @@ nsNotifyAddrListener::CheckLinkStatus(void) { DWORD ret; const char *event; + bool prevLinkUp = mLinkUp; + ULONG prevCsum = mIPInterfaceChecksum; - // This call is very expensive (~650 milliseconds), so we don't want to - // call it synchronously. Instead, we just start up assuming we have a - // network link, but we'll report that the status is unknown. + // The CheckAdaptersAddresses call is very expensive (~650 milliseconds), + // so we don't want to call it synchronously. Instead, we just start up + // assuming we have a network link, but we'll report that the status is + // unknown. if (NS_IsMainThread()) { NS_WARNING("CheckLinkStatus called on main thread! No check " "performed. Assuming link is up, status is unknown."); mLinkUp = true; + + if (!mStatusKnown) { + event = NS_NETWORK_LINK_DATA_UNKNOWN; + } else if (!prevLinkUp) { + event = NS_NETWORK_LINK_DATA_UP; + } else { + // Known status and it was already UP + event = nullptr; + } + + if (event) { + SendEvent(event); + } } else { ret = CheckAdaptersAddresses(); if (ret != ERROR_SUCCESS) { mLinkUp = true; } - } - if (mStatusKnown) - event = mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN; - else - event = NS_NETWORK_LINK_DATA_UNKNOWN; - SendEventToUI(event); + if (mLinkUp && (prevCsum != mIPInterfaceChecksum)) { + // Network is online. Topology has changed. Always send CHANGED + // before UP. + SendEvent(NS_NETWORK_LINK_DATA_CHANGED); + } + if (prevLinkUp != mLinkUp) { + // UP/DOWN status changed, send appropriate UP/DOWN event + SendEvent(mLinkUp ? + NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN); + } + } } diff --git a/netwerk/system/win32/nsNotifyAddrListener.h b/netwerk/system/win32/nsNotifyAddrListener.h index 9982dfa2d65..22feb535408 100644 --- a/netwerk/system/win32/nsNotifyAddrListener.h +++ b/netwerk/system/win32/nsNotifyAddrListener.h @@ -30,6 +30,7 @@ public: nsNotifyAddrListener(); nsresult Init(void); + void CheckLinkStatus(void); protected: class ChangeEvent : public nsRunnable { @@ -48,16 +49,22 @@ protected: bool mCheckAttempted; nsresult Shutdown(void); - nsresult SendEventToUI(const char *aEventID); + nsresult SendEvent(const char *aEventID); DWORD CheckAdaptersAddresses(void); - bool CheckIsGateway(PIP_ADAPTER_ADDRESSES aAdapter); + + // Checks for an Internet Connection Sharing (ICS) gateway. + bool CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter); bool CheckICSStatus(PWCHAR aAdapterName); - void CheckLinkStatus(void); nsCOMPtr mThread; HANDLE mShutdownEvent; + +private: + // This is a checksum of various meta data for all network interfaces + // considered UP at last check. + ULONG mIPInterfaceChecksum; }; #endif /* NSNOTIFYADDRLISTENER_H_ */