summaryrefslogtreecommitdiffstats
path: root/netwerk/system/win32
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/system/win32')
-rw-r--r--netwerk/system/win32/moz.build12
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp743
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h100
3 files changed, 855 insertions, 0 deletions
diff --git a/netwerk/system/win32/moz.build b/netwerk/system/win32/moz.build
new file mode 100644
index 000000000..31b46b6b5
--- /dev/null
+++ b/netwerk/system/win32/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += [
+ 'nsNotifyAddrListener.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp
new file mode 100644
index 000000000..5d1ec3a61
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// We define this to make our use of inet_ntoa() pass. The "proper" function
+// inet_ntop() doesn't exist on Windows XP.
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+
+#include <stdarg.h>
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winuser.h>
+#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 "mozilla/Logging.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Services.h"
+#include "nsCRT.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Telemetry.h"
+
+#include <iptypes.h>
+#include <iphlpapi.h>
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+static HMODULE sNetshell;
+static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties;
+
+static HMODULE sIphlpapi;
+static decltype(NotifyIpInterfaceChange)* sNotifyIpInterfaceChange;
+static decltype(CancelMibChangeNotify2)* sCancelMibChangeNotify2;
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+#define NETWORK_NOTIFY_IPV6_PREF "network.notify.IPv6"
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+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) {
+ sNetshell = LoadLibraryW(L"Netshell.dll");
+ if (sNetshell) {
+ sNcFreeNetconProperties = (decltype(NcFreeNetconProperties)*)
+ GetProcAddress(sNetshell, "NcFreeNetconProperties");
+ }
+ }
+}
+
+static void FreeDynamicLibraries(void)
+{
+ if (sNetshell) {
+ sNcFreeNetconProperties = nullptr;
+ FreeLibrary(sNetshell);
+ sNetshell = nullptr;
+ }
+ if (sIphlpapi) {
+ sNotifyIpInterfaceChange = nullptr;
+ sCancelMibChangeNotify2 = nullptr;
+ FreeLibrary(sIphlpapi);
+ sIphlpapi = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNotifyAddrListener,
+ nsINetworkLinkService,
+ nsIRunnable,
+ nsIObserver)
+
+nsNotifyAddrListener::nsNotifyAddrListener()
+ : mLinkUp(true) // assume true by default
+ , mStatusKnown(false)
+ , mCheckAttempted(false)
+ , mCheckEvent(nullptr)
+ , mShutdown(false)
+ , mIPInterfaceChecksum(0)
+ , mAllowChangedEvent(true)
+ , mIPv6Changes(false)
+ , mCoalescingActive(false)
+{
+ InitIphlpapi();
+}
+
+nsNotifyAddrListener::~nsNotifyAddrListener()
+{
+ NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
+ FreeDynamicLibraries();
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetIsLinkUp(bool *aIsUp)
+{
+ if (!mCheckAttempted && !mStatusKnown) {
+ mCheckAttempted = true;
+ CheckLinkStatus();
+ }
+
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetLinkStatusKnown(bool *aIsUp)
+{
+ *aIsUp = mStatusKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetLinkType(uint32_t *aLinkType)
+{
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ // XXX This function has not yet been implemented for this platform
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+}
+
+static bool macAddr(BYTE addr[], DWORD len, char *buf, size_t buflen)
+{
+ buf[0] = '\0';
+ if (!addr || !len || (len * 3 > buflen)) {
+ return false;
+ }
+
+ for (DWORD i = 0; i < len; ++i) {
+ sprintf_s(buf + (i * 3), sizeof(buf + (i * 3)),
+ "%02x%s", addr[i], (i == len-1) ? "" : ":");
+ }
+ return true;
+}
+
+bool nsNotifyAddrListener::findMac(char *gateway)
+{
+ // query for buffer size needed
+ DWORD dwActualSize = 0;
+ bool found = FALSE;
+
+ // GetIpNetTable gets the IPv4 to physical address mapping table
+ DWORD status = GetIpNetTable(NULL, &dwActualSize, FALSE);
+ if (status == ERROR_INSUFFICIENT_BUFFER) {
+ // the expected route, now with a known buffer size
+ UniquePtr <char[]>buf(new char[dwActualSize]);
+ PMIB_IPNETTABLE pIpNetTable =
+ reinterpret_cast<PMIB_IPNETTABLE>(&buf[0]);
+
+ status = GetIpNetTable(pIpNetTable, &dwActualSize, FALSE);
+
+ if (status == NO_ERROR) {
+ for (DWORD i = 0; i < pIpNetTable->dwNumEntries; ++i) {
+ DWORD dwCurrIndex = pIpNetTable->table[i].dwIndex;
+ char hw[256];
+
+ if (!macAddr(pIpNetTable->table[i].bPhysAddr,
+ pIpNetTable->table[i].dwPhysAddrLen,
+ hw, sizeof(hw))) {
+ // failed to get the MAC
+ continue;
+ }
+
+ struct in_addr addr;
+ addr.s_addr = pIpNetTable->table[i].dwAddr;
+
+ if (!strcmp(gateway, inet_ntoa(addr))) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ Base64Encode(newString, output);
+ LOG(("networkid: id %s\n", output.get()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+// returns 'true' when the gw is found and stored
+static bool defaultgw(char *aGateway, size_t aGatewayLen)
+{
+ PMIB_IPFORWARDTABLE pIpForwardTable = NULL;
+
+ DWORD dwSize = 0;
+ if (GetIpForwardTable(NULL, &dwSize, 0) != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ UniquePtr <char[]>buf(new char[dwSize]);
+ pIpForwardTable = reinterpret_cast<PMIB_IPFORWARDTABLE>(&buf[0]);
+
+ // Note that the IPv4 addresses returned in GetIpForwardTable entries are
+ // in network byte order
+
+ DWORD retVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0);
+ if (retVal == NO_ERROR) {
+ for (unsigned int i = 0; i < pIpForwardTable->dwNumEntries; ++i) {
+ // Convert IPv4 addresses to strings
+ struct in_addr IpAddr;
+ IpAddr.S_un.S_addr = static_cast<u_long>
+ (pIpForwardTable->table[i].dwForwardDest);
+ char *ipStr = inet_ntoa(IpAddr);
+ if (ipStr && !strcmp("0.0.0.0", ipStr)) {
+ // Default gateway!
+ IpAddr.S_un.S_addr = static_cast<u_long>
+ (pIpForwardTable->table[i].dwForwardNextHop);
+ ipStr = inet_ntoa(IpAddr);
+ if (ipStr) {
+ strcpy_s(aGateway, aGatewayLen, ipStr);
+ return true;
+ }
+ }
+ } // for loop
+ }
+
+ return false;
+}
+
+//
+// Figure out the current "network identification" string.
+//
+// It detects the IP of the default gateway in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+//
+void nsNotifyAddrListener::calculateNetworkId(void)
+{
+ bool found = FALSE;
+ char gateway[128];
+ if (defaultgw(gateway, sizeof(gateway) )) {
+ found = findMac(gateway);
+ }
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+// 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();
+}
+
+DWORD
+nsNotifyAddrListener::nextCoalesceWaitTime()
+{
+ // check if coalescing period should continue
+ double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ calculateNetworkId();
+ SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
+ mCoalescingActive = false;
+ return INFINITE; // return default
+ } else {
+ // wait no longer than to the end of the period
+ return static_cast<DWORD>
+ (kNetworkChangeCoalescingPeriod - period);
+ }
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::Run()
+{
+ PR_SetCurrentThreadName("Link Monitor");
+
+ mStartTime = TimeStamp::Now();
+
+ calculateNetworkId();
+
+ DWORD waitTime = INFINITE;
+
+ if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || !mIPv6Changes) {
+ // 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 handles[2] = { ev, mCheckEvent };
+ OVERLAPPED overlapped = { 0 };
+ bool shuttingDown = false;
+
+ overlapped.hEvent = ev;
+ while (!shuttingDown) {
+ HANDLE h;
+ DWORD ret = NotifyAddrChange(&h, &overlapped);
+
+ if (ret == ERROR_IO_PENDING) {
+ ret = WaitForMultipleObjects(2, handles, FALSE, waitTime);
+ if (ret == WAIT_OBJECT_0) {
+ CheckLinkStatus();
+ } else if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ } else {
+ shuttingDown = true;
+ }
+ } else {
+ shuttingDown = true;
+ }
+ }
+ 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) {
+ do {
+ ret = WaitForSingleObject(mCheckEvent, waitTime);
+ if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ }
+ else {
+ break;
+ }
+ } while (ret != WAIT_FAILED);
+ sCancelMibChangeNotify2(interfacechange);
+ } else {
+ LOG(("Link Monitor: sNotifyIpInterfaceChange returned %d\n",
+ (int)ret));
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp("xpcom-shutdown-threads", topic))
+ Shutdown();
+
+ return NS_OK;
+}
+
+nsresult
+nsNotifyAddrListener::Init(void)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = observerService->AddObserver(this, "xpcom-shutdown-threads",
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Preferences::AddBoolVarCache(&mAllowChangedEvent,
+ NETWORK_NOTIFY_CHANGED_PREF, true);
+ Preferences::AddBoolVarCache(&mIPv6Changes,
+ NETWORK_NOTIFY_IPV6_PREF, false);
+
+ mCheckEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = NS_NewThread(getter_AddRefs(mThread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsNotifyAddrListener::Shutdown(void)
+{
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+
+ if (!mCheckEvent)
+ return NS_OK;
+
+ mShutdown = true;
+ SetEvent(mCheckEvent);
+
+ nsresult rv = mThread ? mThread->Shutdown() : NS_OK;
+
+ // Have to break the cycle here, otherwise nsNotifyAddrListener holds
+ // onto the thread and the thread holds onto the nsNotifyAddrListener
+ // via its mRunnable
+ mThread = nullptr;
+
+ CloseHandle(mCheckEvent);
+ mCheckEvent = nullptr;
+
+ return rv;
+}
+
+/*
+ * A network event has been registered. Delay the actual sending of the event
+ * for a while and absorb subsequent events in the mean time in an effort to
+ * squash potentially many triggers into a single event.
+ * Only ever called from the same thread.
+ */
+nsresult
+nsNotifyAddrListener::NetworkChanged()
+{
+ if (mCoalescingActive) {
+ LOG(("NetworkChanged: absorbed an event (coalescing active)\n"));
+ } else {
+ // A fresh trigger!
+ mChangeTime = TimeStamp::Now();
+ mCoalescingActive = true;
+ SetEvent(mCheckEvent);
+ LOG(("NetworkChanged: coalescing period started\n"));
+ }
+ return NS_OK;
+}
+
+/* Sends the given event. Assumes aEventID never goes out of scope (static
+ * strings are ideal).
+ */
+nsresult
+nsNotifyAddrListener::SendEvent(const char *aEventID)
+{
+ if (!aEventID)
+ return NS_ERROR_NULL_POINTER;
+
+ LOG(("SendEvent: network is '%s'\n", aEventID));
+
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID);
+ if (NS_FAILED(rv = NS_DispatchToMainThread(event)))
+ NS_WARNING("Failed to dispatch ChangeEvent");
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::ChangeEvent::Run()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(
+ mService, NS_NETWORK_LINK_TOPIC,
+ NS_ConvertASCIItoUTF16(mEventID).get());
+ 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::CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter)
+{
+ if (!aAdapter->FirstUnicastAddress)
+ return false;
+
+ LPSOCKADDR aAddress = aAdapter->FirstUnicastAddress->Address.lpSockaddr;
+ if (!aAddress)
+ return false;
+
+ PSOCKADDR_IN in_addr = (PSOCKADDR_IN)aAddress;
+ bool isGateway = (aAddress->sa_family == AF_INET &&
+ in_addr->sin_addr.S_un.S_un_b.s_b1 == 192 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b2 == 168 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b3 == 0 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b4 == 1);
+
+ if (isGateway)
+ isGateway = CheckICSStatus(aAdapter->FriendlyName);
+
+ return isGateway;
+}
+
+bool
+nsNotifyAddrListener::CheckICSStatus(PWCHAR aAdapterName)
+{
+ InitNetshellLibrary();
+
+ // This method enumerates all privately shared connections and checks if some
+ // of them has the same name as the one provided in aAdapterName. If such
+ // connection is found in the collection the adapter is used as ICS gateway
+ bool isICSGatewayAdapter = false;
+
+ HRESULT hr;
+ RefPtr<INetSharingManager> netSharingManager;
+ hr = CoCreateInstance(
+ CLSID_NetSharingManager,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_INetSharingManager,
+ getter_AddRefs(netSharingManager));
+
+ RefPtr<INetSharingPrivateConnectionCollection> privateCollection;
+ if (SUCCEEDED(hr)) {
+ hr = netSharingManager->get_EnumPrivateConnections(
+ ICSSC_DEFAULT,
+ getter_AddRefs(privateCollection));
+ }
+
+ RefPtr<IEnumNetSharingPrivateConnection> privateEnum;
+ if (SUCCEEDED(hr)) {
+ RefPtr<IUnknown> privateEnumUnknown;
+ hr = privateCollection->get__NewEnum(getter_AddRefs(privateEnumUnknown));
+ if (SUCCEEDED(hr)) {
+ hr = privateEnumUnknown->QueryInterface(
+ IID_IEnumNetSharingPrivateConnection,
+ getter_AddRefs(privateEnum));
+ }
+ }
+
+ if (SUCCEEDED(hr)) {
+ ULONG fetched;
+ VARIANT connectionVariant;
+ while (!isICSGatewayAdapter &&
+ SUCCEEDED(hr = privateEnum->Next(1, &connectionVariant,
+ &fetched)) &&
+ fetched) {
+ if (connectionVariant.vt != VT_UNKNOWN) {
+ // We should call VariantClear here but it needs to link
+ // with oleaut32.lib that produces a Ts incrase about 10ms
+ // that is undesired. As it is quit unlikely the result would
+ // be of a different type anyway, let's pass the variant
+ // unfreed here.
+ NS_ERROR("Variant of unexpected type, expecting VT_UNKNOWN, we probably leak it!");
+ continue;
+ }
+
+ RefPtr<INetConnection> connection;
+ if (SUCCEEDED(connectionVariant.punkVal->QueryInterface(
+ IID_INetConnection,
+ getter_AddRefs(connection)))) {
+ connectionVariant.punkVal->Release();
+
+ NETCON_PROPERTIES *properties;
+ if (SUCCEEDED(connection->GetProperties(&properties))) {
+ if (!wcscmp(properties->pszwName, aAdapterName))
+ isICSGatewayAdapter = true;
+
+ if (sNcFreeNetconProperties)
+ sNcFreeNetconProperties(properties);
+ }
+ }
+ }
+ }
+
+ return isICSGatewayAdapter;
+}
+
+DWORD
+nsNotifyAddrListener::CheckAdaptersAddresses(void)
+{
+ ULONG len = 16384;
+
+ PIP_ADAPTER_ADDRESSES adapterList = (PIP_ADAPTER_ADDRESSES) moz_xmalloc(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(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(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) {
+ bool linkUp = false;
+
+ 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(adapterList);
+
+ if (mLinkUp) {
+ /* Store the checksum only if one or more interfaces are up */
+ mIPInterfaceChecksum = sum;
+ }
+
+ CoUninitialize();
+
+ return ret;
+}
+
+/**
+ * Checks the status of all network adapters. If one is up and has a valid IP
+ * address, sets mLinkUp to true. Sets mStatusKnown to true if the link status
+ * is definitive.
+ */
+void
+nsNotifyAddrListener::CheckLinkStatus(void)
+{
+ DWORD ret;
+ const char *event;
+ bool prevLinkUp = mLinkUp;
+ ULONG prevCsum = mIPInterfaceChecksum;
+
+ LOG(("check status of all network adapters\n"));
+
+ // 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 (mLinkUp && (prevCsum != mIPInterfaceChecksum)) {
+ TimeDuration since = TimeStamp::Now() - mStartTime;
+
+ // Network is online. Topology has changed. Always send CHANGED
+ // before UP - if allowed to and having cooled down.
+ if (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) {
+ NetworkChanged();
+ }
+ }
+ 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
new file mode 100644
index 000000000..47a9ed645
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NSNOTIFYADDRLISTENER_H_
+#define NSNOTIFYADDRLISTENER_H_
+
+#include <windows.h>
+#include <winsock2.h>
+#include <iptypes.h>
+#include "nsINetworkLinkService.h"
+#include "nsIRunnable.h"
+#include "nsIObserver.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.h"
+
+class nsNotifyAddrListener : public nsINetworkLinkService,
+ public nsIRunnable,
+ public nsIObserver
+{
+ virtual ~nsNotifyAddrListener();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+
+ nsNotifyAddrListener();
+
+ nsresult Init(void);
+ void CheckLinkStatus(void);
+
+protected:
+ class ChangeEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ ChangeEvent(nsINetworkLinkService *aService, const char *aEventID)
+ : mService(aService), mEventID(aEventID) {
+ }
+ private:
+ nsCOMPtr<nsINetworkLinkService> mService;
+ const char *mEventID;
+ };
+
+ bool mLinkUp;
+ bool mStatusKnown;
+ bool mCheckAttempted;
+
+ nsresult Shutdown(void);
+ nsresult SendEvent(const char *aEventID);
+
+ DWORD CheckAdaptersAddresses(void);
+
+ // Checks for an Internet Connection Sharing (ICS) gateway.
+ bool CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter);
+ bool CheckICSStatus(PWCHAR aAdapterName);
+
+ nsCOMPtr<nsIThread> mThread;
+
+private:
+ // Returns the new timeout period for coalescing (or INFINITE)
+ DWORD nextCoalesceWaitTime();
+
+ // Called for every detected network change
+ nsresult NetworkChanged();
+
+ // Figure out the current network identification
+ void calculateNetworkId(void);
+ bool findMac(char *gateway);
+ nsCString mNetworkId;
+
+ HANDLE mCheckEvent;
+
+ // set true when mCheckEvent means shutdown
+ bool mShutdown;
+
+ // This is a checksum of various meta data for all network interfaces
+ // considered UP at last check.
+ ULONG mIPInterfaceChecksum;
+
+ // start time of the checking
+ mozilla::TimeStamp mStartTime;
+
+ // Network changed events are enabled
+ bool mAllowChangedEvent;
+
+ // Check for IPv6 network changes
+ bool mIPv6Changes;
+
+ // Flag set while coalescing change events
+ bool mCoalescingActive;
+
+ // Time stamp for first event during coalescing
+ mozilla::TimeStamp mChangeTime;
+};
+
+#endif /* NSNOTIFYADDRLISTENER_H_ */