Bug 939318 - Detect network interface changes on Windows. 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. It does not send any CHANGED events
during the first 2 seconds after startup.

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 and restarts
ongoing name resolves)

HttpHandler: acts on network changes
This commit is contained in:
Daniel Stenberg 2014-09-24 23:14:00 -04:00
Родитель 2128623a66
Коммит e3a15e4b5d
9 изменённых файлов: 398 добавлений и 124 удалений

Просмотреть файл

@ -10,7 +10,7 @@
/**
* Network link status monitoring service.
*/
[scriptable, uuid(f7d3be87-7403-4a1e-b89f-2797776e9b08)]
[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)]
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.
*/

Просмотреть файл

@ -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);
}
}
@ -950,13 +949,12 @@ nsIOService::Observe(nsISupports *subject,
// Break circular reference.
mProxyService = nullptr;
}
else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
} else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
if (!mOfflineForProfileChange && mManageOfflineStatus) {
TrackNetworkLinkStatusForOffline();
OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
}
}
return NS_OK;
}
@ -1054,24 +1052,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 +1083,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);
}

Просмотреть файл

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

Просмотреть файл

@ -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<nsIObserverService> 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,20 @@ 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)) {
nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
if (!strcmp(converted.get(), 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

Просмотреть файл

@ -289,6 +289,7 @@ nsHostRecord::nsHostRecord(const nsHostKey *key)
, mGetTtl(false)
#endif
, mBlacklistedCount(0)
, mResolveAgain(false)
{
host = ((char *) this) + sizeof(nsHostRecord);
memcpy((char *) host, key->host, strlen(key->host) + 1);
@ -467,6 +468,21 @@ nsHostRecord::GetPriority(uint16_t aFlags)
return nsHostRecord::DNS_PRIORITY_LOW;
}
// Returns true if the entry can be removed, or false if it was marked to get
// refreshed.
bool
nsHostRecord::RemoveOrRefresh()
{
// This condition implies that the request has been passed to the OS
// resolver. The resultant DNS record should be considered stale and not
// trusted; set a flag to ensure it is called again.
if (resolving && !onQueue) {
mResolveAgain = true;
return false;
}
return true; // can be removed now
}
//----------------------------------------------------------------------------
struct nsHostDBEnt : PLDHashEntryHdr
@ -580,6 +596,21 @@ HostDB_RemoveEntry(PLDHashTable *table,
return PL_DHASH_REMOVE;
}
static PLDHashOperator
HostDB_PruneEntry(PLDHashTable *table,
PLDHashEntryHdr *hdr,
uint32_t number,
void *arg)
{
nsHostDBEnt* ent = static_cast<nsHostDBEnt *>(hdr);
// Try to remove the record, or mark it for refresh
if (ent->rec->RemoveOrRefresh()) {
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
//----------------------------------------------------------------------------
#if TTL_AVAILABLE
@ -746,6 +777,41 @@ nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
}
}
//
// FlushCache() is what we call when the network has changed. We must not
// trust names that were resolved before this change. They may resolve
// differently now.
//
// This function removes all existing resolved host entries from the hash.
// Names that are in the pending queues can be left there. Entries in the
// cache that have 'Resolve' set true but not 'onQueue' are being resolved
// right now, so we need to mark them to get re-resolved on completion!
void
nsHostResolver::FlushCache()
{
PRCList evictionQ;
PR_INIT_CLIST(&evictionQ);
{
MutexAutoLock lock(mLock);
MoveCList(mEvictionQ, evictionQ);
mEvictionQSize = 0;
// prune the hash from all hosts already resolved
PL_DHashTableEnumerate(&mDB, HostDB_PruneEntry, nullptr);
}
if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
PRCList *node = evictionQ.next;
while (node != &evictionQ) {
nsHostRecord *rec = static_cast<nsHostRecord *>(node);
node = node->next;
NS_RELEASE(rec);
}
}
}
void
nsHostResolver::Shutdown()
{
@ -772,7 +838,7 @@ nsHostResolver::Shutdown()
{
MutexAutoLock lock(mLock);
mShutdown = true;
MoveCList(mHighQ, pendingQHigh);
@ -781,14 +847,14 @@ nsHostResolver::Shutdown()
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);
@ -1331,7 +1397,12 @@ nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const
rec->host, lifetime, grace, sDnsVariant));
}
void
//
// OnLookupComplete() checks if the resolving should be redone and if so it
// returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
//
nsHostResolver::LookupStatus
nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* result)
{
// get the list of pending callbacks for this lookup, and notify
@ -1341,6 +1412,11 @@ nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* r
{
MutexAutoLock lock(mLock);
if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) {
rec->mResolveAgain = false;
return LOOKUP_RESOLVEAGAIN;
}
// grab list of callbacks to notify
MoveCList(rec->callbacks, cbs);
@ -1419,6 +1495,8 @@ nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* r
#endif
NS_RELEASE(rec);
return LOOKUP_OK;
}
void
@ -1502,9 +1580,10 @@ nsHostResolver::ThreadFunc(void *arg)
nsResState rs;
#endif
nsHostResolver *resolver = (nsHostResolver *)arg;
nsHostRecord *rec;
nsHostRecord *rec = nullptr;
AddrInfo *ai = nullptr;
while (resolver->GetHostToLookup(&rec)) {
while (rec || resolver->GetHostToLookup(&rec)) {
LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n",
rec->host));
@ -1548,7 +1627,13 @@ nsHostResolver::ThreadFunc(void *arg)
// OnLookupComplete may release "rec", long before we lose it.
LOG(("DNS lookup thread - lookup completed for host [%s]: %s.\n",
rec->host, ai ? "success" : "failure: unknown host"));
resolver->OnLookupComplete(rec, status, ai);
if (LOOKUP_RESOLVEAGAIN == resolver->OnLookupComplete(rec, status, ai)) {
// leave 'rec' assigned and loop to make a renewed host resolve
LOG(("DNS lookup thread - Re-resolving host [%s].\n",
rec->host));
} else {
rec = nullptr;
}
}
NS_RELEASE(resolver);
LOG(("DNS lookup thread - queue empty, thread finished.\n"));

Просмотреть файл

@ -119,6 +119,9 @@ public:
};
static DnsPriority GetPriority(uint16_t aFlags);
bool RemoveOrRefresh(); // Returns whether the host record can be removed
// or needs to be refreshed
private:
friend class nsHostResolver;
@ -141,6 +144,10 @@ private:
// lifetime.
uint32_t mBlacklistedCount;
// when the results from this resolve is returned, it is not to be
// trusted, but instead a new resolve must be made!
bool mResolveAgain;
// a list of addresses associated with this record that have been reported
// as unusable. the list is kept as a set of strings to make it independent
// of gencnt.
@ -278,6 +285,11 @@ public:
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
/**
* Flush the DNS cache.
*/
void FlushCache();
private:
explicit nsHostResolver(uint32_t maxCacheEntries,
uint32_t defaultCacheEntryLifetime,
@ -287,7 +299,13 @@ private:
nsresult Init();
nsresult IssueLookup(nsHostRecord *);
bool GetHostToLookup(nsHostRecord **m);
void OnLookupComplete(nsHostRecord *, nsresult, mozilla::net::AddrInfo *);
enum LookupStatus {
LOOKUP_OK,
LOOKUP_RESOLVEAGAIN,
};
LookupStatus OnLookupComplete(nsHostRecord *, nsresult, mozilla::net::AddrInfo *);
void DeQueue(PRCList &aQ, nsHostRecord **aResult);
void ClearPendingQueue(PRCList *aPendingQueue);
nsresult ConditionallyCreateThread(nsHostRecord *rec);

Просмотреть файл

@ -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"
@ -348,6 +349,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();
@ -1780,13 +1782,12 @@ nsHttpHandler::Observe(nsISupports *subject,
{
LOG(("nsHttpHandler::Observe [topic=\"%s\"]\n", topic));
if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
nsCOMPtr<nsIPrefBranch> 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") ||
!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ) {
mHandlerActive = false;
@ -1806,37 +1807,39 @@ 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")) {
// initialize connection manager
InitConnectionMgr();
}
else if (strcmp(topic, "net:clear-active-logins") == 0) {
} else if (!strcmp(topic, "net:clear-active-logins")) {
mAuthCache.ClearAll();
mPrivateAuthCache.ClearAll();
}
else if (strcmp(topic, "net:prune-dead-connections") == 0) {
} else if (!strcmp(topic, "net:prune-dead-connections")) {
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")) {
nsCOMPtr<nsIURI> 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")) {
mPrivateAuthCache.ClearAll();
} else if (strcmp(topic, "browser:purge-session-history") == 0) {
} else if (!strcmp(topic, "browser:purge-session-history")) {
if (mConnMgr && gSocketTransportService) {
nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mConnMgr,
&nsHttpConnectionMgr::ClearConnectionHistory);
gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
}
} else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
if (mConnMgr) {
mConnMgr->PruneDeadConnections();
}
}
}
return NS_OK;

Просмотреть файл

@ -12,6 +12,11 @@
#include <ole2.h>
#include <netcon.h>
#include <objbase.h>
#include <winsock2.h>
#include <ws2ipdef.h>
#include <tcpmib.h>
#include <iphlpapi.h>
#include <netioapi.h>
#include <iprtrmib.h>
#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,
@ -59,7 +90,9 @@ nsNotifyAddrListener::nsNotifyAddrListener()
, mStatusKnown(false)
, mCheckAttempted(false)
, mShutdownEvent(nullptr)
, mIPInterfaceChecksum(0)
{
InitIphlpapi();
}
nsNotifyAddrListener::~nsNotifyAddrListener()
@ -97,36 +130,65 @@ 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<nsNotifyAddrListener*>(callerContext);
notify->CheckLinkStatus();
}
NS_IMETHODIMP
nsNotifyAddrListener::Run()
{
PR_SetCurrentThreadName("Link Monitor");
HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY);
mChangedTime = TimeStamp::Now();
HANDLE handles[2] = { ev, mShutdownEvent };
OVERLAPPED overlapped = { 0 };
bool shuttingDown = false;
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);
overlapped.hEvent = ev;
while (!shuttingDown) {
HANDLE h;
DWORD ret = NotifyAddrChange(&h, &overlapped);
HANDLE handles[2] = { ev, mShutdownEvent };
OVERLAPPED overlapped = { 0 };
bool shuttingDown = false;
if (ret == ERROR_IO_PENDING) {
ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
if (ret == WAIT_OBJECT_0) {
CheckLinkStatus();
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();
} 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 +251,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 +279,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 +386,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<PIP_ADAPTER_ADDRESSES> (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<unsigned char *>
(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 +466,49 @@ 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)) {
TimeDuration since = TimeStamp::Now() - mChangedTime;
// Network is online. Topology has changed. Always send CHANGED
// before UP - after having cooled down.
if (since.ToMilliseconds() > 2000) {
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);
}
}
}

Просмотреть файл

@ -14,6 +14,7 @@
#include "nsIObserver.h"
#include "nsThreadUtils.h"
#include "nsCOMPtr.h"
#include "mozilla/TimeStamp.h"
class nsNotifyAddrListener : public nsINetworkLinkService,
public nsIRunnable,
@ -30,6 +31,7 @@ public:
nsNotifyAddrListener();
nsresult Init(void);
void CheckLinkStatus(void);
protected:
class ChangeEvent : public nsRunnable {
@ -48,16 +50,25 @@ 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<nsIThread> mThread;
HANDLE mShutdownEvent;
private:
// This is a checksum of various meta data for all network interfaces
// considered UP at last check.
ULONG mIPInterfaceChecksum;
// time of the last sent changed event
mozilla::TimeStamp mChangedTime;
};
#endif /* NSNOTIFYADDRLISTENER_H_ */