diff options
Diffstat (limited to 'netwerk/system/win32')
-rw-r--r-- | netwerk/system/win32/moz.build | 16 | ||||
-rw-r--r-- | netwerk/system/win32/nsNotifyAddrListener.cpp | 736 | ||||
-rw-r--r-- | netwerk/system/win32/nsNotifyAddrListener.h | 99 |
3 files changed, 851 insertions, 0 deletions
diff --git a/netwerk/system/win32/moz.build b/netwerk/system/win32/moz.build new file mode 100644 index 0000000000..2d7e668c3c --- /dev/null +++ b/netwerk/system/win32/moz.build @@ -0,0 +1,16 @@ +# -*- 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", + ] + +LOCAL_INCLUDES += [ + "/netwerk/base", +] + +FINAL_LIBRARY = "xul" diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp new file mode 100644 index 0000000000..a64206fbc6 --- /dev/null +++ b/netwerk/system/win32/nsNotifyAddrListener.cpp @@ -0,0 +1,736 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set et sw=2 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 <algorithm> +#include <vector> + +#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 <netlistmgr.h> +#include <iprtrmib.h> +#include "mozilla/Logging.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsIWindowsRegKey.h" +#include "nsServiceManagerUtils.h" +#include "nsNetAddr.h" +#include "nsNotifyAddrListener.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "mozilla/Services.h" +#include "nsCRT.h" +#include "nsThreadPool.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/SHA1.h" +#include "mozilla/Base64.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Telemetry.h" +#include "../LinkServiceCommon.h" +#include <iptypes.h> +#include <iphlpapi.h> + +using namespace mozilla; + +static LazyLogModule gNotifyAddrLog("nsNotifyAddr"); +#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gNotifyAddrLog, mozilla::LogLevel::Debug) + +// period during which to absorb subsequent network change events, in +// milliseconds +static const unsigned int kNetworkChangeCoalescingPeriod = 1000; + +NS_IMPL_ISUPPORTS(nsNotifyAddrListener, nsINetworkLinkService, nsIRunnable, + nsIObserver) + +nsNotifyAddrListener::nsNotifyAddrListener() + : mLinkUp(true), // assume true by default + mStatusKnown(false), + mCheckAttempted(false), + mMutex("nsNotifyAddrListener::mMutex"), + mCheckEvent(nullptr), + mShutdown(false), + mPlatformDNSIndications(NONE_DETECTED), + mIPInterfaceChecksum(0), + mCoalescingActive(false) {} + +nsNotifyAddrListener::~nsNotifyAddrListener() { + NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed"); +} + +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; +} + +NS_IMETHODIMP +nsNotifyAddrListener::GetNetworkID(nsACString& aNetworkID) { + MutexAutoLock lock(mMutex); + aNetworkID = mNetworkId; + return NS_OK; +} + +NS_IMETHODIMP +nsNotifyAddrListener::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) { + aDnsSuffixList.Clear(); + MutexAutoLock lock(mMutex); + aDnsSuffixList.AppendElements(mDnsSuffixList); + return NS_OK; +} + +NS_IMETHODIMP +nsNotifyAddrListener::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) { + nsTArray<mozilla::net::NetAddr> addresses; + nsresult rv = GetNativeResolvers(addresses); + if (NS_FAILED(rv)) { + return rv; + } + + for (const auto& addr : addresses) { + aResolvers.AppendElement(MakeRefPtr<nsNetAddr>(&addr)); + } + return NS_OK; +} + +NS_IMETHODIMP +nsNotifyAddrListener::GetNativeResolvers( + nsTArray<mozilla::net::NetAddr>& aResolvers) { + aResolvers.Clear(); + MutexAutoLock lock(mMutex); + aResolvers.AppendElements(mDNSResolvers); + return NS_OK; +} + +NS_IMETHODIMP +nsNotifyAddrListener::GetPlatformDNSIndications( + uint32_t* aPlatformDNSIndications) { + *aPlatformDNSIndications = mPlatformDNSIndications; + return NS_OK; +} + +// +// Hash the sorted network ids +// +void nsNotifyAddrListener::HashSortedNetworkIds(std::vector<GUID> nwGUIDS, + SHA1Sum& sha1) { + std::sort(nwGUIDS.begin(), nwGUIDS.end(), [](const GUID& a, const GUID& b) { + return memcmp(&a, &b, sizeof(GUID)) < 0; + }); + + for (auto const& nwGUID : nwGUIDS) { + sha1.update(&nwGUID, sizeof(GUID)); + + if (LOG_ENABLED()) { + nsPrintfCString guid("%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", + nwGUID.Data1, nwGUID.Data2, nwGUID.Data3, + nwGUID.Data4[0], nwGUID.Data4[1], nwGUID.Data4[2], + nwGUID.Data4[3], nwGUID.Data4[4], nwGUID.Data4[5], + nwGUID.Data4[6], nwGUID.Data4[7]); + LOG(("calculateNetworkId: interface networkID: %s\n", guid.get())); + } + } +} + +// +// Figure out the current "network identification" string. +// +void nsNotifyAddrListener::calculateNetworkId(void) { + MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread"); + + // No need to recompute the networkId if we're shutting down. + if (mShutdown) { + return; + } + + if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { + return; + } + + auto unitialize = MakeScopeExit([]() { CoUninitialize(); }); + + RefPtr<INetworkListManager> nlm; + HRESULT hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_ALL, + IID_INetworkListManager, getter_AddRefs(nlm)); + if (NS_WARN_IF(FAILED(hr))) { + LOG(("CoCreateInstance error: %lX", hr)); + return; + } + RefPtr<IEnumNetworks> enumNetworks; + hr = nlm->GetNetworks(NLM_ENUM_NETWORK_CONNECTED, + getter_AddRefs(enumNetworks)); + if (NS_WARN_IF(FAILED(hr))) { + LOG(("GetNetworks error: %lX", hr)); + return; + } + + // We will hash the found network ids + // for privacy reasons + SHA1Sum sha1; + + // The networks stored in enumNetworks + // are not ordered. We will sort them + // To keep a consistent hash + // regardless of the found networks order. + std::vector<GUID> nwGUIDS; + + // Consume the found networks iterator + while (true) { + RefPtr<INetwork> network; + hr = enumNetworks->Next(1, getter_AddRefs(network), nullptr); + if (hr != S_OK) { + break; + } + + GUID nwGUID; + hr = network->GetNetworkId(&nwGUID); + if (hr != S_OK) { + continue; + } + nwGUIDS.push_back(nwGUID); + } + + if (nwGUIDS.empty()) { + bool idChanged = false; + { + MutexAutoLock lock(mMutex); + if (!mNetworkId.IsEmpty()) { + idChanged = true; + } + mNetworkId.Truncate(); + } + LOG(("calculateNetworkId: no network ID - no active networks")); + Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0); + if (idChanged) { + NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr); + } + return; + } + + nsAutoCString output; + SHA1Sum::Hash digest; + HashSortedNetworkIds(nwGUIDS, sha1); + SeedNetworkId(sha1); + sha1.finish(digest); + nsCString newString(reinterpret_cast<char*>(digest), SHA1Sum::kHashSize); + nsresult rv = Base64Encode(newString, output); + if (NS_FAILED(rv)) { + { + MutexAutoLock lock(mMutex); + mNetworkId.Truncate(); + } + Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0); + LOG(("calculateNetworkId: no network ID Base64Encode error %X", + uint32_t(rv))); + return; + } + + MutexAutoLock lock(mMutex); + if (output != mNetworkId) { + mNetworkId = output; + Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); + LOG(("calculateNetworkId: new NetworkID: %s", output.get())); + NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr); + } else { + Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2); + LOG(("calculateNetworkId: same NetworkID: %s", output.get())); + } +} + +// 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) { + NotifyObservers(NS_NETWORK_LINK_TOPIC, 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() { + mStartTime = TimeStamp::Now(); + + calculateNetworkId(); + + DWORD waitTime = INFINITE; + + // Windows Vista and newer versions. + HANDLE interfacechange; + // The callback will simply invoke CheckLinkStatus() + DWORD ret = NotifyIpInterfaceChange( + StaticPrefs::network_notify_IPv6() ? AF_UNSPEC + : AF_INET, // IPv4 and IPv6 + (PIPINTERFACE_CHANGE_CALLBACK)OnInterfaceChange, + this, // pass to callback + StaticPrefs::network_notify_initial_call(), // initial notification + &interfacechange); + + if (ret == NO_ERROR) { + do { + ret = WaitForSingleObject(mCheckEvent, waitTime); + if (!mShutdown) { + waitTime = nextCoalesceWaitTime(); + } else { + break; + } + } while (ret != WAIT_FAILED); + CancelMibChangeNotify2(interfacechange); + } else { + LOG(("Link Monitor: NotifyIpInterfaceChange 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); + + mCheckEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool(); + MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(1)); + MOZ_ALWAYS_SUCCEEDS( + threadPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize)); + MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("Link Monitor"_ns)); + mThread = threadPool.forget(); + + return mThread->Dispatch(this, NS_DISPATCH_NORMAL); +} + +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->ShutdownWithTimeout(2000) : 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 aTopic/aData never goes out of scope (static + * strings are ideal). + */ +nsresult nsNotifyAddrListener::NotifyObservers(const char* aTopic, + const char* aData) { + LOG(("NotifyObservers: %s=%s\n", aTopic, aData)); + + if (mShutdown) { + LOG(("NotifyObservers call failed when called during shutdown")); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + auto runnable = [self = RefPtr<nsNotifyAddrListener>(this), aTopic, aData] { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + observerService->NotifyObservers( + static_cast<nsINetworkLinkService*>(self.get()), aTopic, + !aData ? nullptr : NS_ConvertASCIItoUTF16(aData).get()); + }; + nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsNotifyAddrListener::NotifyObservers", runnable)); + if (NS_FAILED(rv)) { + NS_WARNING( + "nsNotifyAddrListener::NotifyObservers Failed to dispatch observer " + "notification"); + } + return rv; +} + +DWORD +nsNotifyAddrListener::CheckAdaptersAddresses(void) { + MOZ_ASSERT(!NS_IsMainThread(), "Don't call this on the main thread"); + ULONG len = 16384; + + PIP_ADAPTER_ADDRESSES adapterList = (PIP_ADAPTER_ADDRESSES)moz_xmalloc(len); + + ULONG flags = GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST; + if (!StaticPrefs::network_notify_resolvers()) { + flags |= GAA_FLAG_SKIP_DNS_SERVER; + } + + 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 sumAll = 0; + + nsTArray<nsCString> dnsSuffixList; + nsTArray<mozilla::net::NetAddr> resolvers; + uint32_t platformDNSIndications = NONE_DETECTED; + if (ret == ERROR_SUCCESS) { + bool linkUp = false; + ULONG sum = 0; + + for (PIP_ADAPTER_ADDRESSES adapter = adapterList; adapter; + adapter = adapter->Next) { + if (adapter->OperStatus != IfOperStatusUp || + !adapter->FirstUnicastAddress || + adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) { + continue; + } + + LOG(("Adapter %s type: %lu", + NS_ConvertUTF16toUTF8(adapter->FriendlyName).get(), + adapter->IfType)); + + if (adapter->IfType == IF_TYPE_PPP || + adapter->IfType == IF_TYPE_PROP_VIRTUAL || + nsDependentString(adapter->FriendlyName).Find(u"VPN") != kNotFound || + nsDependentString(adapter->Description).Find(u"VPN") != kNotFound) { + LOG(("VPN connection found")); + platformDNSIndications |= VPN_DETECTED; + } + + sum <<= 2; + // Add chars from AdapterName to the checksum. + for (int i = 0; adapter->AdapterName[i]; ++i) { + 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]; + } + } + + for (IP_ADAPTER_DNS_SERVER_ADDRESS* pDnServer = + adapter->FirstDnsServerAddress; + pDnServer; pDnServer = pDnServer->Next) { + mozilla::net::NetAddr addr; + if (pDnServer->Address.lpSockaddr->sa_family == AF_INET) { + const struct sockaddr_in* sin = + (const struct sockaddr_in*)pDnServer->Address.lpSockaddr; + addr.inet.family = AF_INET; + addr.inet.ip = sin->sin_addr.s_addr; + addr.inet.port = sin->sin_port; + } else if (pDnServer->Address.lpSockaddr->sa_family == AF_INET6) { + const struct sockaddr_in6* sin6 = + (const struct sockaddr_in6*)pDnServer->Address.lpSockaddr; + addr.inet6.family = AF_INET6; + memcpy(&addr.inet6.ip.u8, &sin6->sin6_addr, sizeof(addr.inet6.ip.u8)); + addr.inet6.port = sin6->sin6_port; + } else { + NS_WARNING("Unexpected addr type"); + continue; + } + + if (LOG_ENABLED()) { + char buf[100]; + addr.ToStringBuffer(buf, 100); + LOG(("Found DNS resolver=%s", buf)); + } + resolvers.AppendElement(addr); + } + + if (StaticPrefs::network_notify_dnsSuffixList()) { + nsCString suffix = NS_ConvertUTF16toUTF8(adapter->DnsSuffix); + if (!suffix.IsEmpty()) { + LOG((" found DNS suffix=%s\n", suffix.get())); + dnsSuffixList.AppendElement(suffix); + } + } + + linkUp = true; + sumAll ^= sum; + } + mLinkUp = linkUp; + mStatusKnown = true; + } + free(adapterList); + + if (mLinkUp) { + /* Store the checksum only if one or more interfaces are up */ + mIPInterfaceChecksum = sumAll; + } + + CoUninitialize(); + + if (StaticPrefs::network_notify_dnsSuffixList()) { + // It seems that the only way to retrieve non-connection specific DNS + // suffixes is via the Windows registry. + + // This function takes a registry path. If aRegPath\\SearchList is + // found and successfully parsed, then it returns true. Otherwise it + // returns false. + auto checkRegistry = [&dnsSuffixList](const nsAString& aRegPath) -> bool { + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_FAILED(rv)) { + LOG((" creating nsIWindowsRegKey failed\n")); + return false; + } + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, aRegPath, + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + LOG((" opening registry key failed\n")); + return false; + } + nsAutoString wideSuffixString; + rv = regKey->ReadStringValue(u"SearchList"_ns, wideSuffixString); + if (NS_FAILED(rv)) { + LOG((" reading registry string value failed\n")); + return false; + } + + // Normally the key should not contain whitespace, but editing the + // registry manually or through gpedit doesn't alway enforce this. + nsAutoCString list = NS_ConvertUTF16toUTF8(wideSuffixString); + list.StripWhitespace(); + for (const nsACString& suffix : list.Split(',')) { + LOG((" appending DNS suffix from registry: %s\n", + suffix.BeginReading())); + if (!suffix.IsEmpty()) { + dnsSuffixList.AppendElement(suffix); + } + } + + return true; + }; + + // The Local group policy overrides the user set suffix list, so we must + // first check the registry key that is sets by gpedit, and if that fails we + // fall back to the one that is set by the user. + if (!checkRegistry(nsLiteralString( + u"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient"))) { + checkRegistry(nsLiteralString( + u"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters")); + } + } + + auto registryChildCount = [](const nsAString& aRegPath) -> uint32_t { + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_FAILED(rv)) { + LOG((" creating nsIWindowsRegKey failed\n")); + return 0; + } + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, aRegPath, + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + LOG((" opening registry key failed\n")); + return 0; + } + + uint32_t count = 0; + rv = regKey->GetChildCount(&count); + if (NS_FAILED(rv)) { + return 0; + } + + return count; + }; + + if (StaticPrefs::network_notify_checkForProxies()) { + if (registryChildCount(u"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\" + "Parameters\\DnsConnections"_ns) > 0 || + registryChildCount(u"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\" + "Parameters\\DnsConnectionsProxies"_ns) > 0) { + platformDNSIndications |= PROXY_DETECTED; + } + } + + if (StaticPrefs::network_notify_checkForNRPT()) { + if (registryChildCount(u"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\" + "Parameters\\DnsPolicyConfig"_ns) > 0 || + registryChildCount(u"SOFTWARE\\Policies\\Microsoft\\Windows NT\\" + "DNSClient\\DnsPolicyConfig"_ns) > 0) { + platformDNSIndications |= NRPT_DETECTED; + } + } + + { + MutexAutoLock lock(mMutex); + mDnsSuffixList = std::move(dnsSuffixList); + mDNSResolvers = std::move(resolvers); + mPlatformDNSIndications = platformDNSIndications; + } + + NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr); + + calculateNetworkId(); + + 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) { + NotifyObservers(NS_NETWORK_LINK_TOPIC, 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 (StaticPrefs::network_notify_changed() && + (since.ToMilliseconds() > 2000)) { + NetworkChanged(); + } + } + if (prevLinkUp != mLinkUp) { + // UP/DOWN status changed, send appropriate UP/DOWN event + NotifyObservers(NS_NETWORK_LINK_TOPIC, 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 0000000000..30244685f2 --- /dev/null +++ b/netwerk/system/win32/nsNotifyAddrListener.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set et sw=2 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 "nsString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsThreadPool.h" +#include "nsCOMPtr.h" +#include "mozilla/Atomics.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Mutex.h" +#include "mozilla/SHA1.h" +#include "mozilla/net/DNS.h" + +class nsIThreadPool; + +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); + static void HashSortedNetworkIds(const std::vector<GUID> nwGUIDS, + mozilla::SHA1Sum& sha1); + + protected: + bool mLinkUp; + bool mStatusKnown; + bool mCheckAttempted; + + nsresult Shutdown(void); + nsresult NotifyObservers(const char* aTopic, const char* aData); + + DWORD CheckAdaptersAddresses(void); + + // This threadpool only ever holds 1 thread. It is a threadpool and not a + // regular thread so that we may call shutdownWithTimeout on it. + nsCOMPtr<nsIThreadPool> 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); + + mozilla::Mutex mMutex MOZ_UNANNOTATED; + nsCString mNetworkId; + nsTArray<nsCString> mDnsSuffixList; + nsTArray<mozilla::net::NetAddr> mDNSResolvers; + + HANDLE mCheckEvent; + + // set true when mCheckEvent means shutdown + mozilla::Atomic<bool> mShutdown; + + // Contains a set of flags that codify the reasons for which + // the platform indicates DNS should be used instead of TRR. + mozilla::Atomic<uint32_t, mozilla::Relaxed> mPlatformDNSIndications; + + // 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; + + // Flag set while coalescing change events + bool mCoalescingActive; + + // Time stamp for first event during coalescing + mozilla::TimeStamp mChangeTime; +}; + +#endif /* NSNOTIFYADDRLISTENER_H_ */ |