summaryrefslogtreecommitdiffstats
path: root/netwerk/system/win32
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/system/win32
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/system/win32')
-rw-r--r--netwerk/system/win32/moz.build16
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp736
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h99
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_ */