Bug 939318 - Detect network interface changes on windows properly. r=mcmanus

Now supports IPv6 as well if a new enough windows version is used.
Which notification function to use is detect at run-time.

Now sends CHANGED event if the online interface(s) are different in any
way since it was previously checked and considered UP. CHANGED is sent
before UP in case both are detected.

nIOService: split up the network event receiver function from the
network status init function and have the event receiver act on the
incoming event.

DNSservice: acts on network changes (flushes the host cache)

HttpHandler: acts on network changes
This commit is contained in:
Daniel Stenberg 2014-08-29 01:18:00 -04:00
parent f6846ab8ee
commit 7b39579914
9 changed files with 296 additions and 122 deletions

View File

@ -10,7 +10,7 @@
/** /**
* Network link status monitoring service. * Network link status monitoring service.
*/ */
[scriptable, uuid(f7d3be87-7403-4a1e-b89f-2797776e9b08)] [scriptable, uuid(2deead82-1d29-45f5-8c59-5dbee477cff8)]
interface nsINetworkLinkService : nsISupports interface nsINetworkLinkService : nsISupports
{ {
/* Link type constants */ /* Link type constants */
@ -64,6 +64,11 @@ interface nsINetworkLinkService : nsISupports
* isLinkUp is now false, linkStatusKnown is true. * isLinkUp is now false, linkStatusKnown is true.
*/ */
#define NS_NETWORK_LINK_DATA_DOWN "down" #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. * linkStatusKnown is now false.
*/ */

View File

@ -60,8 +60,8 @@ using namespace mozilla;
nsIOService* gIOService = nullptr; nsIOService* gIOService = nullptr;
static bool gHasWarnedUploadChannel2; static bool gHasWarnedUploadChannel2;
// A general port blacklist. Connections to these ports will not be allowed unless // A general port blacklist. Connections to these ports will not be allowed
// the protocol overrides. // unless the protocol overrides.
// //
// TODO: I am sure that there are more ports to be added. // TODO: I am sure that there are more ports to be added.
// This cut is based on the classic mozilla codebase // This cut is based on the classic mozilla codebase
@ -266,9 +266,8 @@ nsIOService::InitializeNetworkLinkService()
mManageOfflineStatus = false; mManageOfflineStatus = false;
} }
if (mManageOfflineStatus) if (mManageOfflineStatus)
TrackNetworkLinkStatusForOffline(); OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
else else
SetOffline(false); SetOffline(false);
@ -922,7 +921,7 @@ nsIOService::Observe(nsISupports *subject,
if (mOfflineForProfileChange) { if (mOfflineForProfileChange) {
mOfflineForProfileChange = false; mOfflineForProfileChange = false;
if (!mManageOfflineStatus || if (!mManageOfflineStatus ||
NS_FAILED(TrackNetworkLinkStatusForOffline())) { NS_FAILED(OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN))) {
SetOffline(false); SetOffline(false);
} }
} }
@ -952,8 +951,8 @@ nsIOService::Observe(nsISupports *subject,
mProxyService = nullptr; mProxyService = nullptr;
} }
else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
if (!mOfflineForProfileChange && mManageOfflineStatus) { if (!mOfflineForProfileChange) {
TrackNetworkLinkStatusForOffline(); OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
} }
} }
@ -1054,24 +1053,25 @@ nsIOService::NewSimpleNestedURI(nsIURI* aURI, nsIURI** aResult)
} }
NS_IMETHODIMP NS_IMETHODIMP
nsIOService::SetManageOfflineStatus(bool aManage) { nsIOService::SetManageOfflineStatus(bool aManage)
{
nsresult rv = NS_OK; nsresult rv = NS_OK;
// SetManageOfflineStatus must throw when we fail to go from non-managed // SetManageOfflineStatus must throw when we fail to go from non-managed
// to managed. Usually because there is no link monitoring service // to managed. Usually because there is no link monitoring service
// available. Failure to do this switch is detected by a failure of // available. Failure to do this switch is detected by a failure of
// TrackNetworkLinkStatusForOffline(). When there is no network link // OnNetworkLinkEvent(). When there is no network link available during
// available during call to InitializeNetworkLinkService(), application is // call to InitializeNetworkLinkService(), application is put to offline
// put to offline mode. And when we change mMangeOfflineStatus to false // mode. And when we change mMangeOfflineStatus to false on the next line
// on the next line we get stuck on being offline even though the link // we get stuck on being offline even though the link becomes later
// becomes later available. // available.
bool wasManaged = mManageOfflineStatus; bool wasManaged = mManageOfflineStatus;
mManageOfflineStatus = aManage; mManageOfflineStatus = aManage;
InitializeNetworkLinkService(); InitializeNetworkLinkService();
if (mManageOfflineStatus && !wasManaged) { if (mManageOfflineStatus && !wasManaged) {
rv = TrackNetworkLinkStatusForOffline(); rv = OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
if (NS_FAILED(rv)) if (NS_FAILED(rv))
mManageOfflineStatus = false; mManageOfflineStatus = false;
} }
@ -1084,41 +1084,57 @@ nsIOService::GetManageOfflineStatus(bool* aManage) {
return NS_OK; return NS_OK;
} }
// input argument 'data' is already UTF8'ed
nsresult nsresult
nsIOService::TrackNetworkLinkStatusForOffline() nsIOService::OnNetworkLinkEvent(const char *data)
{ {
NS_ASSERTION(mManageOfflineStatus,
"Don't call this unless we're managing the offline status");
if (!mNetworkLinkService) if (!mNetworkLinkService)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
if (mShutdown) if (mShutdown)
return NS_ERROR_NOT_AVAILABLE; return NS_ERROR_NOT_AVAILABLE;
if (mManageOfflineStatus)
return NS_OK;
if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
// check to make sure this won't collide with Autodial // check to make sure this won't collide with Autodial
if (mSocketTransportService) { if (mSocketTransportService) {
bool autodialEnabled = false; bool autodialEnabled = false;
mSocketTransportService->GetAutodialEnabled(&autodialEnabled); mSocketTransportService->GetAutodialEnabled(&autodialEnabled);
// If autodialing-on-link-down is enabled, check if the OS auto dial // If autodialing-on-link-down is enabled, check if the OS auto
// option is set to always autodial. If so, then we are // dial option is set to always autodial. If so, then we are
// always up for the purposes of offline management. // always up for the purposes of offline management.
if (autodialEnabled) { if (autodialEnabled) {
#if defined(XP_WIN) #if defined(XP_WIN)
// On Windows, we should first check with the OS // On Windows, we should first check with the OS to see if
// to see if autodial is enabled. If it is // autodial is enabled. If it is enabled then we are allowed
// enabled then we are allowed to manage the // to manage the offline state.
// offline state. if (nsNativeConnectionHelper::IsAutodialEnabled()) {
if(nsNativeConnectionHelper::IsAutodialEnabled())
return SetOffline(false); return SetOffline(false);
}
#else #else
return SetOffline(false); return SetOffline(false);
#endif #endif
} }
} }
}
bool isUp; bool isUp;
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); nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
} else {
NS_WARNING("Unhandled network event!");
return NS_OK;
}
return SetOffline(!isUp); return SetOffline(!isUp);
} }

View File

@ -81,7 +81,7 @@ private:
nsIOService(); nsIOService();
~nsIOService(); ~nsIOService();
nsresult TrackNetworkLinkStatusForOffline(); nsresult OnNetworkLinkEvent(const char *data);
nsresult GetCachedProtocolHandler(const char *scheme, nsresult GetCachedProtocolHandler(const char *scheme,
nsIProtocolHandler* *hdlrResult, nsIProtocolHandler* *hdlrResult,

View File

@ -30,12 +30,15 @@
#include "nsCharSeparatedTokenizer.h" #include "nsCharSeparatedTokenizer.h"
#include "nsNetAddr.h" #include "nsNetAddr.h"
#include "nsProxyRelease.h" #include "nsProxyRelease.h"
#include "nsIObserverService.h"
#include "nsINetworkLinkService.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/VisualEventTracer.h" #include "mozilla/VisualEventTracer.h"
#include "mozilla/net/NeckoCommon.h" #include "mozilla/net/NeckoCommon.h"
#include "mozilla/net/ChildDNSService.h" #include "mozilla/net/ChildDNSService.h"
#include "mozilla/net/DNSListenerProxy.h" #include "mozilla/net/DNSListenerProxy.h"
#include "mozilla/Services.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::net; using namespace mozilla::net;
@ -540,12 +543,13 @@ nsDNSService::Init()
prefs->AddObserver("network.proxy.type", this, false); prefs->AddObserver("network.proxy.type", this, false);
} }
nsresult rv;
nsCOMPtr<nsIObserverService> observerService = nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1", &rv); mozilla::services::GetObserverService();
if (NS_SUCCEEDED(rv)) { if (observerService) {
observerService->AddObserver(this, "last-pb-context-exited", false); observerService->AddObserver(this, "last-pb-context-exited", false);
observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
} }
} }
nsDNSPrefetch::Initialize(this); nsDNSPrefetch::Initialize(this);
@ -870,11 +874,22 @@ nsDNSService::GetMyHostName(nsACString &result)
NS_IMETHODIMP NS_IMETHODIMP
nsDNSService::Observe(nsISupports *subject, const char *topic, const char16_t *data) 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 || NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0 ||
strcmp(topic, "last-pb-context-exited") == 0, strcmp(topic, "last-pb-context-exited") == 0 ||
strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0,
"unexpected observe call"); "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 // Shutdown and this function are both only called on the UI thread, so we don't
// have to worry about mResolver being cleared out from under us. // have to worry about mResolver being cleared out from under us.

View File

@ -478,10 +478,8 @@ nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
} }
void void
nsHostResolver::Shutdown() nsHostResolver::FlushCache()
{ {
LOG(("Shutting down host resolver.\n"));
PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ; PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
PR_INIT_CLIST(&pendingQHigh); PR_INIT_CLIST(&pendingQHigh);
PR_INIT_CLIST(&pendingQMed); PR_INIT_CLIST(&pendingQMed);
@ -490,9 +488,6 @@ nsHostResolver::Shutdown()
{ {
MutexAutoLock lock(mLock); MutexAutoLock lock(mLock);
mShutdown = true;
MoveCList(mHighQ, pendingQHigh); MoveCList(mHighQ, pendingQHigh);
MoveCList(mMediumQ, pendingQMed); MoveCList(mMediumQ, pendingQMed);
MoveCList(mLowQ, pendingQLow); MoveCList(mLowQ, pendingQLow);
@ -519,6 +514,18 @@ nsHostResolver::Shutdown()
NS_RELEASE(rec); NS_RELEASE(rec);
} }
} }
}
void
nsHostResolver::Shutdown()
{
LOG(("Shutting down host resolver.\n"));
{
MutexAutoLock lock(mLock);
mShutdown = true;
}
FlushCache();
#ifdef NS_BUILD_REFCNT_LOGGING #ifdef NS_BUILD_REFCNT_LOGGING

View File

@ -238,6 +238,11 @@ public:
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
/**
* Flush the DNS cache.
*/
void FlushCache();
private: private:
explicit nsHostResolver(uint32_t maxCacheEntries = 50, uint32_t maxCacheLifetime = 60, explicit nsHostResolver(uint32_t maxCacheEntries = 50, uint32_t maxCacheLifetime = 60,
uint32_t lifetimeGracePeriod = 0); uint32_t lifetimeGracePeriod = 0);

View File

@ -47,6 +47,7 @@
#include "SpdyZlibReporter.h" #include "SpdyZlibReporter.h"
#include "nsIMemoryReporter.h" #include "nsIMemoryReporter.h"
#include "nsIParentalControlsService.h" #include "nsIParentalControlsService.h"
#include "nsINetworkLinkService.h"
#include "mozilla/net/NeckoChild.h" #include "mozilla/net/NeckoChild.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
@ -347,6 +348,7 @@ nsHttpHandler::Init()
mObserverService->AddObserver(this, "net:failed-to-process-uri-content", true); mObserverService->AddObserver(this, "net:failed-to-process-uri-content", true);
mObserverService->AddObserver(this, "last-pb-context-exited", true); mObserverService->AddObserver(this, "last-pb-context-exited", true);
mObserverService->AddObserver(this, "browser:purge-session-history", true); mObserverService->AddObserver(this, "browser:purge-session-history", true);
mObserverService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
} }
MakeNewRequestTokenBucket(); MakeNewRequestTokenBucket();
@ -1777,8 +1779,7 @@ nsHttpHandler::Observe(nsISupports *subject,
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject); nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
if (prefBranch) if (prefBranch)
PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get()); PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
} } else if (strcmp(topic, "profile-change-net-teardown") == 0 ||
else if (strcmp(topic, "profile-change-net-teardown") == 0 ||
strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
mHandlerActive = false; mHandlerActive = false;
@ -1799,30 +1800,25 @@ nsHttpHandler::Observe(nsISupports *subject,
if (!mDoNotTrackEnabled) { if (!mDoNotTrackEnabled) {
Telemetry::Accumulate(Telemetry::DNT_USAGE, DONOTTRACK_VALUE_UNSET); Telemetry::Accumulate(Telemetry::DNT_USAGE, DONOTTRACK_VALUE_UNSET);
} } else {
else {
Telemetry::Accumulate(Telemetry::DNT_USAGE, mDoNotTrackValue); 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 // initialize connection manager
InitConnectionMgr(); InitConnectionMgr();
} } else if (strcmp(topic, "net:clear-active-logins") == 0) {
else if (strcmp(topic, "net:clear-active-logins") == 0) {
mAuthCache.ClearAll(); mAuthCache.ClearAll();
mPrivateAuthCache.ClearAll(); mPrivateAuthCache.ClearAll();
} } else if (strcmp(topic, "net:prune-dead-connections") == 0) {
else if (strcmp(topic, "net:prune-dead-connections") == 0) {
if (mConnMgr) { if (mConnMgr) {
mConnMgr->PruneDeadConnections(); 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<nsIURI> uri = do_QueryInterface(subject); nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
if (uri && mConnMgr) if (uri && mConnMgr) {
mConnMgr->ReportFailedToProcess(uri); mConnMgr->ReportFailedToProcess(uri);
} }
else if (strcmp(topic, "last-pb-context-exited") == 0) { } else if (strcmp(topic, "last-pb-context-exited") == 0) {
mPrivateAuthCache.ClearAll(); mPrivateAuthCache.ClearAll();
} else if (strcmp(topic, "browser:purge-session-history") == 0) { } else if (strcmp(topic, "browser:purge-session-history") == 0) {
if (mConnMgr && gSocketTransportService) { if (mConnMgr && gSocketTransportService) {
@ -1830,6 +1826,14 @@ nsHttpHandler::Observe(nsISupports *subject,
&nsHttpConnectionMgr::ClearConnectionHistory); &nsHttpConnectionMgr::ClearConnectionHistory);
gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); 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; return NS_OK;

View File

@ -12,6 +12,11 @@
#include <ole2.h> #include <ole2.h>
#include <netcon.h> #include <netcon.h>
#include <objbase.h> #include <objbase.h>
#include <winsock2.h>
#include <ws2ipdef.h>
#include <tcpmib.h>
#include <iphlpapi.h>
#include <netioapi.h>
#include <iprtrmib.h> #include <iprtrmib.h>
#include "plstr.h" #include "plstr.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
@ -29,6 +34,26 @@
static HMODULE sNetshell; static HMODULE sNetshell;
static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties; 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) static void InitNetshellLibrary(void)
{ {
if (!sNetshell) { if (!sNetshell) {
@ -47,6 +72,12 @@ static void FreeDynamicLibraries(void)
FreeLibrary(sNetshell); FreeLibrary(sNetshell);
sNetshell = nullptr; sNetshell = nullptr;
} }
if (sIphlpapi) {
sNotifyIpInterfaceChange = nullptr;
sCancelMibChangeNotify2 = nullptr;
FreeLibrary(sIphlpapi);
sIphlpapi = nullptr;
}
} }
NS_IMPL_ISUPPORTS(nsNotifyAddrListener, NS_IMPL_ISUPPORTS(nsNotifyAddrListener,
@ -60,6 +91,7 @@ nsNotifyAddrListener::nsNotifyAddrListener()
, mCheckAttempted(false) , mCheckAttempted(false)
, mShutdownEvent(nullptr) , mShutdownEvent(nullptr)
{ {
InitIphlpapi();
} }
nsNotifyAddrListener::~nsNotifyAddrListener() nsNotifyAddrListener::~nsNotifyAddrListener()
@ -97,11 +129,22 @@ nsNotifyAddrListener::GetLinkType(uint32_t *aLinkType)
return NS_OK; 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<nsNotifyAddrListener*>(callerContext);
notify->CheckLinkStatus();
}
NS_IMETHODIMP NS_IMETHODIMP
nsNotifyAddrListener::Run() nsNotifyAddrListener::Run()
{ {
PR_SetCurrentThreadName("Link Monitor"); 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); HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY); NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY);
@ -126,7 +169,22 @@ nsNotifyAddrListener::Run()
} }
} }
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; return NS_OK;
} }
@ -189,11 +247,11 @@ nsNotifyAddrListener::Shutdown(void)
return rv; return rv;
} }
/* Sends the given event to the UI thread. Assumes aEventID never goes out /* Sends the given event. Assumes aEventID never goes out of scope (static
* of scope (static strings are ideal). * strings are ideal).
*/ */
nsresult nsresult
nsNotifyAddrListener::SendEventToUI(const char *aEventID) nsNotifyAddrListener::SendEvent(const char *aEventID)
{ {
if (!aEventID) if (!aEventID)
return NS_ERROR_NULL_POINTER; return NS_ERROR_NULL_POINTER;
@ -217,8 +275,12 @@ nsNotifyAddrListener::ChangeEvent::Run()
return NS_OK; 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 bool
nsNotifyAddrListener::CheckIsGateway(PIP_ADAPTER_ADDRESSES aAdapter) nsNotifyAddrListener::CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter)
{ {
if (!aAdapter->FirstUnicastAddress) if (!aAdapter->FirstUnicastAddress)
return false; return false;
@ -320,38 +382,70 @@ nsNotifyAddrListener::CheckAdaptersAddresses(void)
{ {
ULONG len = 16384; ULONG len = 16384;
PIP_ADAPTER_ADDRESSES addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); PIP_ADAPTER_ADDRESSES adapterList = (PIP_ADAPTER_ADDRESSES) moz_xmalloc(len);
if (!addresses)
return ERROR_OUTOFMEMORY;
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) { if (ret == ERROR_BUFFER_OVERFLOW) {
free(addresses); free(adapterList);
addresses = (PIP_ADAPTER_ADDRESSES) malloc(len); adapterList = static_cast<PIP_ADAPTER_ADDRESSES> (moz_xmalloc(len));
if (!addresses)
return ERROR_BUFFER_OVERFLOW; ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len);
ret = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, addresses, &len);
} }
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
free(addresses); free(adapterList);
return ERROR_NOT_SUPPORTED; 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) { if (ret == ERROR_SUCCESS) {
PIP_ADAPTER_ADDRESSES ptr;
bool linkUp = false; bool linkUp = false;
for (ptr = addresses; !linkUp && ptr; ptr = ptr->Next) { for (PIP_ADAPTER_ADDRESSES adapter = adapterList; adapter;
if (ptr->OperStatus == IfOperStatusUp && adapter = adapter->Next) {
ptr->IfType != IF_TYPE_SOFTWARE_LOOPBACK && if (adapter->OperStatus != IfOperStatusUp ||
!CheckIsGateway(ptr)) !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<unsigned char *>
(sockAddr->lpSockaddr))[i];
}
}
linkUp = true; linkUp = true;
} }
mLinkUp = linkUp; mLinkUp = linkUp;
mStatusKnown = true; mStatusKnown = true;
} }
free(addresses); free(adapterList);
if (mLinkUp) {
/* Store the checksum only if one or more interfaces are up */
mIPInterfaceChecksum = sum;
}
CoUninitialize(); CoUninitialize();
@ -368,24 +462,45 @@ nsNotifyAddrListener::CheckLinkStatus(void)
{ {
DWORD ret; DWORD ret;
const char *event; const char *event;
bool prevLinkUp = mLinkUp;
ULONG prevCsum = mIPInterfaceChecksum;
// This call is very expensive (~650 milliseconds), so we don't want to // The CheckAdaptersAddresses call is very expensive (~650 milliseconds),
// call it synchronously. Instead, we just start up assuming we have a // so we don't want to call it synchronously. Instead, we just start up
// network link, but we'll report that the status is unknown. // assuming we have a network link, but we'll report that the status is
// unknown.
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
NS_WARNING("CheckLinkStatus called on main thread! No check " NS_WARNING("CheckLinkStatus called on main thread! No check "
"performed. Assuming link is up, status is unknown."); "performed. Assuming link is up, status is unknown.");
mLinkUp = true; 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 { } else {
ret = CheckAdaptersAddresses(); ret = CheckAdaptersAddresses();
if (ret != ERROR_SUCCESS) { if (ret != ERROR_SUCCESS) {
mLinkUp = true; mLinkUp = true;
} }
}
if (mStatusKnown) if (mLinkUp && (prevCsum != mIPInterfaceChecksum)) {
event = mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN; // Network is online. Topology has changed. Always send CHANGED
else // before UP.
event = NS_NETWORK_LINK_DATA_UNKNOWN; SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
SendEventToUI(event); }
if (prevLinkUp != mLinkUp) {
// UP/DOWN status changed, send appropriate UP/DOWN event
SendEvent(mLinkUp ?
NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
}
}
} }

View File

@ -30,6 +30,7 @@ public:
nsNotifyAddrListener(); nsNotifyAddrListener();
nsresult Init(void); nsresult Init(void);
void CheckLinkStatus(void);
protected: protected:
class ChangeEvent : public nsRunnable { class ChangeEvent : public nsRunnable {
@ -48,16 +49,22 @@ protected:
bool mCheckAttempted; bool mCheckAttempted;
nsresult Shutdown(void); nsresult Shutdown(void);
nsresult SendEventToUI(const char *aEventID); nsresult SendEvent(const char *aEventID);
DWORD CheckAdaptersAddresses(void); 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); bool CheckICSStatus(PWCHAR aAdapterName);
void CheckLinkStatus(void);
nsCOMPtr<nsIThread> mThread; nsCOMPtr<nsIThread> mThread;
HANDLE mShutdownEvent; 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_ */ #endif /* NSNOTIFYADDRLISTENER_H_ */