summaryrefslogtreecommitdiffstats
path: root/netwerk/system
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/system')
-rw-r--r--netwerk/system/LinkServiceCommon.cpp30
-rw-r--r--netwerk/system/LinkServiceCommon.h17
-rw-r--r--netwerk/system/NetworkLinkServiceDefines.h21
-rw-r--r--netwerk/system/android/moz.build14
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.cpp243
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.h48
-rw-r--r--netwerk/system/linux/moz.build16
-rw-r--r--netwerk/system/linux/nsNetworkLinkService.cpp211
-rw-r--r--netwerk/system/linux/nsNetworkLinkService.h47
-rw-r--r--netwerk/system/mac/moz.build11
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.h88
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm993
-rw-r--r--netwerk/system/moz.build23
-rw-r--r--netwerk/system/netlink/NetlinkService.cpp1903
-rw-r--r--netwerk/system/netlink/NetlinkService.h168
-rw-r--r--netwerk/system/netlink/moz.build12
-rw-r--r--netwerk/system/win32/moz.build16
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp736
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h99
19 files changed, 4696 insertions, 0 deletions
diff --git a/netwerk/system/LinkServiceCommon.cpp b/netwerk/system/LinkServiceCommon.cpp
new file mode 100644
index 0000000000..9c9ef986d7
--- /dev/null
+++ b/netwerk/system/LinkServiceCommon.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "LinkServiceCommon.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/TimeStamp.h"
+#include "nsID.h"
+
+using namespace mozilla;
+
+void SeedNetworkId(SHA1Sum& aSha1) {
+ static Maybe<nsID> seed = ([]() {
+ Maybe<nsID> uuid(std::in_place);
+ if (NS_FAILED(nsID::GenerateUUIDInPlace(*uuid))) {
+ uuid.reset();
+ }
+ return uuid;
+ })();
+ if (seed) {
+ aSha1.update(seed.ptr(), sizeof(*seed));
+ } else {
+ TimeStamp timestamp = TimeStamp::ProcessCreation();
+ aSha1.update(&timestamp, sizeof(timestamp));
+ }
+}
diff --git a/netwerk/system/LinkServiceCommon.h b/netwerk/system/LinkServiceCommon.h
new file mode 100644
index 0000000000..fc96e7ad95
--- /dev/null
+++ b/netwerk/system/LinkServiceCommon.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 LINK_SERVICE_COMMON_H_
+#define LINK_SERVICE_COMMON_H_
+
+namespace mozilla {
+class SHA1Sum;
+}
+
+// Add a seed to the computed network ID to prevent user linkability.
+void SeedNetworkId(mozilla::SHA1Sum& aSha1);
+
+#endif // LINK_SERVICE_COMMON_H_
diff --git a/netwerk/system/NetworkLinkServiceDefines.h b/netwerk/system/NetworkLinkServiceDefines.h
new file mode 100644
index 0000000000..b0b1892f72
--- /dev/null
+++ b/netwerk/system/NetworkLinkServiceDefines.h
@@ -0,0 +1,21 @@
+/* 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 NETWORK_LINK_SERVICE_DEFINES_H_
+#define NETWORK_LINK_SERVICE_DEFINES_H_
+
+namespace mozilla {
+namespace net {
+
+// IP addresses that are used to check the route for public traffic. They are
+// used just to check routing rules, no packets are sent to those hosts.
+// Initially, addresses of host detectportal.firefox.com were used but they
+// don't necessarily need to be updated when addresses of this host change.
+#define ROUTE_CHECK_IPV4 "23.219.91.27"
+#define ROUTE_CHECK_IPV6 "2a02:26f0:40::17db:5b1b"
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NETWORK_LINK_SERVICE_DEFINES_H_
diff --git a/netwerk/system/android/moz.build b/netwerk/system/android/moz.build
new file mode 100644
index 0000000000..c60b3e5871
--- /dev/null
+++ b/netwerk/system/android/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+SOURCES += [
+ "nsAndroidNetworkLinkService.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.cpp b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
new file mode 100644
index 0000000000..850ac5b565
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "nsAndroidNetworkLinkService.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIObserverService.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+
+#include "AndroidBridge.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/jni/Utils.h"
+
+namespace java = mozilla::java;
+namespace jni = mozilla::jni;
+
+static mozilla::LazyLogModule gNotifyAddrLog("nsAndroidNetworkLinkService");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAndroidNetworkLinkService, nsINetworkLinkService,
+ nsIObserver)
+
+nsAndroidNetworkLinkService::nsAndroidNetworkLinkService()
+ : mStatusIsKnown(false) {}
+
+nsresult nsAndroidNetworkLinkService::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mNetlinkSvc = new mozilla::net::NetlinkService();
+ rv = mNetlinkSvc->Init(this);
+ if (NS_FAILED(rv)) {
+ mNetlinkSvc = nullptr;
+ LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsAndroidNetworkLinkService::Shutdown() {
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+
+ if (mNetlinkSvc) {
+ mNetlinkSvc->Shutdown();
+ mNetlinkSvc = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp("xpcom-shutdown-threads", topic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+ if (mNetlinkSvc && mStatusIsKnown) {
+ mNetlinkSvc->GetIsLinkUp(aIsUp);
+ return NS_OK;
+ }
+
+ if (!mozilla::AndroidBridge::Bridge()) {
+ // Fail soft here and assume a connection exists
+ NS_WARNING("GetIsLinkUp is not supported without a bridge connection");
+ *aIsUp = true;
+ return NS_OK;
+ }
+
+ *aIsUp = java::GeckoAppShell::IsNetworkLinkUp();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+ if (mStatusIsKnown) {
+ *aIsKnown = true;
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(mozilla::AndroidBridge::Bridge(), NS_ERROR_NOT_IMPLEMENTED);
+
+ *aIsKnown = java::GeckoAppShell::IsNetworkLinkKnown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ if (!mozilla::AndroidBridge::Bridge()) {
+ // Fail soft here and assume a connection exists
+ NS_WARNING("GetLinkType is not supported without a bridge connection");
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+ }
+
+ *aLinkType = java::GeckoAppShell::GetNetworkLinkType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetlinkSvc->GetNetworkID(aNetworkID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetDnsSuffixList(
+ nsTArray<nsCString>& aDnsSuffixList) {
+ aDnsSuffixList.Clear();
+ if (!jni::IsAvailable()) {
+ NS_WARNING("GetDnsSuffixList is not supported without JNI");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ jni::String::LocalRef suffixList;
+ nsresult rv = java::GeckoAppShell::GetDNSDomains(&suffixList);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!suffixList || !suffixList->Length()) {
+ return NS_OK;
+ }
+
+ nsAutoCString list(suffixList->ToCString());
+ for (const nsACString& suffix : list.Split(',')) {
+ aDnsSuffixList.AppendElement(suffix);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetResolvers(
+ nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsAndroidNetworkLinkService::OnNetworkChanged() {
+ if (mozilla::StaticPrefs::network_notify_changed()) {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnNetworkChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC,
+ NS_NETWORK_LINK_DATA_CHANGED);
+ }));
+ }
+}
+
+void nsAndroidNetworkLinkService::OnNetworkIDChanged() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnNetworkIDChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+ }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkUp() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsAndroidNetworkLinkService::OnLinkUp", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UP);
+ }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkDown() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnLinkDown", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_DOWN);
+ }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
+
+void nsAndroidNetworkLinkService::OnDnsSuffixListUpdated() {
+ RefPtr<nsAndroidNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAndroidNetworkLinkService::OnDnsSuffixListUpdated", [self]() {
+ self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+ }));
+}
+
+/* Sends the given event. Assumes aTopic/aData never goes out of scope (static
+ * strings are ideal).
+ */
+void nsAndroidNetworkLinkService::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsAndroidNetworkLinkService::NotifyObservers: topic:%s data:%s\n",
+ aTopic, aData ? aData : ""));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(this), aTopic,
+ aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
+ }
+}
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.h b/netwerk/system/android/nsAndroidNetworkLinkService.h
new file mode 100644
index 0000000000..eaa3e2de4a
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 NSANDROIDNETWORKLINKSERVICE_H_
+#define NSANDROIDNETWORKLINKSERVICE_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "../netlink/NetlinkService.h"
+
+class nsAndroidNetworkLinkService
+ : public nsINetworkLinkService,
+ public nsIObserver,
+ public mozilla::net::NetlinkServiceListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsAndroidNetworkLinkService();
+
+ nsresult Init();
+
+ void OnNetworkChanged() override;
+ void OnNetworkIDChanged() override;
+ void OnLinkUp() override;
+ void OnLinkDown() override;
+ void OnLinkStatusKnown() override;
+ void OnDnsSuffixListUpdated() override;
+
+ private:
+ virtual ~nsAndroidNetworkLinkService() = default;
+
+ // Called when xpcom-shutdown-threads is received.
+ nsresult Shutdown();
+
+ // Sends the network event.
+ void NotifyObservers(const char* aTopic, const char* aData);
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
+
+ RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
+};
+
+#endif /* NSANDROIDNETWORKLINKSERVICE_H_ */
diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build
new file mode 100644
index 0000000000..8ddc517c3e
--- /dev/null
+++ b/netwerk/system/linux/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"] == "Linux":
+ SOURCES += [
+ "nsNetworkLinkService.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/linux/nsNetworkLinkService.cpp b/netwerk/system/linux/nsNetworkLinkService.cpp
new file mode 100644
index 0000000000..363b058eed
--- /dev/null
+++ b/netwerk/system/linux/nsNetworkLinkService.cpp
@@ -0,0 +1,211 @@
+/* -*- 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/. */
+
+#include "nsIObserverService.h"
+#include "nsNetworkLinkService.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+#include "nsNetAddr.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNetworkLinkService");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
+
+nsNetworkLinkService::nsNetworkLinkService() : mStatusIsKnown(false) {}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetlinkSvc->GetIsLinkUp(aIsUp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+ *aIsKnown = mStatusIsKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::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
+nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetlinkSvc->GetNetworkID(aNetworkID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mNetlinkSvc->GetDnsSuffixList(aDnsSuffixList);
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::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
+nsNetworkLinkService::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ if (!mNetlinkSvc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mNetlinkSvc->GetResolvers(aResolvers);
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp("xpcom-shutdown-threads", topic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNetworkLinkService::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mNetlinkSvc = new mozilla::net::NetlinkService();
+ rv = mNetlinkSvc->Init(this);
+ if (NS_FAILED(rv)) {
+ mNetlinkSvc = nullptr;
+ LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsNetworkLinkService::Shutdown() {
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+ }
+
+ if (mNetlinkSvc) {
+ mNetlinkSvc->Shutdown();
+ mNetlinkSvc = nullptr;
+ }
+
+ return NS_OK;
+}
+
+void nsNetworkLinkService::OnNetworkChanged() {
+ if (StaticPrefs::network_notify_changed()) {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::OnNetworkChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC,
+ NS_NETWORK_LINK_DATA_CHANGED);
+ }));
+ }
+}
+
+void nsNetworkLinkService::OnNetworkIDChanged() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::OnNetworkIDChanged", [self]() {
+ self->NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+ }));
+}
+
+void nsNetworkLinkService::OnLinkUp() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsNetworkLinkService::OnLinkUp", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UP);
+ }));
+}
+
+void nsNetworkLinkService::OnLinkDown() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsNetworkLinkService::OnLinkDown", [self]() {
+ self->NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_DOWN);
+ }));
+}
+
+void nsNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
+
+void nsNetworkLinkService::OnDnsSuffixListUpdated() {
+ RefPtr<nsNetworkLinkService> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::OnDnsSuffixListUpdated", [self]() {
+ self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+ }));
+}
+
+/* Sends the given event. Assumes aTopic/aData never goes out of scope (static
+ * strings are ideal).
+ */
+void nsNetworkLinkService::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsNetworkLinkService::NotifyObservers: topic:%s data:%s\n", aTopic,
+ aData ? aData : ""));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(this), aTopic,
+ aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
+ }
+}
diff --git a/netwerk/system/linux/nsNetworkLinkService.h b/netwerk/system/linux/nsNetworkLinkService.h
new file mode 100644
index 0000000000..d2c4521122
--- /dev/null
+++ b/netwerk/system/linux/nsNetworkLinkService.h
@@ -0,0 +1,47 @@
+/* -*- 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 NSNETWORKLINKSERVICE_LINUX_H_
+#define NSNETWORKLINKSERVICE_LINUX_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "../netlink/NetlinkService.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Atomics.h"
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver,
+ public mozilla::net::NetlinkServiceListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsNetworkLinkService();
+ nsresult Init();
+
+ void OnNetworkChanged() override;
+ void OnNetworkIDChanged() override;
+ void OnLinkUp() override;
+ void OnLinkDown() override;
+ void OnLinkStatusKnown() override;
+ void OnDnsSuffixListUpdated() override;
+
+ private:
+ virtual ~nsNetworkLinkService() = default;
+
+ // Called when xpcom-shutdown-threads is received.
+ nsresult Shutdown();
+
+ // Sends the network event.
+ void NotifyObservers(const char* aTopic, const char* aData);
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
+
+ RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
+};
+
+#endif /* NSNETWORKLINKSERVICE_LINUX_H_ */
diff --git a/netwerk/system/mac/moz.build b/netwerk/system/mac/moz.build
new file mode 100644
index 0000000000..f6436fab56
--- /dev/null
+++ b/netwerk/system/mac/moz.build
@@ -0,0 +1,11 @@
+# -*- 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/.
+
+SOURCES += [
+ "nsNetworkLinkService.mm",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h
new file mode 100644
index 0000000000..d3de10e6a5
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -0,0 +1,88 @@
+/* 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 NSNETWORKLINKSERVICEMAC_H_
+#define NSNETWORKLINKSERVICEMAC_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SHA1.h"
+
+#include <netinet/in.h>
+#include <SystemConfiguration/SCNetworkReachability.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+using prefix_and_netmask = std::pair<in6_addr, in6_addr>;
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsNetworkLinkService();
+
+ nsresult Init();
+ nsresult Shutdown();
+
+ static void HashSortedPrefixesAndNetmasks(
+ std::vector<prefix_and_netmask> prefixAndNetmaskStore,
+ mozilla::SHA1Sum* sha1);
+
+ protected:
+ virtual ~nsNetworkLinkService();
+
+ private:
+ bool mLinkUp;
+ bool mStatusKnown;
+
+ SCNetworkReachabilityRef mReachability;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mRunLoopSource;
+ SCDynamicStoreRef mStoreRef;
+
+ bool IPv4NetworkId(mozilla::SHA1Sum* sha1);
+ bool IPv6NetworkId(mozilla::SHA1Sum* sha1);
+
+ void UpdateReachability();
+ void OnIPConfigChanged();
+ void OnNetworkIdChanged();
+ void OnReachabilityChanged();
+ void NotifyObservers(const char* aTopic, const char* aData);
+ static void ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags, void* info);
+ static void NetworkConfigChanged(SCDynamicStoreRef store,
+ CFArrayRef changedKeys, void* info);
+ void calculateNetworkIdWithDelay(uint32_t aDelay);
+ void calculateNetworkIdInternal(void);
+ void DNSConfigChanged(uint32_t aDelayMs);
+ void GetDnsSuffixListInternal();
+ bool RoutingFromKernel(nsTArray<nsCString>& aHash);
+ bool RoutingTable(nsTArray<nsCString>& aHash);
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDNSSuffixList;
+
+ // The timer used to delay the calculation of network id since it takes some
+ // time to discover the gateway's MAC address.
+ nsCOMPtr<nsITimer> mNetworkIdTimer;
+
+ // Scheduled timers used to delay querying of the DNS suffix list when
+ // triggered by a network change. Guarded by mMutex.
+ nsTArray<nsCOMPtr<nsITimer>> mDNSConfigChangedTimers;
+
+ // IP address used to check the route for public traffic.
+ struct in_addr mRouteCheckIPv4;
+};
+
+#endif /* NSNETWORKLINKSERVICEMAC_H_ */
diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm
new file mode 100644
index 0000000000..83271c0712
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,993 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include <numeric>
+#include <vector>
+#include <algorithm>
+
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <resolv.h>
+
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "nsNetworkLinkService.h"
+#include "../../base/IPv6Utils.h"
+#include "../LinkServiceCommon.h"
+#include "../NetworkLinkServiceDefines.h"
+
+#import <Cocoa/Cocoa.h>
+#import <netinet/in.h>
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+// See bug 1584165. Sometimes the ARP table is empty or doesn't have
+// the entry of gateway after the network change, so we'd like to delay
+// the calaulation of network id.
+static const uint32_t kNetworkIdDelayAfterChange = 3000;
+
+// When you remove search domains from the settings page and hit Apply a
+// network change event is generated, but res.dnsrch is not updated to the
+// correct values. Thus, after a network change, we add a small delay to
+// the runnable so the OS has the chance to update the values.
+static const uint32_t kDNSSuffixDelayAfterChange = 50;
+
+// If non-successful, extract the error code and return it. This
+// error code dance is inspired by
+// http://developer.apple.com/technotes/tn/tn1145.html
+static OSStatus getErrorCodeBool(Boolean success) {
+ OSStatus err = noErr;
+ if (!success) {
+ int scErr = ::SCError();
+ if (scErr == kSCStatusOK) {
+ scErr = kSCStatusFailed;
+ }
+ err = scErr;
+ }
+ return err;
+}
+
+// If given a NULL pointer, return the error code.
+static OSStatus getErrorCodePtr(const void* value) {
+ return getErrorCodeBool(value != nullptr);
+}
+
+// Convenience function to allow NULL input.
+static void CFReleaseSafe(CFTypeRef cf) {
+ if (cf) {
+ // "If cf is NULL, this will cause a runtime error and your
+ // application will crash." / Apple docs
+ ::CFRelease(cf);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver,
+ nsITimerCallback, nsINamed)
+
+nsNetworkLinkService::nsNetworkLinkService()
+ : mLinkUp(true),
+ mStatusKnown(false),
+ mReachability(nullptr),
+ mCFRunLoop(nullptr),
+ mRunLoopSource(nullptr),
+ mStoreRef(nullptr),
+ mMutex("nsNetworkLinkService::mMutex") {}
+
+nsNetworkLinkService::~nsNetworkLinkService() = default;
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkStatusKnown(bool* aIsUp) {
+ *aIsUp = mStatusKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::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
+nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+ MutexAutoLock lock(mMutex);
+ aNetworkID = mNetworkId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetPlatformDNSIndications(
+ uint32_t* aPlatformDNSIndications) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsNetworkLinkService::GetDnsSuffixListInternal() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ LOG(("GetDnsSuffixListInternal"));
+
+ auto sendNotification = mozilla::MakeScopeExit([self = RefPtr{this}] {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::GetDnsSuffixListInternal", [self]() {
+ self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
+ }));
+ });
+
+ nsTArray<nsCString> result;
+
+ struct __res_state res;
+ if (res_ninit(&res) == 0) {
+ for (int i = 0; i < MAXDNSRCH; i++) {
+ if (!res.dnsrch[i]) {
+ break;
+ }
+ LOG(("DNS search domain from [%s]\n", res.dnsrch[i]));
+ result.AppendElement(nsCString(res.dnsrch[i]));
+ }
+ res_nclose(&res);
+ }
+
+ MutexAutoLock lock(mMutex);
+ mDNSSuffixList = std::move(result);
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+ aDnsSuffixList.Clear();
+
+ MutexAutoLock lock(mMutex);
+ aDnsSuffixList.AppendElements(mDNSSuffixList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetNativeResolvers(
+ nsTArray<mozilla::net::NetAddr>& aResolvers) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#ifndef SA_SIZE
+# define SA_SIZE(sa) \
+ ((!(sa) || ((struct sockaddr*)(sa))->sa_len == 0) \
+ ? sizeof(uint32_t) \
+ : 1 + ((((struct sockaddr*)(sa))->sa_len - 1) | \
+ (sizeof(uint32_t) - 1)))
+#endif
+
+static bool getMac(struct sockaddr_dl* sdl, char* buf, size_t bufsize) {
+ unsigned char* mac;
+ mac = (unsigned char*)LLADDR(sdl);
+
+ if (sdl->sdl_alen != 6) {
+ LOG(("networkid: unexpected MAC size %u", sdl->sdl_alen));
+ return false;
+ }
+
+ snprintf(buf, bufsize, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1],
+ mac[2], mac[3], mac[4], mac[5]);
+ return true;
+}
+
+/* If the IP matches, get the MAC and return true */
+static bool matchIp(struct sockaddr_dl* sdl, struct sockaddr_inarp* addr,
+ char* ip, char* buf, size_t bufsize) {
+ if (sdl->sdl_alen) {
+ if (!strcmp(inet_ntoa(addr->sin_addr), ip)) {
+ if (getMac(sdl, buf, bufsize)) {
+ return true; /* done! */
+ }
+ }
+ }
+ return false; /* continue */
+}
+
+/*
+ * Scan for the 'IP' address in the ARP table and store the corresponding MAC
+ * address in 'mac'. The output buffer is 'maclen' bytes big.
+ *
+ * Returns 'true' if it found the IP and returns a MAC.
+ */
+static bool scanArp(char* ip, char* mac, size_t maclen) {
+ int mib[6];
+ char *lim, *next;
+ int st;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_FLAGS;
+ mib[5] = RTF_LLINFO;
+
+ size_t needed;
+ auto allocateBuf = [&]() -> UniquePtr<char[]> {
+ // calling sysctl with a null buffer to get the minimum buffer size
+ if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
+ return nullptr;
+ }
+
+ if (needed == 0) {
+ LOG(("scanArp: empty table"));
+ return nullptr;
+ }
+
+ return MakeUnique<char[]>(needed);
+ };
+
+ UniquePtr<char[]> buf = allocateBuf();
+ if (!buf) {
+ return false;
+ }
+
+ st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
+ // If errno is ENOMEM, try to allocate a new buffer and try again.
+ if (st != 0) {
+ if (errno != ENOMEM) {
+ return false;
+ }
+
+ buf = allocateBuf();
+ if (!buf) {
+ return false;
+ }
+
+ st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
+ if (st == -1) {
+ return false;
+ }
+ }
+
+ lim = &buf[needed];
+
+ struct rt_msghdr* rtm;
+ for (next = &buf[0]; next < lim; next += rtm->rtm_msglen) {
+ rtm = reinterpret_cast<struct rt_msghdr*>(next);
+ struct sockaddr_inarp* sin2 =
+ reinterpret_cast<struct sockaddr_inarp*>(rtm + 1);
+ struct sockaddr_dl* sdl =
+ reinterpret_cast<struct sockaddr_dl*>((char*)sin2 + SA_SIZE(sin2));
+ if (matchIp(sdl, sin2, ip, mac, maclen)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Append the mac address of rtm to `stringsToHash`. If it's not in arp table,
+// append ifname and IP address.
+static bool parseHashKey(struct rt_msghdr* rtm,
+ nsTArray<nsCString>& stringsToHash,
+ bool skipDstCheck) {
+ struct sockaddr* sa;
+ struct sockaddr_in* sockin;
+ char ip[INET_ADDRSTRLEN];
+
+ // Ignore the routing table message without destination/gateway sockaddr.
+ // Destination address is needed to check if the gateway is default or
+ // overwritten by VPN. If yes, append the mac address or IP/interface name to
+ // `stringsToHash`.
+ if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) != (RTA_DST | RTA_GATEWAY)) {
+ return false;
+ }
+
+ sa = reinterpret_cast<struct sockaddr*>(rtm + 1);
+
+ struct sockaddr* destination =
+ reinterpret_cast<struct sockaddr*>((char*)sa + RTAX_DST * SA_SIZE(sa));
+ if (!destination || destination->sa_family != AF_INET) {
+ return false;
+ }
+
+ sockin = reinterpret_cast<struct sockaddr_in*>(destination);
+
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
+
+ if (!skipDstCheck && strcmp("0.0.0.0", ip)) {
+ return false;
+ }
+
+ struct sockaddr* gateway = reinterpret_cast<struct sockaddr*>(
+ (char*)sa + RTAX_GATEWAY * SA_SIZE(sa));
+
+ if (!gateway) {
+ return false;
+ }
+ if (gateway->sa_family == AF_INET) {
+ sockin = reinterpret_cast<struct sockaddr_in*>(gateway);
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
+ char mac[18];
+
+ // TODO: cache the arp table instead of multiple system call.
+ if (scanArp(ip, mac, sizeof(mac))) {
+ stringsToHash.AppendElement(nsCString(mac));
+ } else {
+ // Can't find a real MAC address. This might be a VPN gateway.
+ char buf[IFNAMSIZ] = {0};
+ char* ifName = if_indextoname(rtm->rtm_index, buf);
+ if (!ifName) {
+ LOG(("parseHashKey: AF_INET if_indextoname failed"));
+ return false;
+ }
+
+ stringsToHash.AppendElement(nsCString(ifName));
+ stringsToHash.AppendElement(nsCString(ip));
+ }
+ } else if (gateway->sa_family == AF_LINK) {
+ char buf[64];
+ struct sockaddr_dl* sockdl = reinterpret_cast<struct sockaddr_dl*>(gateway);
+ if (getMac(sockdl, buf, sizeof(buf))) {
+ stringsToHash.AppendElement(nsCString(buf));
+ } else {
+ char buf[IFNAMSIZ] = {0};
+ char* ifName = if_indextoname(rtm->rtm_index, buf);
+ if (!ifName) {
+ LOG(("parseHashKey: AF_LINK if_indextoname failed"));
+ return false;
+ }
+
+ stringsToHash.AppendElement(nsCString(ifName));
+ }
+ }
+ return true;
+}
+
+// It detects the IP of the default gateways in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+bool nsNetworkLinkService::RoutingTable(nsTArray<nsCString>& aHash) {
+ size_t needed;
+ int mib[6];
+ struct rt_msghdr* rtm;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
+ return false;
+ }
+
+ UniquePtr<char[]> buf(new char[needed]);
+
+ if (sysctl(mib, 6, &buf[0], &needed, nullptr, 0) < 0) {
+ return false;
+ }
+
+ char* lim = &buf[0] + needed;
+ bool rv = false;
+
+ // `next + 1 < lim` ensures we have valid `rtm->rtm_msglen` which is an
+ // unsigned short at the beginning of `rt_msghdr`.
+ for (char* next = &buf[0]; next + 1 < lim; next += rtm->rtm_msglen) {
+ rtm = reinterpret_cast<struct rt_msghdr*>(next);
+
+ if (next + rtm->rtm_msglen > lim) {
+ LOG(("Rt msg is truncated..."));
+ break;
+ }
+
+ if (parseHashKey(rtm, aHash, false)) {
+ rv = true;
+ }
+ }
+ return rv;
+}
+
+// Detect the routing of network.netlink.route.check.IPv4
+bool nsNetworkLinkService::RoutingFromKernel(nsTArray<nsCString>& aHash) {
+ int sockfd;
+ if ((sockfd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) {
+ LOG(("RoutingFromKernel: Can create a socket for network id"));
+ return false;
+ }
+
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ size_t needed = 1024;
+ struct rt_msghdr* rtm;
+ struct sockaddr_in* sin;
+ UniquePtr<char[]> buf(new char[needed]);
+ pid_t pid;
+ int seq;
+
+ rtm = reinterpret_cast<struct rt_msghdr*>(&buf[0]);
+ memset(rtm, 0, sizeof(struct rt_msghdr));
+ rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_type = RTM_GET;
+ rtm->rtm_addrs = RTA_DST;
+ rtm->rtm_pid = (pid = getpid());
+ rtm->rtm_seq = (seq = random());
+
+ sin = reinterpret_cast<struct sockaddr_in*>(rtm + 1);
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_len = sizeof(struct sockaddr_in);
+ sin->sin_family = AF_INET;
+ sin->sin_addr = mRouteCheckIPv4;
+
+ if (write(sockfd, rtm, rtm->rtm_msglen) == -1) {
+ LOG(("RoutingFromKernel: write() failed. No route to the predefine "
+ "destincation"));
+ return false;
+ }
+
+ do {
+ ssize_t r;
+ if ((r = read(sockfd, rtm, needed)) < 0) {
+ LOG(("RoutingFromKernel: read() failed."));
+ return false;
+ }
+
+ LOG(("RoutingFromKernel: read() rtm_type: %d (%d), rtm_pid: %d (%d), "
+ "rtm_seq: %d (%d)\n",
+ rtm->rtm_type, RTM_GET, rtm->rtm_pid, pid, rtm->rtm_seq, seq));
+ } while (rtm->rtm_type != RTM_GET || rtm->rtm_pid != pid ||
+ rtm->rtm_seq != seq);
+
+ return parseHashKey(rtm, aHash, true);
+}
+
+// Figure out the current IPv4 "network identification" string.
+bool nsNetworkLinkService::IPv4NetworkId(SHA1Sum* aSHA1) {
+ nsTArray<nsCString> hash;
+ if (!RoutingTable(hash)) {
+ NS_WARNING("IPv4NetworkId: No default gateways");
+ }
+
+ if (!RoutingFromKernel(hash)) {
+ NS_WARNING("IPv4NetworkId: No route to the predefined destination");
+ }
+
+ // We didn't get any valid hash key to generate network ID.
+ if (hash.IsEmpty()) {
+ LOG(("IPv4NetworkId: No valid hash key"));
+ return false;
+ }
+
+ hash.Sort();
+ for (uint32_t i = 0; i < hash.Length(); ++i) {
+ LOG(("IPv4NetworkId: Hashing string for network id: %s", hash[i].get()));
+ aSHA1->update(hash[i].get(), hash[i].Length());
+ }
+
+ return true;
+}
+
+//
+// Sort and hash the prefixes and netmasks
+//
+void nsNetworkLinkService::HashSortedPrefixesAndNetmasks(
+ std::vector<prefix_and_netmask> prefixAndNetmaskStore, SHA1Sum* sha1) {
+ // getifaddrs does not guarantee the interfaces will always be in the same
+ // order. We want to make sure the hash remains consistent Regardless of the
+ // interface order.
+ std::sort(prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
+ [](prefix_and_netmask a, prefix_and_netmask b) {
+ // compare prefixStore
+ int comparedPrefix = memcmp(&a.first, &b.first, sizeof(in6_addr));
+ if (comparedPrefix == 0) {
+ // compare netmaskStore
+ return memcmp(&a.second, &b.second, sizeof(in6_addr)) < 0;
+ }
+ return comparedPrefix < 0;
+ });
+
+ for (const auto& prefixAndNetmask : prefixAndNetmaskStore) {
+ sha1->update(&prefixAndNetmask.first, sizeof(in6_addr));
+ sha1->update(&prefixAndNetmask.second, sizeof(in6_addr));
+ }
+}
+
+bool nsNetworkLinkService::IPv6NetworkId(SHA1Sum* sha1) {
+ struct ifaddrs* ifap;
+ std::vector<prefix_and_netmask> prefixAndNetmaskStore;
+
+ if (!getifaddrs(&ifap)) {
+ struct ifaddrs* ifa;
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL) {
+ continue;
+ }
+ if ((AF_INET6 == ifa->ifa_addr->sa_family) &&
+ !(ifa->ifa_flags & (IFF_POINTOPOINT | IFF_LOOPBACK))) {
+ // only IPv6 interfaces that aren't pointtopoint or loopback
+ struct sockaddr_in6* sin_netmask =
+ (struct sockaddr_in6*)ifa->ifa_netmask;
+ if (sin_netmask) {
+ struct sockaddr_in6* sin_addr = (struct sockaddr_in6*)ifa->ifa_addr;
+ int scope = net::utils::ipv6_scope(sin_addr->sin6_addr.s6_addr);
+ if (scope == IPV6_SCOPE_GLOBAL) {
+ struct in6_addr prefix;
+ memset(&prefix, 0, sizeof(prefix));
+ // Get the prefix by combining the address and netmask.
+ for (size_t i = 0; i < sizeof(prefix); ++i) {
+ prefix.s6_addr[i] = sin_addr->sin6_addr.s6_addr[i] &
+ sin_netmask->sin6_addr.s6_addr[i];
+ }
+
+ // check if prefix and netmask was already found
+ auto prefixAndNetmask =
+ std::make_pair(prefix, sin_netmask->sin6_addr);
+ auto foundPosition = std::find_if(
+ prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
+ [&prefixAndNetmask](prefix_and_netmask current) {
+ return memcmp(&prefixAndNetmask.first, &current.first,
+ sizeof(in6_addr)) == 0 &&
+ memcmp(&prefixAndNetmask.second, &current.second,
+ sizeof(in6_addr)) == 0;
+ });
+ if (foundPosition != prefixAndNetmaskStore.end()) {
+ continue;
+ }
+ prefixAndNetmaskStore.push_back(prefixAndNetmask);
+ }
+ }
+ }
+ }
+ freeifaddrs(ifap);
+ }
+ if (prefixAndNetmaskStore.empty()) {
+ LOG(("IPv6NetworkId failed"));
+ return false;
+ }
+
+ nsNetworkLinkService::HashSortedPrefixesAndNetmasks(prefixAndNetmaskStore,
+ sha1);
+
+ return true;
+}
+
+void nsNetworkLinkService::calculateNetworkIdWithDelay(uint32_t aDelay) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aDelay) {
+ if (mNetworkIdTimer) {
+ LOG(("Restart the network id timer."));
+ mNetworkIdTimer->Cancel();
+ } else {
+ LOG(("Create the network id timer."));
+ mNetworkIdTimer = NS_NewTimer();
+ }
+ mNetworkIdTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
+ NewRunnableMethod("nsNetworkLinkService::calculateNetworkIdInternal",
+ this,
+ &nsNetworkLinkService::calculateNetworkIdInternal),
+ NS_DISPATCH_NORMAL));
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer == mNetworkIdTimer);
+
+ mNetworkIdTimer = nullptr;
+ calculateNetworkIdWithDelay(0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsNetworkLinkService");
+ return NS_OK;
+}
+
+void nsNetworkLinkService::calculateNetworkIdInternal(void) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread");
+ SHA1Sum sha1;
+ bool idChanged = false;
+ bool found4 = IPv4NetworkId(&sha1);
+ bool found6 = IPv6NetworkId(&sha1);
+
+ if (found4 || found6) {
+ nsAutoCString output;
+ SeedNetworkId(sha1);
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsAutoCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ nsresult rv = Base64Encode(newString, output);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("networkid: id %s\n", output.get()));
+ MutexAutoLock lock(mMutex);
+ if (mNetworkId != output) {
+ // new id
+ if (found4 && !found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); // IPv4 only
+ } else if (!found4 && found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3); // IPv6 only
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4); // Both!
+ }
+ mNetworkId = output;
+ idChanged = true;
+ } else {
+ // same id
+ LOG(("Same network id"));
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
+ }
+ } else {
+ // no id
+ LOG(("No network id"));
+ MutexAutoLock lock(mMutex);
+ if (!mNetworkId.IsEmpty()) {
+ mNetworkId.Truncate();
+ idChanged = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+ }
+ }
+
+ // Don't report network change if this is the first time we calculate the id.
+ static bool initialIDCalculation = true;
+ if (idChanged && !initialIDCalculation) {
+ RefPtr<nsNetworkLinkService> self = this;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::calculateNetworkIdInternal",
+ [self]() { self->OnNetworkIdChanged(); }));
+ }
+
+ initialIDCalculation = false;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsNetworkLinkService::NetworkConfigChanged(SCDynamicStoreRef aStoreREf,
+ CFArrayRef aChangedKeys,
+ void* aInfo) {
+ LOG(("nsNetworkLinkService::NetworkConfigChanged"));
+
+ bool ipConfigChanged = false;
+ bool dnsConfigChanged = false;
+ for (CFIndex i = 0; i < CFArrayGetCount(aChangedKeys); ++i) {
+ CFStringRef key =
+ static_cast<CFStringRef>(CFArrayGetValueAtIndex(aChangedKeys, i));
+ if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
+ CFStringHasSuffix(key, kSCEntNetIPv6)) {
+ ipConfigChanged = true;
+ }
+ if (CFStringHasSuffix(key, kSCEntNetDNS)) {
+ dnsConfigChanged = true;
+ }
+ }
+
+ nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(aInfo);
+ if (ipConfigChanged) {
+ service->OnIPConfigChanged();
+ }
+
+ if (dnsConfigChanged) {
+ service->DNSConfigChanged(kDNSSuffixDelayAfterChange);
+ }
+}
+
+void nsNetworkLinkService::DNSConfigChanged(uint32_t aDelayMs) {
+ LOG(("nsNetworkLinkService::DNSConfigChanged"));
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ return;
+ }
+ if (aDelayMs) {
+ MutexAutoLock lock(mMutex);
+ nsCOMPtr<nsITimer> timer;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewTimerWithCallback(
+ getter_AddRefs(timer),
+ [self = RefPtr{this}](nsITimer* aTimer) {
+ self->GetDnsSuffixListInternal();
+
+ MutexAutoLock lock(self->mMutex);
+ self->mDNSConfigChangedTimers.RemoveElement(aTimer);
+ },
+ TimeDuration::FromMilliseconds(aDelayMs), nsITimer::TYPE_ONE_SHOT,
+ "nsNetworkLinkService::GetDnsSuffixListInternal", target));
+ mDNSConfigChangedTimers.AppendElement(timer);
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(target->Dispatch(NS_NewRunnableFunction(
+ "nsNetworkLinkService::GetDnsSuffixListInternal",
+ [self = RefPtr{this}]() { self->GetDnsSuffixListInternal(); })));
+ }
+}
+
+nsresult nsNetworkLinkService::Init(void) {
+ nsresult rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
+ LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV4);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If the network reachability API can reach 0.0.0.0 without
+ // requiring a connection, there is a network interface available.
+ struct sockaddr_in addr;
+ bzero(&addr, sizeof(addr));
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ mReachability = ::SCNetworkReachabilityCreateWithAddress(
+ nullptr, (struct sockaddr*)&addr);
+ if (!mReachability) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr};
+ if (!::SCNetworkReachabilitySetCallback(mReachability, ReachabilityChanged,
+ &context)) {
+ NS_WARNING("SCNetworkReachabilitySetCallback failed.");
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCDynamicStoreContext storeContext = {0, this, nullptr, nullptr, nullptr};
+ mStoreRef =
+ ::SCDynamicStoreCreate(nullptr, CFSTR("IPAndDNSChangeCallbackSCF"),
+ NetworkConfigChanged, &storeContext);
+
+ CFStringRef patterns[4] = {nullptr, nullptr, nullptr, nullptr};
+ OSStatus err = getErrorCodePtr(mStoreRef);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv4".
+ patterns[0] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
+ // This pattern is "State:/Network/Service/[^/]+/IPv6".
+ patterns[1] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6);
+ // This pattern is "State:/Network/Service/[^/]+/DNS".
+ patterns[2] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetDNS);
+ // This pattern is "Setup:/Network/Service/[^/]+/DNS".
+ patterns[3] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetDNS);
+ if (!patterns[0] || !patterns[1] || !patterns[2] || !patterns[3]) {
+ err = -1;
+ }
+ }
+
+ CFArrayRef patternList = nullptr;
+ // Create a pattern list containing just one pattern,
+ // then tell SCF that we want to watch changes in keys
+ // that match that pattern list, then create our run loop
+ // source.
+ if (err == noErr) {
+ patternList = ::CFArrayCreate(nullptr, (const void**)patterns, 4,
+ &kCFTypeArrayCallBacks);
+ if (!patternList) {
+ err = -1;
+ }
+ }
+ if (err == noErr) {
+ err = getErrorCodeBool(
+ ::SCDynamicStoreSetNotificationKeys(mStoreRef, nullptr, patternList));
+ }
+
+ if (err == noErr) {
+ mRunLoopSource = ::SCDynamicStoreCreateRunLoopSource(nullptr, mStoreRef, 0);
+ err = getErrorCodePtr(mRunLoopSource);
+ }
+
+ CFReleaseSafe(patterns[0]);
+ CFReleaseSafe(patterns[1]);
+ CFReleaseSafe(patterns[2]);
+ CFReleaseSafe(patterns[3]);
+ CFReleaseSafe(patternList);
+
+ if (err != noErr) {
+ CFReleaseSafe(mStoreRef);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Get the current run loop. This service is initialized at startup,
+ // so we shouldn't run in to any problems with modal dialog run loops.
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ if (!mCFRunLoop) {
+ NS_WARNING("Could not get current run loop.");
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ ::CFRetain(mCFRunLoop);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ UpdateReachability();
+
+ calculateNetworkIdWithDelay(0);
+
+ DNSConfigChanged(0);
+
+ return NS_OK;
+}
+
+nsresult nsNetworkLinkService::Shutdown() {
+ if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
+ }
+
+ CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = nullptr;
+
+ ::CFRelease(mStoreRef);
+ mStoreRef = nullptr;
+
+ ::CFRelease(mRunLoopSource);
+ mRunLoopSource = nullptr;
+
+ if (mNetworkIdTimer) {
+ mNetworkIdTimer->Cancel();
+ mNetworkIdTimer = nullptr;
+ }
+
+ nsTArray<nsCOMPtr<nsITimer>> dnsConfigChangedTimers;
+ {
+ MutexAutoLock lock(mMutex);
+ dnsConfigChangedTimers = std::move(mDNSConfigChangedTimers);
+ mDNSConfigChangedTimers.Clear();
+ }
+ for (const auto& timer : dnsConfigChangedTimers) {
+ timer->Cancel();
+ }
+
+ return NS_OK;
+}
+
+void nsNetworkLinkService::UpdateReachability() {
+ if (!mReachability) {
+ return;
+ }
+
+ SCNetworkConnectionFlags flags;
+ if (!::SCNetworkReachabilityGetFlags(mReachability, &flags)) {
+ mStatusKnown = false;
+ return;
+ }
+
+ bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
+ bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
+
+ mLinkUp = (reachable && !needsConnection);
+ mStatusKnown = true;
+}
+
+void nsNetworkLinkService::OnIPConfigChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
+ if (!StaticPrefs::network_notify_changed()) {
+ return;
+ }
+
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_CHANGED);
+}
+
+void nsNetworkLinkService::OnNetworkIdChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
+}
+
+void nsNetworkLinkService::OnReachabilityChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mStatusKnown) {
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UNKNOWN);
+ return;
+ }
+
+ NotifyObservers(NS_NETWORK_LINK_TOPIC, mLinkUp ? NS_NETWORK_LINK_DATA_UP
+ : NS_NETWORK_LINK_DATA_DOWN);
+}
+
+void nsNetworkLinkService::NotifyObservers(const char* aTopic,
+ const char* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsNetworkLinkService::NotifyObservers: topic:%s data:%s\n", aTopic,
+ aData ? aData : ""));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(
+ static_cast<nsINetworkLinkService*>(this), aTopic,
+ aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
+ }
+}
+
+/* static */
+void nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void* info) {
+ LOG(("nsNetworkLinkService::ReachabilityChanged"));
+ nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(info);
+
+ service->UpdateReachability();
+ service->OnReachabilityChanged();
+ service->calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
+ // If a new interface is up or the order of interfaces is changed, we should
+ // update the DNS suffix list.
+ service->DNSConfigChanged(0);
+}
diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build
new file mode 100644
index 0000000000..b70eb40aaa
--- /dev/null
+++ b/netwerk/system/moz.build
@@ -0,0 +1,23 @@
+# -*- 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":
+ DIRS += ["win32"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ DIRS += ["mac"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ DIRS += ["android", "netlink"]
+
+elif CONFIG["OS_ARCH"] == "Linux":
+ DIRS += ["linux", "netlink"]
+
+SOURCES += [
+ "LinkServiceCommon.cpp",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/netlink/NetlinkService.cpp b/netwerk/system/netlink/NetlinkService.cpp
new file mode 100644
index 0000000000..f50284af0c
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.cpp
@@ -0,0 +1,1903 @@
+/* -*- 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/. */
+
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <poll.h>
+#include <unistd.h>
+#include <linux/rtnetlink.h>
+
+#include "nsThreadUtils.h"
+#include "NetlinkService.h"
+#include "nsIThread.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Logging.h"
+#include "../../base/IPv6Utils.h"
+#include "../LinkServiceCommon.h"
+#include "../NetworkLinkServiceDefines.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/FunctionTypeTraits.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+
+#if defined(HAVE_RES_NINIT)
+# include <netinet/in.h>
+# include <resolv.h>
+#endif
+
+namespace mozilla::net {
+
+template <typename F>
+static auto eintr_retry(F&& func) ->
+ typename FunctionTypeTraits<decltype(func)>::ReturnType {
+ typename FunctionTypeTraits<decltype(func)>::ReturnType _rc;
+ do {
+ _rc = func();
+ } while (_rc == -1 && errno == EINTR);
+ return _rc;
+}
+
+#define EINTR_RETRY(expr) eintr_retry([&]() { return expr; })
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+static LazyLogModule gNlSvcLog("NetlinkService");
+#define LOG(args) MOZ_LOG(gNlSvcLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gNlSvcLog, mozilla::LogLevel::Debug)
+
+using in_common_addr = union {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+};
+
+static void GetAddrStr(const in_common_addr* aAddr, uint8_t aFamily,
+ nsACString& _retval) {
+ char addr[INET6_ADDRSTRLEN];
+ addr[0] = 0;
+
+ if (aFamily == AF_INET) {
+ inet_ntop(AF_INET, &(aAddr->addr4), addr, INET_ADDRSTRLEN);
+ } else {
+ inet_ntop(AF_INET6, &(aAddr->addr6), addr, INET6_ADDRSTRLEN);
+ }
+ _retval.Assign(addr);
+}
+
+class NetlinkAddress {
+ public:
+ NetlinkAddress() = default;
+
+ uint8_t Family() const { return mIfam.ifa_family; }
+ uint32_t GetIndex() const { return mIfam.ifa_index; }
+ uint8_t GetPrefixLen() const { return mIfam.ifa_prefixlen; }
+ bool ScopeIsUniverse() const { return mIfam.ifa_scope == RT_SCOPE_UNIVERSE; }
+ const in_common_addr* GetAddrPtr() const { return &mAddr; }
+
+ bool MsgEquals(const NetlinkAddress& aOther) const {
+ return !memcmp(&mIfam, &(aOther.mIfam), sizeof(mIfam));
+ }
+
+ bool Equals(const NetlinkAddress& aOther) const {
+ if (mIfam.ifa_family != aOther.mIfam.ifa_family) {
+ return false;
+ }
+ if (mIfam.ifa_index != aOther.mIfam.ifa_index) {
+ // addresses are different when they are on a different interface
+ return false;
+ }
+ if (mIfam.ifa_prefixlen != aOther.mIfam.ifa_prefixlen) {
+ // It's possible to have two equal addresses with a different netmask on
+ // the same interface, so we need to check prefixlen too.
+ return false;
+ }
+ size_t addrSize = (mIfam.ifa_family == AF_INET) ? sizeof(mAddr.addr4)
+ : sizeof(mAddr.addr6);
+ return memcmp(&mAddr, aOther.GetAddrPtr(), addrSize) == 0;
+ }
+
+ bool ContainsAddr(const in_common_addr* aAddr) {
+ int32_t addrSize = (mIfam.ifa_family == AF_INET)
+ ? (int32_t)sizeof(mAddr.addr4)
+ : (int32_t)sizeof(mAddr.addr6);
+ uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+ int32_t bits = mIfam.ifa_prefixlen;
+ if (bits > addrSize * 8) {
+ MOZ_ASSERT(false, "Unexpected prefix length!");
+ LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
+ addrSize * 8));
+ return false;
+ }
+ for (int32_t i = 0; i < addrSize; i++) {
+ uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
+ if ((((unsigned char*)aAddr)[i] & mask) !=
+ (((unsigned char*)(&mAddr))[i] & mask)) {
+ return false;
+ }
+ bits -= 8;
+ if (bits <= 0) {
+ return true;
+ }
+ }
+ return true;
+ }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct ifaddrmsg* ifam;
+ struct rtattr* attr;
+ int len;
+
+ ifam = (ifaddrmsg*)NLMSG_DATA(aNlh);
+ len = IFA_PAYLOAD(aNlh);
+
+ if (ifam->ifa_family != AF_INET && ifam->ifa_family != AF_INET6) {
+ return false;
+ }
+
+ bool hasAddr = false;
+ for (attr = IFA_RTA(ifam); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == IFA_ADDRESS || attr->rta_type == IFA_LOCAL) {
+ memcpy(&mAddr, RTA_DATA(attr),
+ ifam->ifa_family == AF_INET ? sizeof(mAddr.addr4)
+ : sizeof(mAddr.addr6));
+ hasAddr = true;
+ if (attr->rta_type == IFA_LOCAL) {
+ // local address is preferred, so don't continue parsing other
+ // attributes
+ break;
+ }
+ }
+ }
+
+ if (!hasAddr) {
+ return false;
+ }
+
+ memcpy(&mIfam, (ifaddrmsg*)NLMSG_DATA(aNlh), sizeof(mIfam));
+ return true;
+ }
+
+ private:
+ in_common_addr mAddr;
+ struct ifaddrmsg mIfam;
+};
+
+class NetlinkNeighbor {
+ public:
+ NetlinkNeighbor() = default;
+
+ uint8_t Family() const { return mNeigh.ndm_family; }
+ uint32_t GetIndex() const { return mNeigh.ndm_ifindex; }
+ const in_common_addr* GetAddrPtr() const { return &mAddr; }
+ const uint8_t* GetMACPtr() const { return mMAC; }
+ bool HasMAC() const { return mHasMAC; }
+
+ void GetAsString(nsACString& _retval) const {
+ nsAutoCString addrStr;
+ _retval.Assign("addr=");
+ GetAddrStr(&mAddr, mNeigh.ndm_family, addrStr);
+ _retval.Append(addrStr);
+ if (mNeigh.ndm_family == AF_INET) {
+ _retval.Append(" family=AF_INET if=");
+ } else {
+ _retval.Append(" family=AF_INET6 if=");
+ }
+ _retval.AppendInt(mNeigh.ndm_ifindex);
+ if (mHasMAC) {
+ _retval.Append(" mac=");
+ _retval.Append(nsPrintfCString("%02x:%02x:%02x:%02x:%02x:%02x", mMAC[0],
+ mMAC[1], mMAC[2], mMAC[3], mMAC[4],
+ mMAC[5]));
+ }
+ }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct ndmsg* neigh;
+ struct rtattr* attr;
+ int len;
+
+ neigh = (ndmsg*)NLMSG_DATA(aNlh);
+ len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*neigh));
+
+ if (neigh->ndm_family != AF_INET && neigh->ndm_family != AF_INET6) {
+ return false;
+ }
+
+ bool hasDST = false;
+ for (attr = RTM_RTA(neigh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == NDA_LLADDR) {
+ memcpy(mMAC, RTA_DATA(attr), ETH_ALEN);
+ mHasMAC = true;
+ }
+
+ if (attr->rta_type == NDA_DST) {
+ memcpy(&mAddr, RTA_DATA(attr),
+ neigh->ndm_family == AF_INET ? sizeof(mAddr.addr4)
+ : sizeof(mAddr.addr6));
+ hasDST = true;
+ }
+ }
+
+ if (!hasDST) {
+ return false;
+ }
+
+ memcpy(&mNeigh, (ndmsg*)NLMSG_DATA(aNlh), sizeof(mNeigh));
+ return true;
+ }
+
+ private:
+ bool mHasMAC{false};
+ uint8_t mMAC[ETH_ALEN]{};
+ in_common_addr mAddr{};
+ struct ndmsg mNeigh {};
+};
+
+class NetlinkLink {
+ public:
+ NetlinkLink() = default;
+
+ bool IsUp() const {
+ return (mIface.ifi_flags & IFF_RUNNING) &&
+ !(mIface.ifi_flags & IFF_LOOPBACK);
+ }
+
+ void GetName(nsACString& _retval) const { _retval = mName; }
+ bool IsTypeEther() const { return mIface.ifi_type == ARPHRD_ETHER; }
+ uint32_t GetIndex() const { return mIface.ifi_index; }
+ uint32_t GetFlags() const { return mIface.ifi_flags; }
+ uint16_t GetType() const { return mIface.ifi_type; }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct ifinfomsg* iface;
+ struct rtattr* attr;
+ int len;
+
+ iface = (ifinfomsg*)NLMSG_DATA(aNlh);
+ len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));
+
+ bool hasName = false;
+ for (attr = IFLA_RTA(iface); RTA_OK(attr, len);
+ attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == IFLA_IFNAME) {
+ mName.Assign((char*)RTA_DATA(attr));
+ hasName = true;
+ break;
+ }
+ }
+
+ if (!hasName) {
+ return false;
+ }
+
+ memcpy(&mIface, (ifinfomsg*)NLMSG_DATA(aNlh), sizeof(mIface));
+ return true;
+ }
+
+ private:
+ nsCString mName;
+ struct ifinfomsg mIface {};
+};
+
+class NetlinkRoute {
+ public:
+ NetlinkRoute()
+ : mHasGWAddr(false),
+ mHasPrefSrcAddr(false),
+ mHasDstAddr(false),
+ mHasOif(false),
+ mHasPrio(false) {}
+
+ bool IsUnicast() const { return mRtm.rtm_type == RTN_UNICAST; }
+ bool ScopeIsUniverse() const { return mRtm.rtm_scope == RT_SCOPE_UNIVERSE; }
+ bool IsDefault() const { return mRtm.rtm_dst_len == 0; }
+ bool HasOif() const { return mHasOif; }
+ uint8_t Oif() const { return mOif; }
+ uint8_t Family() const { return mRtm.rtm_family; }
+ bool HasPrefSrcAddr() const { return mHasPrefSrcAddr; }
+ const in_common_addr* GetGWAddrPtr() const {
+ return mHasGWAddr ? &mGWAddr : nullptr;
+ }
+ const in_common_addr* GetPrefSrcAddrPtr() const {
+ return mHasPrefSrcAddr ? &mPrefSrcAddr : nullptr;
+ }
+
+ bool Equals(const NetlinkRoute& aOther) const {
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
+ : sizeof(mDstAddr.addr6);
+ if (memcmp(&mRtm, &(aOther.mRtm), sizeof(mRtm)) != 0) {
+ return false;
+ }
+ if (mHasOif != aOther.mHasOif || mOif != aOther.mOif) {
+ return false;
+ }
+ if (mHasPrio != aOther.mHasPrio || mPrio != aOther.mPrio) {
+ return false;
+ }
+ if ((mHasGWAddr != aOther.mHasGWAddr) ||
+ (mHasGWAddr && memcmp(&mGWAddr, &(aOther.mGWAddr), addrSize) != 0)) {
+ return false;
+ }
+ if ((mHasDstAddr != aOther.mHasDstAddr) ||
+ (mHasDstAddr && memcmp(&mDstAddr, &(aOther.mDstAddr), addrSize) != 0)) {
+ return false;
+ }
+ if ((mHasPrefSrcAddr != aOther.mHasPrefSrcAddr) ||
+ (mHasPrefSrcAddr &&
+ memcmp(&mPrefSrcAddr, &(aOther.mPrefSrcAddr), addrSize) != 0)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool GatewayEquals(const NetlinkNeighbor& aNeigh) const {
+ if (!mHasGWAddr) {
+ return false;
+ }
+ if (aNeigh.Family() != mRtm.rtm_family) {
+ return false;
+ }
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+ : sizeof(mGWAddr.addr6);
+ return memcmp(&mGWAddr, aNeigh.GetAddrPtr(), addrSize) == 0;
+ }
+
+ bool GatewayEquals(const NetlinkRoute* aRoute) const {
+ if (!mHasGWAddr || !aRoute->mHasGWAddr) {
+ return false;
+ }
+ if (mRtm.rtm_family != aRoute->mRtm.rtm_family) {
+ return false;
+ }
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+ : sizeof(mGWAddr.addr6);
+ return memcmp(&mGWAddr, &(aRoute->mGWAddr), addrSize) == 0;
+ }
+
+ bool PrefSrcAddrEquals(const NetlinkAddress& aAddress) const {
+ if (!mHasPrefSrcAddr) {
+ return false;
+ }
+ if (mRtm.rtm_family != aAddress.Family()) {
+ return false;
+ }
+ size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
+ : sizeof(mPrefSrcAddr.addr6);
+ return memcmp(&mPrefSrcAddr, aAddress.GetAddrPtr(), addrSize) == 0;
+ }
+
+ void GetAsString(nsACString& _retval) const {
+ nsAutoCString addrStr;
+ _retval.Assign("table=");
+ _retval.AppendInt(mRtm.rtm_table);
+ _retval.Append(" type=");
+ _retval.AppendInt(mRtm.rtm_type);
+ _retval.Append(" scope=");
+ _retval.AppendInt(mRtm.rtm_scope);
+ if (mRtm.rtm_family == AF_INET) {
+ _retval.Append(" family=AF_INET dst=");
+ addrStr.Assign("0.0.0.0/");
+ } else {
+ _retval.Append(" family=AF_INET6 dst=");
+ addrStr.Assign("::/");
+ }
+ if (mHasDstAddr) {
+ GetAddrStr(&mDstAddr, mRtm.rtm_family, addrStr);
+ addrStr.Append("/");
+ }
+ _retval.Append(addrStr);
+ _retval.AppendInt(mRtm.rtm_dst_len);
+ if (mHasPrefSrcAddr) {
+ _retval.Append(" src=");
+ GetAddrStr(&mPrefSrcAddr, mRtm.rtm_family, addrStr);
+ _retval.Append(addrStr);
+ }
+ if (mHasGWAddr) {
+ _retval.Append(" via=");
+ GetAddrStr(&mGWAddr, mRtm.rtm_family, addrStr);
+ _retval.Append(addrStr);
+ }
+ if (mHasOif) {
+ _retval.Append(" oif=");
+ _retval.AppendInt(mOif);
+ }
+ if (mHasPrio) {
+ _retval.Append(" prio=");
+ _retval.AppendInt(mPrio);
+ }
+ }
+
+ bool Init(struct nlmsghdr* aNlh) {
+ struct rtmsg* rtm;
+ struct rtattr* attr;
+ int len;
+
+ rtm = (rtmsg*)NLMSG_DATA(aNlh);
+ len = RTM_PAYLOAD(aNlh);
+
+ if (rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6) {
+ return false;
+ }
+
+ for (attr = RTM_RTA(rtm); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+ if (attr->rta_type == RTA_DST) {
+ memcpy(&mDstAddr, RTA_DATA(attr),
+ (rtm->rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
+ : sizeof(mDstAddr.addr6));
+ mHasDstAddr = true;
+ } else if (attr->rta_type == RTA_GATEWAY) {
+ memcpy(&mGWAddr, RTA_DATA(attr),
+ (rtm->rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+ : sizeof(mGWAddr.addr6));
+ mHasGWAddr = true;
+ } else if (attr->rta_type == RTA_PREFSRC) {
+ memcpy(&mPrefSrcAddr, RTA_DATA(attr),
+ (rtm->rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
+ : sizeof(mPrefSrcAddr.addr6));
+ mHasPrefSrcAddr = true;
+ } else if (attr->rta_type == RTA_OIF) {
+ mOif = *(uint32_t*)RTA_DATA(attr);
+ mHasOif = true;
+ } else if (attr->rta_type == RTA_PRIORITY) {
+ mPrio = *(uint32_t*)RTA_DATA(attr);
+ mHasPrio = true;
+ }
+ }
+
+ memcpy(&mRtm, (rtmsg*)NLMSG_DATA(aNlh), sizeof(mRtm));
+ return true;
+ }
+
+ private:
+ bool mHasGWAddr : 1;
+ bool mHasPrefSrcAddr : 1;
+ bool mHasDstAddr : 1;
+ bool mHasOif : 1;
+ bool mHasPrio : 1;
+
+ in_common_addr mGWAddr{};
+ in_common_addr mDstAddr{};
+ in_common_addr mPrefSrcAddr{};
+
+ uint32_t mOif{};
+ uint32_t mPrio{};
+ struct rtmsg mRtm {};
+};
+
+class NetlinkMsg {
+ public:
+ static uint8_t const kGenMsg = 1;
+ static uint8_t const kRtMsg = 2;
+
+ NetlinkMsg() = default;
+ virtual ~NetlinkMsg() = default;
+
+ virtual bool Send(int aFD) = 0;
+ virtual bool IsPending() { return mIsPending; }
+ virtual uint32_t SeqId() = 0;
+ virtual uint8_t Family() = 0;
+ virtual uint8_t MsgType() = 0;
+
+ protected:
+ bool SendRequest(int aFD, void* aRequest, uint32_t aRequestLength) {
+ MOZ_ASSERT(!mIsPending, "Request has been already sent!");
+
+ struct sockaddr_nl kernel {};
+ memset(&kernel, 0, sizeof(kernel));
+ kernel.nl_family = AF_NETLINK;
+ kernel.nl_groups = 0;
+
+ struct iovec io {};
+ memset(&io, 0, sizeof(io));
+ io.iov_base = aRequest;
+ io.iov_len = aRequestLength;
+
+ struct msghdr rtnl_msg {};
+ memset(&rtnl_msg, 0, sizeof(rtnl_msg));
+ rtnl_msg.msg_iov = &io;
+ rtnl_msg.msg_iovlen = 1;
+ rtnl_msg.msg_name = &kernel;
+ rtnl_msg.msg_namelen = sizeof(kernel);
+
+ ssize_t rc = EINTR_RETRY(sendmsg(aFD, (struct msghdr*)&rtnl_msg, 0));
+ if (rc > 0 && (uint32_t)rc == aRequestLength) {
+ mIsPending = true;
+ }
+
+ return mIsPending;
+ }
+
+ bool mIsPending{false};
+};
+
+class NetlinkGenMsg : public NetlinkMsg {
+ public:
+ NetlinkGenMsg(uint16_t aMsgType, uint8_t aFamily, uint32_t aSeqId) {
+ memset(&mReq, 0, sizeof(mReq));
+
+ mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+ mReq.hdr.nlmsg_type = aMsgType;
+ mReq.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ mReq.hdr.nlmsg_seq = aSeqId;
+ mReq.hdr.nlmsg_pid = 0;
+
+ mReq.gen.rtgen_family = aFamily;
+ }
+
+ virtual bool Send(int aFD) {
+ return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
+ }
+
+ virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
+ virtual uint8_t Family() { return mReq.gen.rtgen_family; }
+ virtual uint8_t MsgType() { return kGenMsg; }
+
+ private:
+ struct {
+ struct nlmsghdr hdr;
+ struct rtgenmsg gen;
+ } mReq{};
+};
+
+class NetlinkRtMsg : public NetlinkMsg {
+ public:
+ NetlinkRtMsg(uint8_t aFamily, void* aAddress, uint32_t aSeqId) {
+ MOZ_ASSERT(aFamily == AF_INET || aFamily == AF_INET6);
+
+ memset(&mReq, 0, sizeof(mReq));
+
+ mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ mReq.hdr.nlmsg_type = RTM_GETROUTE;
+ mReq.hdr.nlmsg_flags = NLM_F_REQUEST;
+ mReq.hdr.nlmsg_seq = aSeqId;
+ mReq.hdr.nlmsg_pid = 0;
+
+ mReq.rtm.rtm_family = aFamily;
+ mReq.rtm.rtm_flags = 0;
+ mReq.rtm.rtm_dst_len = aFamily == AF_INET ? 32 : 128;
+
+ struct rtattr* rta;
+ rta = (struct rtattr*)(((char*)&mReq) + NLMSG_ALIGN(mReq.hdr.nlmsg_len));
+ rta->rta_type = RTA_DST;
+ size_t addrSize =
+ aFamily == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr);
+ rta->rta_len = RTA_LENGTH(addrSize);
+ memcpy(RTA_DATA(rta), aAddress, addrSize);
+ mReq.hdr.nlmsg_len = NLMSG_ALIGN(mReq.hdr.nlmsg_len) + RTA_LENGTH(addrSize);
+ }
+
+ virtual bool Send(int aFD) {
+ return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
+ }
+
+ virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
+ virtual uint8_t Family() { return mReq.rtm.rtm_family; }
+ virtual uint8_t MsgType() { return kRtMsg; }
+
+ private:
+ struct {
+ struct nlmsghdr hdr;
+ struct rtmsg rtm;
+ unsigned char data[1024];
+ } mReq{};
+};
+
+NetlinkService::LinkInfo::LinkInfo(UniquePtr<NetlinkLink>&& aLink)
+ : mLink(std::move(aLink)), mIsUp(false) {}
+
+NetlinkService::LinkInfo::~LinkInfo() = default;
+
+bool NetlinkService::LinkInfo::UpdateStatus() {
+ LOG(("NetlinkService::LinkInfo::UpdateStatus"));
+
+ bool oldIsUp = mIsUp;
+ mIsUp = false;
+
+ if (!mLink->IsUp()) {
+ // The link is not up or is a loopback
+ LOG(("The link is down or is a loopback"));
+ } else {
+ // Link is up when there is non-local address associated with it.
+ for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
+ if (LOG_ENABLED()) {
+ nsAutoCString dbgStr;
+ GetAddrStr(mAddresses[i]->GetAddrPtr(), mAddresses[i]->Family(),
+ dbgStr);
+ LOG(("checking address %s", dbgStr.get()));
+ }
+ if (mAddresses[i]->ScopeIsUniverse()) {
+ mIsUp = true;
+ LOG(("global address found"));
+ break;
+ }
+ }
+ }
+
+ return mIsUp == oldIsUp;
+}
+
+NS_IMPL_ISUPPORTS(NetlinkService, nsIRunnable)
+
+NetlinkService::NetlinkService() : mPid(getpid()) {}
+
+NetlinkService::~NetlinkService() {
+ MOZ_ASSERT(!mThread, "NetlinkService thread shutdown failed");
+
+ if (mShutdownPipe[0] != -1) {
+ EINTR_RETRY(close(mShutdownPipe[0]));
+ }
+ if (mShutdownPipe[1] != -1) {
+ EINTR_RETRY(close(mShutdownPipe[1]));
+ }
+}
+
+void NetlinkService::OnNetlinkMessage(int aNetlinkSocket) {
+ // The buffer size 4096 is a common page size, which is a recommended limit
+ // for netlink messages.
+ char buffer[4096];
+
+ struct sockaddr_nl kernel {};
+ memset(&kernel, 0, sizeof(kernel));
+ kernel.nl_family = AF_NETLINK;
+ kernel.nl_groups = 0;
+
+ struct iovec io {};
+ memset(&io, 0, sizeof(io));
+ io.iov_base = buffer;
+ io.iov_len = sizeof(buffer);
+
+ struct msghdr rtnl_reply {};
+ memset(&rtnl_reply, 0, sizeof(rtnl_reply));
+ rtnl_reply.msg_iov = &io;
+ rtnl_reply.msg_iovlen = 1;
+ rtnl_reply.msg_name = &kernel;
+ rtnl_reply.msg_namelen = sizeof(kernel);
+
+ ssize_t rc = EINTR_RETRY(recvmsg(aNetlinkSocket, &rtnl_reply, MSG_DONTWAIT));
+ if (rc < 0) {
+ return;
+ }
+ size_t netlink_bytes = rc;
+
+ struct nlmsghdr* nlh = reinterpret_cast<struct nlmsghdr*>(buffer);
+
+ for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
+ // If PID in the message is our PID, then it's a response to our request.
+ // Otherwise it's a multicast message.
+ bool isResponse = (pid_t)nlh->nlmsg_pid == mPid;
+ if (isResponse) {
+ if (!mOutgoingMessages.Length() || !mOutgoingMessages[0]->IsPending()) {
+ // There is no enqueued message pending?
+ LOG((
+ "Ignoring message seq_id %u, because there is no associated message"
+ " pending",
+ nlh->nlmsg_seq));
+ continue;
+ }
+
+ if (mOutgoingMessages[0]->SeqId() != nlh->nlmsg_seq) {
+ LOG(("Received unexpected seq_id [received=%u, expected=%u]",
+ nlh->nlmsg_seq, mOutgoingMessages[0]->SeqId()));
+ RemovePendingMsg();
+ continue;
+ }
+ }
+
+ switch (nlh->nlmsg_type) {
+ case NLMSG_DONE: /* Message signalling end of dump for responses to
+ request containing NLM_F_DUMP flag */
+ LOG(("received NLMSG_DONE"));
+ if (isResponse) {
+ RemovePendingMsg();
+ }
+ break;
+ case NLMSG_ERROR:
+ LOG(("received NLMSG_ERROR"));
+ if (isResponse) {
+ if (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg) {
+ OnRouteCheckResult(nullptr);
+ }
+ RemovePendingMsg();
+ }
+ break;
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ MOZ_ASSERT(!isResponse ||
+ (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+ OnLinkMessage(nlh);
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ MOZ_ASSERT(!isResponse ||
+ (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+ OnAddrMessage(nlh);
+ break;
+ case RTM_NEWROUTE:
+ case RTM_DELROUTE:
+ if (isResponse && ((nlh->nlmsg_flags & NLM_F_MULTI) != NLM_F_MULTI)) {
+ // If it's not multipart message, then it must be response to a route
+ // check.
+ MOZ_ASSERT(mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
+ OnRouteCheckResult(nlh);
+ RemovePendingMsg();
+ } else {
+ OnRouteMessage(nlh);
+ }
+ break;
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ MOZ_ASSERT(!isResponse ||
+ (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+ OnNeighborMessage(nlh);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void NetlinkService::OnLinkMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnLinkMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWLINK ? "new" : "del"));
+
+ UniquePtr<NetlinkLink> link(new NetlinkLink());
+ if (!link->Init(aNlh)) {
+ return;
+ }
+
+ const uint32_t linkIndex = link->GetIndex();
+ mLinks.WithEntryHandle(linkIndex, [&](auto&& entry) {
+ nsAutoCString linkName;
+ link->GetName(linkName);
+
+ if (aNlh->nlmsg_type == RTM_NEWLINK) {
+ if (!entry) {
+ LOG(("Creating new link [index=%u, name=%s, flags=%u, type=%u]",
+ linkIndex, linkName.get(), link->GetFlags(), link->GetType()));
+ entry.Insert(MakeUnique<LinkInfo>(std::move(link)));
+ } else {
+ LOG(("Updating link [index=%u, name=%s, flags=%u, type=%u]", linkIndex,
+ linkName.get(), link->GetFlags(), link->GetType()));
+
+ auto* linkInfo = entry->get();
+
+ // Check whether administrative state has changed.
+ if (linkInfo->mLink->GetFlags() & IFF_UP &&
+ !(link->GetFlags() & IFF_UP)) {
+ LOG((" link went down"));
+ // If the link went down, remove all routes and neighbors, but keep
+ // addresses.
+ linkInfo->mDefaultRoutes.Clear();
+ linkInfo->mNeighbors.Clear();
+ }
+
+ linkInfo->mLink = std::move(link);
+ linkInfo->UpdateStatus();
+ }
+ } else {
+ if (!entry) {
+ // This can happen during startup
+ LOG(("Link info doesn't exist [index=%u, name=%s]", linkIndex,
+ linkName.get()));
+ } else {
+ LOG(("Removing link [index=%u, name=%s]", linkIndex, linkName.get()));
+ entry.Remove();
+ }
+ }
+ });
+}
+
+void NetlinkService::OnAddrMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnAddrMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWADDR ? "new" : "del"));
+
+ UniquePtr<NetlinkAddress> address(new NetlinkAddress());
+ if (!address->Init(aNlh)) {
+ return;
+ }
+
+ uint32_t ifIdx = address->GetIndex();
+
+ nsAutoCString addrStr;
+ GetAddrStr(address->GetAddrPtr(), address->Family(), addrStr);
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(ifIdx, &linkInfo);
+ if (!linkInfo) {
+ // This can happen during startup
+ LOG(("Cannot find link info [ifIdx=%u, addr=%s/%u", ifIdx, addrStr.get(),
+ address->GetPrefixLen()));
+ return;
+ }
+
+ // There might be already an equal address in the array even in case of
+ // RTM_NEWADDR message, e.g. when lifetime of IPv6 address is renewed. Equal
+ // in this case means that IP and prefix is the same but some attributes
+ // might be different. Remove existing equal address in case of RTM_DELADDR
+ // as well as RTM_NEWADDR message and add a new one in the latter case.
+ for (uint32_t i = 0; i < linkInfo->mAddresses.Length(); ++i) {
+ if (aNlh->nlmsg_type == RTM_NEWADDR &&
+ linkInfo->mAddresses[i]->MsgEquals(*address)) {
+ // If the new address is exactly the same, there is nothing to do.
+ LOG(("Exactly the same address already exists [ifIdx=%u, addr=%s/%u",
+ ifIdx, addrStr.get(), address->GetPrefixLen()));
+ return;
+ }
+
+ if (linkInfo->mAddresses[i]->Equals(*address)) {
+ LOG(("Removing address [ifIdx=%u, addr=%s/%u]", ifIdx, addrStr.get(),
+ address->GetPrefixLen()));
+ linkInfo->mAddresses.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (aNlh->nlmsg_type == RTM_NEWADDR) {
+ LOG(("Adding address [ifIdx=%u, addr=%s/%u]", ifIdx, addrStr.get(),
+ address->GetPrefixLen()));
+ linkInfo->mAddresses.AppendElement(std::move(address));
+ } else {
+ // Remove all routes associated with this address
+ for (uint32_t i = linkInfo->mDefaultRoutes.Length(); i-- > 0;) {
+ MOZ_ASSERT(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(),
+ "Stored routes must have gateway!");
+ if (linkInfo->mDefaultRoutes[i]->Family() == address->Family() &&
+ address->ContainsAddr(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr())) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ linkInfo->mDefaultRoutes[i]->GetAsString(routeDbgStr);
+ LOG(("Removing default route: %s", routeDbgStr.get()));
+ }
+ linkInfo->mDefaultRoutes.RemoveElementAt(i);
+ }
+ }
+
+ // Remove all neighbors associated with this address
+ for (auto iter = linkInfo->mNeighbors.Iter(); !iter.Done(); iter.Next()) {
+ NetlinkNeighbor* neigh = iter.UserData();
+ if (neigh->Family() == address->Family() &&
+ address->ContainsAddr(neigh->GetAddrPtr())) {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Removing neighbor %s", neighDbgStr.get()));
+ }
+ iter.Remove();
+ }
+ }
+ }
+
+ // Address change on the interface can change its status
+ linkInfo->UpdateStatus();
+
+ // Don't treat address changes during initial scan as a network change
+ if (mInitialScanFinished) {
+ // Send network event change regardless of whether the ID has changed or
+ // not
+ mSendNetworkChangeEvent = true;
+ TriggerNetworkIDCalculation();
+ }
+}
+
+void NetlinkService::OnRouteMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnRouteMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWROUTE ? "new" : "del"));
+
+ UniquePtr<NetlinkRoute> route(new NetlinkRoute());
+ if (!route->Init(aNlh)) {
+ return;
+ }
+
+ if (!route->IsUnicast() || !route->ScopeIsUniverse()) {
+ // Use only unicast routes
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Not an unicast global route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ // Adding/removing any unicast route might change network ID
+ TriggerNetworkIDCalculation();
+
+ if (!route->IsDefault()) {
+ // Store only default routes
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Not a default route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ if (!route->HasOif()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("There is no output interface in route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ if (!route->GetGWAddrPtr()) {
+ // We won't use the route if there is no gateway, so don't store it
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("There is no gateway in route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ if (route->Family() == AF_INET6 &&
+ net::utils::ipv6_scope((const unsigned char*)route->GetGWAddrPtr()) !=
+ IPV6_SCOPE_GLOBAL) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Scope of GW isn't global: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(route->Oif(), &linkInfo);
+ if (!linkInfo) {
+ // This can happen during startup
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Cannot find link info for route: %s", routeDbgStr.get()));
+ }
+ return;
+ }
+
+ for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
+ if (linkInfo->mDefaultRoutes[i]->Equals(*route)) {
+ // We shouldn't find equal route when adding a new one, but just in case
+ // it can happen remove the old one to avoid duplicities.
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Removing default route: %s", routeDbgStr.get()));
+ }
+ linkInfo->mDefaultRoutes.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (aNlh->nlmsg_type == RTM_NEWROUTE) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Adding default route: %s", routeDbgStr.get()));
+ }
+ linkInfo->mDefaultRoutes.AppendElement(std::move(route));
+ }
+}
+
+void NetlinkService::OnNeighborMessage(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnNeighborMessage [type=%s]",
+ aNlh->nlmsg_type == RTM_NEWNEIGH ? "new" : "del"));
+
+ UniquePtr<NetlinkNeighbor> neigh(new NetlinkNeighbor());
+ if (!neigh->Init(aNlh)) {
+ return;
+ }
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(neigh->GetIndex(), &linkInfo);
+ if (!linkInfo) {
+ // This can happen during startup
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Cannot find link info for neighbor: %s", neighDbgStr.get()));
+ }
+ return;
+ }
+
+ if (!linkInfo->mLink->IsTypeEther()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Ignoring message on non-ethernet link: %s", neighDbgStr.get()));
+ }
+ return;
+ }
+
+ nsAutoCString key;
+ GetAddrStr(neigh->GetAddrPtr(), neigh->Family(), key);
+
+ if (aNlh->nlmsg_type == RTM_NEWNEIGH) {
+ if (!mRecalculateNetworkId && neigh->HasMAC()) {
+ NetlinkNeighbor* oldNeigh = nullptr;
+ linkInfo->mNeighbors.Get(key, &oldNeigh);
+
+ if (!oldNeigh || !oldNeigh->HasMAC()) {
+ // The MAC address was added, if it's a host from some of the saved
+ // routing tables we should recalculate network ID
+ for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
+ if (linkInfo->mDefaultRoutes[i]->GatewayEquals(*neigh)) {
+ TriggerNetworkIDCalculation();
+ break;
+ }
+ }
+ if ((mIPv4RouteCheckResult &&
+ mIPv4RouteCheckResult->GatewayEquals(*neigh)) ||
+ (mIPv6RouteCheckResult &&
+ mIPv6RouteCheckResult->GatewayEquals(*neigh))) {
+ TriggerNetworkIDCalculation();
+ }
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Adding neighbor: %s", neighDbgStr.get()));
+ }
+ linkInfo->mNeighbors.InsertOrUpdate(key, std::move(neigh));
+ } else {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Removing neighbor %s", neighDbgStr.get()));
+ }
+ linkInfo->mNeighbors.Remove(key);
+ }
+}
+
+void NetlinkService::OnRouteCheckResult(struct nlmsghdr* aNlh) {
+ LOG(("NetlinkService::OnRouteCheckResult"));
+ UniquePtr<NetlinkRoute> route;
+
+ if (aNlh) {
+ route = MakeUnique<NetlinkRoute>();
+ if (!route->Init(aNlh)) {
+ route = nullptr;
+ } else {
+ if (!route->IsUnicast() || !route->ScopeIsUniverse()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Not an unicast global route: %s", routeDbgStr.get()));
+ }
+ route = nullptr;
+ } else if (!route->HasOif()) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("There is no output interface in route: %s", routeDbgStr.get()));
+ }
+ route = nullptr;
+ }
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ if (route) {
+ nsAutoCString routeDbgStr;
+ route->GetAsString(routeDbgStr);
+ LOG(("Storing route: %s", routeDbgStr.get()));
+ } else {
+ LOG(("Clearing result for the check"));
+ }
+ }
+
+ if (mOutgoingMessages[0]->Family() == AF_INET) {
+ mIPv4RouteCheckResult = std::move(route);
+ } else {
+ mIPv6RouteCheckResult = std::move(route);
+ }
+}
+
+void NetlinkService::EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily) {
+ NetlinkGenMsg* msg = new NetlinkGenMsg(aMsgType, aFamily, ++mMsgId);
+ mOutgoingMessages.AppendElement(msg);
+}
+
+void NetlinkService::EnqueueRtMsg(uint8_t aFamily, void* aAddress) {
+ NetlinkRtMsg* msg = new NetlinkRtMsg(aFamily, aAddress, ++mMsgId);
+ mOutgoingMessages.AppendElement(msg);
+}
+
+void NetlinkService::RemovePendingMsg() {
+ LOG(("NetlinkService::RemovePendingMsg [seqId=%u]",
+ mOutgoingMessages[0]->SeqId()));
+
+ MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
+
+ DebugOnly<bool> isRtMessage =
+ (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
+
+ mOutgoingMessages.RemoveElementAt(0);
+ if (!mOutgoingMessages.Length()) {
+ if (!mInitialScanFinished) {
+ // Now we've received all initial data from the kernel. Perform a link
+ // check and trigger network ID calculation even if it wasn't triggered
+ // by the incoming messages.
+ mInitialScanFinished = true;
+
+ TriggerNetworkIDCalculation();
+
+ // Link status should be known by now.
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+ if (listener) {
+ listener->OnLinkStatusKnown();
+ }
+ } else {
+ // We've received last response for route check, calculate ID now
+ MOZ_ASSERT(isRtMessage);
+ CalculateNetworkID();
+ }
+ }
+}
+
+NS_IMETHODIMP
+NetlinkService::Run() {
+ int netlinkSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (netlinkSocket < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct sockaddr_nl addr {};
+ memset(&addr, 0, sizeof(addr));
+
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_LINK |
+ RTMGRP_NEIGH | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
+
+ if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ // failure!
+ EINTR_RETRY(close(netlinkSocket));
+ return NS_ERROR_FAILURE;
+ }
+
+ struct pollfd fds[2];
+ fds[0].fd = mShutdownPipe[0];
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+
+ fds[1].fd = netlinkSocket;
+ fds[1].events = POLLIN;
+ fds[1].revents = 0;
+
+ // send all requests to get initial network information
+ EnqueueGenMsg(RTM_GETLINK, AF_PACKET);
+ EnqueueGenMsg(RTM_GETNEIGH, AF_INET);
+ EnqueueGenMsg(RTM_GETNEIGH, AF_INET6);
+ EnqueueGenMsg(RTM_GETADDR, AF_PACKET);
+ EnqueueGenMsg(RTM_GETROUTE, AF_PACKET);
+
+ nsresult rv = NS_OK;
+ bool shutdown = false;
+ while (!shutdown) {
+ if (mOutgoingMessages.Length() && !mOutgoingMessages[0]->IsPending()) {
+ if (!mOutgoingMessages[0]->Send(netlinkSocket)) {
+ LOG(("Failed to send netlink message"));
+ mOutgoingMessages.RemoveElementAt(0);
+ // try to send another message if available before polling
+ continue;
+ }
+ }
+
+ int rc = eintr_retry([&]() {
+ AUTO_PROFILER_THREAD_SLEEP;
+ return poll(fds, 2, GetPollWait());
+ });
+
+ if (rc > 0) {
+ if (fds[0].revents & POLLIN) {
+ // shutdown, abort the loop!
+ LOG(("thread shutdown received, dying...\n"));
+ shutdown = true;
+ } else if (fds[1].revents & POLLIN) {
+ LOG(("netlink message received, handling it...\n"));
+ OnNetlinkMessage(netlinkSocket);
+ }
+ } else if (rc < 0) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+
+ EINTR_RETRY(close(netlinkSocket));
+
+ return rv;
+}
+
+nsresult NetlinkService::Init(NetlinkServiceListener* aListener) {
+ nsresult rv;
+
+ mListener = aListener;
+
+ if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
+ LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV4);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (inet_pton(AF_INET6, ROUTE_CHECK_IPV6, &mRouteCheckIPv6) != 1) {
+ LOG(("Cannot parse address " ROUTE_CHECK_IPV6));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV6);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (pipe(mShutdownPipe) == -1) {
+ LOG(("Cannot create pipe"));
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_NewNamedThread("Netlink Monitor", getter_AddRefs(mThread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult NetlinkService::Shutdown() {
+ LOG(("write() to signal thread shutdown\n"));
+
+ {
+ MutexAutoLock lock(mMutex);
+ mListener = nullptr;
+ }
+
+ // awake the thread to make it terminate
+ ssize_t rc = EINTR_RETRY(write(mShutdownPipe[1], "1", 1));
+ LOG(("write() returned %d, errno == %d\n", (int)rc, errno));
+
+ nsresult rv = mThread->Shutdown();
+
+ // Have to break the cycle here, otherwise NetlinkService holds
+ // onto the thread and the thread holds onto the NetlinkService
+ // via its mRunnable
+ mThread = nullptr;
+
+ return rv;
+}
+
+/*
+ * A network event that might change network ID has been registered. Delay
+ * network ID calculation and sending of the event in case it changed for
+ * a while. Absorbing potential subsequent events increases chance of successful
+ * network ID calculation (e.g. MAC address of the router might be discovered in
+ * the meantime)
+ */
+void NetlinkService::TriggerNetworkIDCalculation() {
+ LOG(("NetlinkService::TriggerNetworkIDCalculation"));
+
+ if (mRecalculateNetworkId) {
+ return;
+ }
+
+ mRecalculateNetworkId = true;
+ mTriggerTime = TimeStamp::Now();
+}
+
+int NetlinkService::GetPollWait() {
+ if (!mRecalculateNetworkId) {
+ return -1;
+ }
+
+ if (mOutgoingMessages.Length()) {
+ MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
+ // Message is pending, we don't have to set timeout because we'll receive
+ // reply from kernel ASAP
+ return -1;
+ }
+
+ MOZ_ASSERT(mInitialScanFinished);
+
+ double period = (TimeStamp::Now() - mTriggerTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ // Coalescing time has elapsed, send route check messages to find out
+ // where IPv4 and IPv6 traffic is routed and calculate network ID after
+ // the response is received.
+ EnqueueRtMsg(AF_INET, &mRouteCheckIPv4);
+ EnqueueRtMsg(AF_INET6, &mRouteCheckIPv6);
+
+ // Return 0 to make sure we start sending enqueued messages immediately
+ return 0;
+ }
+
+ return static_cast<int>(kNetworkChangeCoalescingPeriod - period);
+}
+
+class NeighborComparator {
+ public:
+ bool Equals(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
+ return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) == 0);
+ }
+ bool LessThan(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
+ return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) < 0);
+ }
+};
+
+class LinknameComparator {
+ public:
+ bool LessThan(const nsCString& aA, const nsCString& aB) const {
+ return aA < aB;
+ }
+ bool Equals(const nsCString& aA, const nsCString& aB) const {
+ return aA == aB;
+ }
+};
+
+// Get Gateway Neighbours for a particular Address Family, for which we know MAC
+// address
+void NetlinkService::GetGWNeighboursForFamily(
+ uint8_t aFamily, nsTArray<NetlinkNeighbor*>& aGwNeighbors) {
+ LOG(("NetlinkService::GetGWNeighboursForFamily"));
+ // Check only routes on links that are up
+ for (const auto& linkInfo : mLinks.Values()) {
+ nsAutoCString linkName;
+ linkInfo->mLink->GetName(linkName);
+
+ if (!linkInfo->mIsUp) {
+ LOG((" %s is down", linkName.get()));
+ continue;
+ }
+
+ if (!linkInfo->mLink->IsTypeEther()) {
+ LOG((" %s is not ethernet link", linkName.get()));
+ continue;
+ }
+
+ LOG((" checking link %s", linkName.get()));
+
+ // Check all default routes and try to get MAC of the gateway
+ for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ linkInfo->mDefaultRoutes[i]->GetAsString(routeDbgStr);
+ LOG(("Checking default route: %s", routeDbgStr.get()));
+ }
+
+ if (linkInfo->mDefaultRoutes[i]->Family() != aFamily) {
+ LOG((" skipping due to different family"));
+ continue;
+ }
+
+ MOZ_ASSERT(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(),
+ "Stored routes must have gateway!");
+
+ nsAutoCString neighKey;
+ GetAddrStr(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(), aFamily,
+ neighKey);
+
+ NetlinkNeighbor* neigh = nullptr;
+ if (!linkInfo->mNeighbors.Get(neighKey, &neigh)) {
+ LOG(("Neighbor %s not found in hashtable.", neighKey.get()));
+ continue;
+ }
+
+ if (!neigh->HasMAC()) {
+ // We don't know MAC address
+ LOG(("We have no MAC for neighbor %s.", neighKey.get()));
+ continue;
+ }
+
+ if (aGwNeighbors.IndexOf(neigh, 0, NeighborComparator()) !=
+ nsTArray<NetlinkNeighbor*>::NoIndex) {
+ // avoid host duplicities
+ LOG(("MAC of neighbor %s is already selected for hashing.",
+ neighKey.get()));
+ continue;
+ }
+
+ LOG(("MAC of neighbor %s will be used for network ID.", neighKey.get()));
+ aGwNeighbors.AppendElement(neigh);
+ }
+ }
+}
+
+bool NetlinkService::CalculateIDForEthernetLink(uint8_t aFamily,
+ NetlinkRoute* aRouteCheckResult,
+ uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo,
+ SHA1Sum* aSHA1) {
+ LOG(("NetlinkService::CalculateIDForEthernetLink"));
+ bool retval = false;
+ const in_common_addr* addrPtr = aRouteCheckResult->GetGWAddrPtr();
+
+ if (!addrPtr) {
+ // This shouldn't normally happen, missing next hop in case of ethernet
+ // device would mean that the checked host is on the same network.
+ if (LOG_ENABLED()) {
+ nsAutoCString routeDbgStr;
+ aRouteCheckResult->GetAsString(routeDbgStr);
+ LOG(("There is no next hop in route: %s", routeDbgStr.get()));
+ }
+ return retval;
+ }
+
+ // If we know MAC address of the next hop for mRouteCheckIPv4/6 host, hash
+ // it even if it's MAC of some of the default routes we've checked above.
+ // This ensures that if we have 2 different default routes and next hop for
+ // mRouteCheckIPv4/6 changes from one default route to the other, we'll
+ // detect it as a network change.
+ nsAutoCString neighKey;
+ GetAddrStr(addrPtr, aFamily, neighKey);
+ LOG(("Next hop for the checked host is %s on ifIdx %u.", neighKey.get(),
+ aRouteCheckIfIdx));
+
+ NetlinkNeighbor* neigh = nullptr;
+ if (!aRouteCheckLinkInfo->mNeighbors.Get(neighKey, &neigh)) {
+ LOG(("Neighbor %s not found in hashtable.", neighKey.get()));
+ return retval;
+ }
+
+ if (!neigh->HasMAC()) {
+ LOG(("We have no MAC for neighbor %s.", neighKey.get()));
+ return retval;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ neigh->GetAsString(neighDbgStr);
+ LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
+ }
+ aSHA1->update(neigh->GetMACPtr(), ETH_ALEN);
+ retval = true;
+
+ return retval;
+}
+
+bool NetlinkService::CalculateIDForNonEthernetLink(
+ uint8_t aFamily, NetlinkRoute* aRouteCheckResult,
+ nsTArray<nsCString>& aLinkNamesToHash, uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo, SHA1Sum* aSHA1) {
+ LOG(("NetlinkService::CalculateIDForNonEthernetLink"));
+ bool retval = false;
+ const in_common_addr* addrPtr = aRouteCheckResult->GetGWAddrPtr();
+ nsAutoCString routeCheckLinkName;
+ aRouteCheckLinkInfo->mLink->GetName(routeCheckLinkName);
+
+ if (addrPtr) {
+ // The route contains next hop. Hash the name of the interface (e.g.
+ // "tun1") and the IP address of the next hop.
+
+ nsAutoCString addrStr;
+ GetAddrStr(addrPtr, aFamily, addrStr);
+ size_t addrSize =
+ (aFamily == AF_INET) ? sizeof(addrPtr->addr4) : sizeof(addrPtr->addr6);
+
+ LOG(("Hashing link name %s", routeCheckLinkName.get()));
+ aSHA1->update(routeCheckLinkName.get(), routeCheckLinkName.Length());
+
+ // Don't hash GW address if it's rmnet_data device.
+ if (!aLinkNamesToHash.Contains(routeCheckLinkName)) {
+ LOG(("Hashing GW address %s", addrStr.get()));
+ aSHA1->update(addrPtr, addrSize);
+ }
+
+ retval = true;
+ } else {
+ // The traffic is routed directly via an interface. Hash the name of the
+ // interface and the network address. Using host address would cause that
+ // network ID would be different every time we get a different IP address
+ // in this network/VPN.
+
+ bool hasSrcAddr = aRouteCheckResult->HasPrefSrcAddr();
+ if (!hasSrcAddr) {
+ LOG(("There is no preferred source address."));
+ }
+
+ NetlinkAddress* linkAddress = nullptr;
+ // Find network address of the interface matching the source address. In
+ // theory there could be multiple addresses with different prefix length.
+ // Get the one with smallest prefix length.
+ for (uint32_t i = 0; i < aRouteCheckLinkInfo->mAddresses.Length(); ++i) {
+ if (!hasSrcAddr) {
+ // there is no preferred src, match just the family
+ if (aRouteCheckLinkInfo->mAddresses[i]->Family() != aFamily) {
+ continue;
+ }
+ } else if (!aRouteCheckResult->PrefSrcAddrEquals(
+ *aRouteCheckLinkInfo->mAddresses[i])) {
+ continue;
+ }
+
+ if (!linkAddress ||
+ linkAddress->GetPrefixLen() >
+ aRouteCheckLinkInfo->mAddresses[i]->GetPrefixLen()) {
+ // We have no address yet or this one has smaller prefix length,
+ // use it.
+ linkAddress = aRouteCheckLinkInfo->mAddresses[i].get();
+ }
+ }
+
+ if (!linkAddress) {
+ // There is no address in our array?
+ if (LOG_ENABLED()) {
+ nsAutoCString dbgStr;
+ aRouteCheckResult->GetAsString(dbgStr);
+ LOG(("No address found for preferred source address in route: %s",
+ dbgStr.get()));
+ }
+ return retval;
+ }
+
+ in_common_addr prefix;
+ int32_t prefixSize = (aFamily == AF_INET) ? (int32_t)sizeof(prefix.addr4)
+ : (int32_t)sizeof(prefix.addr6);
+ memcpy(&prefix, linkAddress->GetAddrPtr(), prefixSize);
+ uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+ int32_t bits = linkAddress->GetPrefixLen();
+ if (bits > prefixSize * 8) {
+ MOZ_ASSERT(false, "Unexpected prefix length!");
+ LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
+ prefixSize * 8));
+ return retval;
+ }
+ for (int32_t i = 0; i < prefixSize; i++) {
+ uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
+ ((unsigned char*)&prefix)[i] &= mask;
+ bits -= 8;
+ if (bits <= 0) {
+ bits = 0;
+ }
+ }
+
+ nsAutoCString addrStr;
+ GetAddrStr(&prefix, aFamily, addrStr);
+ LOG(("Hashing link name %s and network address %s/%u",
+ routeCheckLinkName.get(), addrStr.get(), linkAddress->GetPrefixLen()));
+ aSHA1->update(routeCheckLinkName.get(), routeCheckLinkName.Length());
+ aSHA1->update(&prefix, prefixSize);
+ bits = linkAddress->GetPrefixLen();
+ aSHA1->update(&bits, sizeof(bits));
+ retval = true;
+ }
+ return retval;
+}
+
+bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) {
+ LOG(("NetlinkService::CalculateIDForFamily [family=%s]",
+ aFamily == AF_INET ? "AF_INET" : "AF_INET6"));
+
+ bool retval = false;
+
+ if (!mLinkUp) {
+ // Skip ID calculation if the link is down, we have no ID...
+ LOG(("Link is down, skipping ID calculation."));
+ return retval;
+ }
+
+ NetlinkRoute* routeCheckResult;
+ if (aFamily == AF_INET) {
+ routeCheckResult = mIPv4RouteCheckResult.get();
+ } else {
+ routeCheckResult = mIPv6RouteCheckResult.get();
+ }
+
+ // All GW neighbors for which we know MAC address. We'll probably have at
+ // most only one, but in case we have more default routes, we hash them all
+ // even though the routing rules sends the traffic only via one of them.
+ // If the system switches between them, we'll detect the change with
+ // mIPv4/6RouteCheckResult.
+ nsTArray<NetlinkNeighbor*> gwNeighbors;
+
+ GetGWNeighboursForFamily(aFamily, gwNeighbors);
+
+ // Sort them so we always have the same network ID on the same network
+ gwNeighbors.Sort(NeighborComparator());
+
+ for (uint32_t i = 0; i < gwNeighbors.Length(); ++i) {
+ if (LOG_ENABLED()) {
+ nsAutoCString neighDbgStr;
+ gwNeighbors[i]->GetAsString(neighDbgStr);
+ LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
+ }
+ aSHA1->update(gwNeighbors[i]->GetMACPtr(), ETH_ALEN);
+ retval = true;
+ }
+
+ nsTArray<nsCString> linkNamesToHash;
+ if (!gwNeighbors.Length()) {
+ // If we don't know MAC of the gateway and link is up, it's probably not
+ // an ethernet link. If the name of the link begins with "rmnet" then
+ // the mobile data is used. We cannot easily differentiate when user
+ // switches sim cards so let's treat mobile data as a single network. We'll
+ // simply hash link name. If the traffic is redirected via some VPN, it'll
+ // still be detected below.
+
+ // TODO: maybe we could get operator name via AndroidBridge
+ for (const auto& linkInfo : mLinks.Values()) {
+ if (linkInfo->mIsUp) {
+ nsAutoCString linkName;
+ linkInfo->mLink->GetName(linkName);
+ if (StringBeginsWith(linkName, "rmnet"_ns)) {
+ // Check whether there is some non-local address associated with this
+ // link.
+ for (uint32_t i = 0; i < linkInfo->mAddresses.Length(); ++i) {
+ if (linkInfo->mAddresses[i]->Family() == aFamily &&
+ linkInfo->mAddresses[i]->ScopeIsUniverse()) {
+ linkNamesToHash.AppendElement(linkName);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Sort link names to ensure consistent results
+ linkNamesToHash.Sort(LinknameComparator());
+
+ for (uint32_t i = 0; i < linkNamesToHash.Length(); ++i) {
+ LOG(("Hashing name of adapter: %s", linkNamesToHash[i].get()));
+ aSHA1->update(linkNamesToHash[i].get(), linkNamesToHash[i].Length());
+ retval = true;
+ }
+ }
+
+ if (!routeCheckResult) {
+ // If we don't have result for route check to mRouteCheckIPv4/6 host, the
+ // network is unreachable and there is no more to do.
+ LOG(("There is no route check result."));
+ return retval;
+ }
+
+ LinkInfo* routeCheckLinkInfo = nullptr;
+ uint32_t routeCheckIfIdx = routeCheckResult->Oif();
+ if (!mLinks.Get(routeCheckIfIdx, &routeCheckLinkInfo)) {
+ LOG(("Cannot find link with index %u ??", routeCheckIfIdx));
+ return retval;
+ }
+
+ if (routeCheckLinkInfo->mLink->IsTypeEther()) {
+ // The traffic is routed through an ethernet device.
+ retval |= CalculateIDForEthernetLink(
+ aFamily, routeCheckResult, routeCheckIfIdx, routeCheckLinkInfo, aSHA1);
+ } else {
+ // The traffic is routed through a non-ethernet device.
+ retval |= CalculateIDForNonEthernetLink(aFamily, routeCheckResult,
+ linkNamesToHash, routeCheckIfIdx,
+ routeCheckLinkInfo, aSHA1);
+ }
+
+ return retval;
+}
+
+void NetlinkService::ExtractDNSProperties() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+ nsTArray<nsCString> suffixList;
+ nsTArray<NetAddr> resolvers;
+#if defined(HAVE_RES_NINIT)
+ [&]() {
+ struct __res_state res {};
+ int ret = res_ninit(&res);
+ if (ret != 0) {
+ LOG(("Call to res_ninit failed: %d", ret));
+ return;
+ }
+
+ // Get DNS suffixes
+ for (int i = 0; i < MAXDNSRCH; i++) {
+ if (!res.dnsrch[i]) {
+ break;
+ }
+ suffixList.AppendElement(nsCString(res.dnsrch[i]));
+ }
+
+ // Get DNS resolvers
+ // Chromium's dns_config_service_posix.cc is the origin of this code
+ // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
+ // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
+ // but we have to combine the two arrays ourselves.
+ for (int i = 0; i < res.nscount; ++i) {
+ const struct sockaddr* addr = nullptr;
+ size_t addr_len = 0;
+ if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
+ addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
+ addr_len = sizeof res.nsaddr_list[i];
+ } else if (res._u._ext.nsaddrs[i]) {
+ addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
+ addr_len = sizeof *res._u._ext.nsaddrs[i];
+ } else {
+ LOG(("Bad ext struct"));
+ return;
+ }
+ const socklen_t kSockaddrInSize = sizeof(struct sockaddr_in);
+ const socklen_t kSockaddrIn6Size = sizeof(struct sockaddr_in6);
+
+ if ((addr->sa_family == AF_INET && addr_len < kSockaddrInSize) ||
+ (addr->sa_family == AF_INET6 && addr_len < kSockaddrIn6Size)) {
+ LOG(("Bad address size"));
+ return;
+ }
+
+ NetAddr ip;
+ if (addr->sa_family == AF_INET) {
+ const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
+ ip.inet.family = AF_INET;
+ ip.inet.ip = sin->sin_addr.s_addr;
+ ip.inet.port = sin->sin_port;
+ } else if (addr->sa_family == AF_INET6) {
+ const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)addr;
+ ip.inet6.family = AF_INET6;
+ memcpy(&ip.inet6.ip.u8, &sin6->sin6_addr, sizeof(ip.inet6.ip.u8));
+ ip.inet6.port = sin6->sin6_port;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected sa_family");
+ return;
+ }
+
+ resolvers.AppendElement(ip);
+ }
+
+ res_nclose(&res);
+ }();
+
+#endif
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ mDNSSuffixList = std::move(suffixList);
+ mDNSResolvers = std::move(resolvers);
+ }
+
+ if (listener) {
+ listener->OnDnsSuffixListUpdated();
+ }
+}
+
+void NetlinkService::UpdateLinkStatus() {
+ LOG(("NetlinkService::UpdateLinkStatus"));
+
+ MOZ_ASSERT(!mRecalculateNetworkId);
+ MOZ_ASSERT(mInitialScanFinished);
+
+ // Link is up when we have a route for ROUTE_CHECK_IPV4 or ROUTE_CHECK_IPV6
+ bool newLinkUp = mIPv4RouteCheckResult || mIPv6RouteCheckResult;
+
+ if (mLinkUp == newLinkUp) {
+ LOG(("Link status hasn't changed [linkUp=%d]", mLinkUp));
+ } else {
+ LOG(("Link status has changed [linkUp=%d]", newLinkUp));
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ mLinkUp = newLinkUp;
+ }
+ if (mLinkUp) {
+ if (listener) {
+ listener->OnLinkUp();
+ }
+ } else {
+ if (listener) {
+ listener->OnLinkDown();
+ }
+ }
+ }
+}
+
+// Figure out the "network identification".
+void NetlinkService::CalculateNetworkID() {
+ LOG(("NetlinkService::CalculateNetworkID"));
+
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+ MOZ_ASSERT(mRecalculateNetworkId);
+
+ mRecalculateNetworkId = false;
+
+ SHA1Sum sha1;
+
+ UpdateLinkStatus();
+ ExtractDNSProperties();
+
+ bool idChanged = false;
+ bool found4 = CalculateIDForFamily(AF_INET, &sha1);
+ bool found6 = CalculateIDForFamily(AF_INET6, &sha1);
+
+ if (found4 || found6) {
+ nsAutoCString output;
+ SeedNetworkId(sha1);
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsAutoCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ nsresult rv = Base64Encode(newString, output);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("networkid: id %s\n", output.get()));
+ MutexAutoLock lock(mMutex);
+ if (mNetworkId != output) {
+ // new id
+ if (found4 && !found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); // IPv4 only
+ } else if (!found4 && found6) {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3); // IPv6 only
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4); // Both!
+ }
+ mNetworkId = output;
+ idChanged = true;
+ } else {
+ // same id
+ LOG(("Same network id"));
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
+ }
+ } else {
+ // no id
+ LOG(("No network id"));
+ MutexAutoLock lock(mMutex);
+ if (!mNetworkId.IsEmpty()) {
+ mNetworkId.Truncate();
+ idChanged = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+ }
+ }
+
+ // If this is first time we calculate network ID, don't report it as a network
+ // change. We've started with an empty ID and we've just calculated the
+ // correct ID. The network hasn't really changed.
+ static bool initialIDCalculation = true;
+
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+
+ if (!initialIDCalculation && idChanged && listener) {
+ listener->OnNetworkIDChanged();
+ mSendNetworkChangeEvent = true;
+ }
+
+ if (mSendNetworkChangeEvent && listener) {
+ listener->OnNetworkChanged();
+ }
+
+ initialIDCalculation = false;
+ mSendNetworkChangeEvent = false;
+}
+
+void NetlinkService::GetNetworkID(nsACString& aNetworkID) {
+ MutexAutoLock lock(mMutex);
+ aNetworkID = mNetworkId;
+}
+
+nsresult NetlinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
+#if defined(HAVE_RES_NINIT)
+ MutexAutoLock lock(mMutex);
+ aDnsSuffixList = mDNSSuffixList.Clone();
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+nsresult NetlinkService::GetResolvers(nsTArray<NetAddr>& aResolvers) {
+#if defined(HAVE_RES_NINIT)
+ MutexAutoLock lock(mMutex);
+ aResolvers = mDNSResolvers.Clone();
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+void NetlinkService::GetIsLinkUp(bool* aIsUp) {
+ MutexAutoLock lock(mMutex);
+ *aIsUp = mLinkUp;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/system/netlink/NetlinkService.h b/netwerk/system/netlink/NetlinkService.h
new file mode 100644
index 0000000000..1de3992f47
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.h
@@ -0,0 +1,168 @@
+/* -*- 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 NETLINKSERVICE_H_
+#define NETLINKSERVICE_H_
+
+#include <netinet/in.h>
+
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsClassHashtable.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "mozilla/net/DNS.h"
+
+namespace mozilla {
+namespace net {
+
+class NetlinkAddress;
+class NetlinkNeighbor;
+class NetlinkLink;
+class NetlinkRoute;
+class NetlinkMsg;
+
+class NetlinkServiceListener : public nsISupports {
+ public:
+ virtual void OnNetworkChanged() = 0;
+ virtual void OnNetworkIDChanged() = 0;
+ virtual void OnLinkUp() = 0;
+ virtual void OnLinkDown() = 0;
+ virtual void OnLinkStatusKnown() = 0;
+ virtual void OnDnsSuffixListUpdated() = 0;
+
+ protected:
+ virtual ~NetlinkServiceListener() = default;
+};
+
+class NetlinkService : public nsIRunnable {
+ virtual ~NetlinkService();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ NetlinkService();
+ nsresult Init(NetlinkServiceListener* aListener);
+ nsresult Shutdown();
+ void GetNetworkID(nsACString& aNetworkID);
+ void GetIsLinkUp(bool* aIsUp);
+ nsresult GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList);
+ nsresult GetResolvers(nsTArray<NetAddr>& aResolvers);
+
+ private:
+ void EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily);
+ void EnqueueRtMsg(uint8_t aFamily, void* aAddress);
+ void RemovePendingMsg();
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED{"NetlinkService::mMutex"};
+
+ void OnNetlinkMessage(int aNetlinkSocket);
+ void OnLinkMessage(struct nlmsghdr* aNlh);
+ void OnAddrMessage(struct nlmsghdr* aNlh);
+ void OnRouteMessage(struct nlmsghdr* aNlh);
+ void OnNeighborMessage(struct nlmsghdr* aNlh);
+ void OnRouteCheckResult(struct nlmsghdr* aNlh);
+
+ void UpdateLinkStatus();
+
+ void TriggerNetworkIDCalculation();
+ int GetPollWait();
+ void GetGWNeighboursForFamily(uint8_t aFamily,
+ nsTArray<NetlinkNeighbor*>& aGwNeighbors);
+ bool CalculateIDForFamily(uint8_t aFamily, mozilla::SHA1Sum* aSHA1);
+ void CalculateNetworkID();
+ void ExtractDNSProperties();
+
+ nsCOMPtr<nsIThread> mThread;
+
+ bool mInitialScanFinished{false};
+
+ // A pipe to signal shutdown with.
+ int mShutdownPipe[2]{-1, -1};
+
+ // IP addresses that are used to check the route for public traffic.
+ struct in_addr mRouteCheckIPv4 {};
+ struct in6_addr mRouteCheckIPv6 {};
+
+ pid_t mPid;
+ uint32_t mMsgId{0};
+
+ bool mLinkUp{true};
+
+ // Flag indicating that network ID could change and should be recalculated.
+ // Calculation is postponed until we receive responses to all enqueued
+ // messages.
+ bool mRecalculateNetworkId{false};
+
+ // Flag indicating that network change event needs to be sent even if
+ // network ID hasn't changed.
+ bool mSendNetworkChangeEvent{false};
+
+ // Time stamp of setting mRecalculateNetworkId to true
+ mozilla::TimeStamp mTriggerTime;
+
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDNSSuffixList;
+ nsTArray<NetAddr> mDNSResolvers;
+
+ class LinkInfo {
+ public:
+ explicit LinkInfo(UniquePtr<NetlinkLink>&& aLink);
+ virtual ~LinkInfo();
+
+ // Updates mIsUp according to current mLink and mAddresses. Returns true if
+ // the value has changed.
+ bool UpdateStatus();
+
+ // NetlinkLink structure for this link
+ UniquePtr<NetlinkLink> mLink;
+
+ // All IPv4/IPv6 addresses on this link
+ nsTArray<UniquePtr<NetlinkAddress>> mAddresses;
+
+ // All neighbors on this link, key is an address
+ nsClassHashtable<nsCStringHashKey, NetlinkNeighbor> mNeighbors;
+
+ // Default IPv4/IPv6 routes
+ nsTArray<UniquePtr<NetlinkRoute>> mDefaultRoutes;
+
+ // Link is up when it's running, it's not a loopback and there is
+ // a non-local address associated with it.
+ bool mIsUp;
+ };
+
+ bool CalculateIDForEthernetLink(uint8_t aFamily,
+ NetlinkRoute* aRouteCheckResult,
+ uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo,
+ mozilla::SHA1Sum* aSHA1);
+ bool CalculateIDForNonEthernetLink(uint8_t aFamily,
+ NetlinkRoute* aRouteCheckResult,
+ nsTArray<nsCString>& aLinkNamesToHash,
+ uint32_t aRouteCheckIfIdx,
+ LinkInfo* aRouteCheckLinkInfo,
+ mozilla::SHA1Sum* aSHA1);
+
+ nsClassHashtable<nsUint32HashKey, LinkInfo> mLinks;
+
+ // Route for mRouteCheckIPv4 address
+ UniquePtr<NetlinkRoute> mIPv4RouteCheckResult;
+ // Route for mRouteCheckIPv6 address
+ UniquePtr<NetlinkRoute> mIPv6RouteCheckResult;
+
+ nsTArray<UniquePtr<NetlinkMsg>> mOutgoingMessages;
+
+ RefPtr<NetlinkServiceListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NETLINKSERVICE_H_ */
diff --git a/netwerk/system/netlink/moz.build b/netwerk/system/netlink/moz.build
new file mode 100644
index 0000000000..6882a21126
--- /dev/null
+++ b/netwerk/system/netlink/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"] == "Linux":
+ SOURCES += [
+ "NetlinkService.cpp",
+ ]
+
+FINAL_LIBRARY = "xul"
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_ */