diff options
Diffstat (limited to 'netwerk/system')
-rw-r--r-- | netwerk/system/NetworkLinkServiceDefines.h | 21 | ||||
-rw-r--r-- | netwerk/system/android/moz.build | 14 | ||||
-rw-r--r-- | netwerk/system/android/nsAndroidNetworkLinkService.cpp | 224 | ||||
-rw-r--r-- | netwerk/system/android/nsAndroidNetworkLinkService.h | 48 | ||||
-rw-r--r-- | netwerk/system/linux/moz.build | 12 | ||||
-rw-r--r-- | netwerk/system/linux/nsNetworkLinkService.cpp | 188 | ||||
-rw-r--r-- | netwerk/system/linux/nsNetworkLinkService.h | 47 | ||||
-rw-r--r-- | netwerk/system/mac/moz.build | 14 | ||||
-rw-r--r-- | netwerk/system/mac/nsNetworkLinkService.h | 80 | ||||
-rw-r--r-- | netwerk/system/mac/nsNetworkLinkService.mm | 1065 | ||||
-rw-r--r-- | netwerk/system/moz.build | 17 | ||||
-rw-r--r-- | netwerk/system/netlink/NetlinkService.cpp | 1838 | ||||
-rw-r--r-- | netwerk/system/netlink/NetlinkService.h | 166 | ||||
-rw-r--r-- | netwerk/system/netlink/moz.build | 12 | ||||
-rw-r--r-- | netwerk/system/win32/moz.build | 12 | ||||
-rw-r--r-- | netwerk/system/win32/nsNotifyAddrListener.cpp | 670 | ||||
-rw-r--r-- | netwerk/system/win32/nsNotifyAddrListener.h | 93 |
17 files changed, 4521 insertions, 0 deletions
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..4007a3239f --- /dev/null +++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp @@ -0,0 +1,224 @@ +/* -*- 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" + +namespace java = mozilla::java; + +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 (!mozilla::AndroidBridge::Bridge()) { + NS_WARNING("GetDnsSuffixList is not supported without a bridge connection"); + return NS_ERROR_NOT_AVAILABLE; + } + + auto suffixList = java::GeckoAppShell::GetDNSDomains(); + if (!suffixList) { + return NS_OK; + } + + nsAutoCString list(suffixList->ToCString()); + for (const nsACString& suffix : list.Split(',')) { + aDnsSuffixList.AppendElement(suffix); + } + return NS_OK; +} + +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..5f7b826cea --- /dev/null +++ b/netwerk/system/linux/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 += [ + "nsNetworkLinkService.cpp", + ] + +FINAL_LIBRARY = "xul" diff --git a/netwerk/system/linux/nsNetworkLinkService.cpp b/netwerk/system/linux/nsNetworkLinkService.cpp new file mode 100644 index 0000000000..7c6c791258 --- /dev/null +++ b/netwerk/system/linux/nsNetworkLinkService.cpp @@ -0,0 +1,188 @@ +/* -*- 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 "nsServiceManagerUtils.h" +#include "nsNetworkLinkService.h" +#include "nsString.h" +#include "mozilla/Logging.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; + } + + mNetlinkSvc->GetDnsSuffixList(aDnsSuffixList); + return NS_OK; +} + +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..20e7d3d16f --- /dev/null +++ b/netwerk/system/mac/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 += [ + "nsNetworkLinkService.mm", +] + +FINAL_LIBRARY = "xul" + +if CONFIG["CC_TYPE"] == "clang": + CXXFLAGS += ["-Wno-error=shadow"] diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h new file mode 100644 index 0000000000..72522317d2 --- /dev/null +++ b/netwerk/system/mac/nsNetworkLinkService.h @@ -0,0 +1,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 NSNETWORKLINKSERVICEMAC_H_ +#define NSNETWORKLINKSERVICEMAC_H_ + +#include "nsINetworkLinkService.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "mozilla/Mutex.h" +#include "mozilla/SHA1.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: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINETWORKLINKSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + + 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(); + void GetDnsSuffixListInternal(); + bool RoutingFromKernel(nsTArray<nsCString>& aHash); + bool RoutingTable(nsTArray<nsCString>& aHash); + + mozilla::Mutex mMutex; + 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; + + // 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..f4d7205663 --- /dev/null +++ b/netwerk/system/mac/nsNetworkLinkService.mm @@ -0,0 +1,1065 @@ +/* -*- 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 "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 "../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; + +// 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) + +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; +} + +// Note that this function is copied from xpcom/io/nsLocalFileUnix.cpp. +static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) { + // first see if the conversion would succeed and find the length of the result + CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef); + CFIndex charsConverted = + ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false, + nullptr, 0, &usedBufLen); + if (charsConverted == inStrLen) { + // all characters converted, do the actual conversion + aOutStr.SetLength(usedBufLen); + if (aOutStr.Length() != (unsigned int)usedBufLen) { + return NS_ERROR_OUT_OF_MEMORY; + } + UInt8* buffer = (UInt8*)aOutStr.BeginWriting(); + ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false, buffer, + usedBufLen, &usedBufLen); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +static void GetDNSSearchDomains(const SCDynamicStoreRef aStoreRef, const CFStringRef aPattern, + nsTArray<nsCString>& aResult) { + CFDictionaryRef dnsDict = (CFDictionaryRef)SCDynamicStoreCopyValue(aStoreRef, aPattern); + if (dnsDict) { + CFTypeRef domains; + if (::CFDictionaryGetValueIfPresent(dnsDict, kSCPropNetDNSSearchDomains, &domains)) { + if (domains && ::CFGetTypeID(domains) == ::CFArrayGetTypeID()) { + int count = ::CFArrayGetCount(static_cast<CFArrayRef>(domains)); + for (int i = 0; i < count; i++) { + CFTypeRef domain = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(domains), i); + if (domain && ::CFGetTypeID(domain) == ::CFStringGetTypeID()) { + nsAutoCString domainStr; + if (NS_SUCCEEDED(CFStringReftoUTF8(static_cast<CFStringRef>(domain), domainStr))) { + LOG(("DNS search domain [%s]\n", domainStr.get())); + aResult.AppendElement(domainStr); + } + } + } + } + } + } + CFReleaseSafe(dnsDict); +} + +static OSStatus GetPrimaryServiceInfo(const SCDynamicStoreRef aStoreRef, nsACString& aServiceName, + nsACString& aServiceId) { + OSStatus err = getErrorCodePtr(aStoreRef); + if (err != noErr) { + return err; + } + + CFStringRef globalIPv4StateKey = ::SCDynamicStoreKeyCreateNetworkGlobalEntity( + nullptr, kSCDynamicStoreDomainState, kSCEntNetIPv4); + err = getErrorCodePtr(globalIPv4StateKey); + + // Get the primary interface. + CFDictionaryRef primaryInterface = nullptr; + if (err == noErr) { + primaryInterface = (CFDictionaryRef)::SCDynamicStoreCopyValue(aStoreRef, globalIPv4StateKey); + err = getErrorCodePtr(primaryInterface); + } + + CFTypeRef serviceId; + if (err == noErr && ::CFDictionaryGetValueIfPresent( + primaryInterface, kSCDynamicStorePropNetPrimaryService, &serviceId)) { + err = -1; + if (serviceId && ::CFGetTypeID(serviceId) == ::CFStringGetTypeID()) { + if (NS_SUCCEEDED(CFStringReftoUTF8(static_cast<CFStringRef>(serviceId), aServiceId))) { + err = noErr; + } + } + } + + CFTypeRef serviceName; + if (err == noErr && ::CFDictionaryGetValueIfPresent( + primaryInterface, kSCDynamicStorePropNetPrimaryInterface, &serviceName)) { + err = -1; + if (serviceName && ::CFGetTypeID(serviceName) == ::CFStringGetTypeID()) { + if (NS_SUCCEEDED(CFStringReftoUTF8(static_cast<CFStringRef>(serviceName), aServiceName))) { + err = noErr; + } + } + } + + CFReleaseSafe(globalIPv4StateKey); + CFReleaseSafe(primaryInterface); + + if (err == noErr) { + return noErr; + } + + return err; +} + +static OSStatus IsInterfaceActive(const SCDynamicStoreRef aStoreRef, const char* aInterfaceName, + bool& aResult) { + aResult = false; + + OSStatus err = getErrorCodePtr(aStoreRef); + if (err != noErr) { + return err; + } + + CFStringRef serviceNameRef = nullptr; + if (err == noErr) { + serviceNameRef = CFStringCreateWithCString(nullptr, aInterfaceName, kCFStringEncodingUTF8); + err = getErrorCodePtr(serviceNameRef); + } + + CFStringRef linkPattern = nullptr; + if (err == noErr) { + linkPattern = ::SCDynamicStoreKeyCreateNetworkInterfaceEntity( + nullptr, kSCDynamicStoreDomainState, serviceNameRef, kSCEntNetLink); + err = getErrorCodePtr(linkPattern); + } + + CFDictionaryRef dict = nullptr; + if (err == noErr) { + dict = (CFDictionaryRef)SCDynamicStoreCopyValue(aStoreRef, linkPattern); + err = getErrorCodePtr(dict); + } + + if (err == noErr) { + CFTypeRef activeRef; + err = -1; + if (::CFDictionaryGetValueIfPresent(dict, kSCPropNetLinkActive, &activeRef)) { + if (activeRef && ::CFGetTypeID(activeRef) == ::CFBooleanGetTypeID()) { + aResult = ::CFBooleanGetValue(static_cast<CFBooleanRef>(activeRef)); + err = noErr; + } + } + } + + CFReleaseSafe(dict); + CFReleaseSafe(linkPattern); + CFReleaseSafe(serviceNameRef); + return err; +} + +void nsNetworkLinkService::GetDnsSuffixListInternal() { + MOZ_ASSERT(!NS_IsMainThread()); + + RefPtr<nsNetworkLinkService> self = this; + auto sendNotification = mozilla::MakeScopeExit([self] { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsNetworkLinkService::GetDnsSuffixListInternal", + [self]() { self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr); })); + }); + + nsAutoCString primaryServiceName; + nsAutoCString primaryServiceId; + OSStatus err = GetPrimaryServiceInfo(mStoreRef, primaryServiceName, primaryServiceId); + + bool active = false; + if (err == noErr) { + err = IsInterfaceActive(mStoreRef, primaryServiceName.get(), active); + LOG(("primaryServiceName:[%s] active=%d", primaryServiceName.get(), active)); + } + + if (err != noErr || !active) { + // Primary interface is not active. Clear the DNS suffix list. + MutexAutoLock lock(mMutex); + mDNSSuffixList.Clear(); + return; + } + + nsTArray<nsCString> result; + + CFStringRef serviceIdRef = + CFStringCreateWithCString(nullptr, primaryServiceId.get(), kCFStringEncodingUTF8); + err = getErrorCodePtr(serviceIdRef); + if (err == noErr) { + CFStringRef dnsSetupPattern = ::SCDynamicStoreKeyCreateNetworkServiceEntity( + nullptr, kSCDynamicStoreDomainSetup, serviceIdRef, kSCEntNetDNS); + CFStringRef dnsStatePattern = ::SCDynamicStoreKeyCreateNetworkServiceEntity( + nullptr, kSCDynamicStoreDomainState, serviceIdRef, kSCEntNetDNS); + err = getErrorCodePtr(dnsSetupPattern); + if (err == noErr) { + err = getErrorCodePtr(dnsStatePattern); + if (err == noErr) { + GetDNSSearchDomains(mStoreRef, dnsSetupPattern, result); + GetDNSSearchDomains(mStoreRef, dnsStatePattern, result); + } + } + CFReleaseSafe(dnsStatePattern); + CFReleaseSafe(dnsSetupPattern); + } + CFReleaseSafe(serviceIdRef); + + if (err == noErr) { + 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; +} + +#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; + if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) { + return false; + } + if (needed == 0) { + LOG(("scanArp: empty table")); + return false; + } + + UniquePtr<char[]> buf(new char[needed]); + + for (;;) { + st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0); + if (st == 0 || errno != ENOMEM) { + break; + } + needed += needed / 8; + + auto tmp = MakeUnique<char[]>(needed); + memcpy(&tmp[0], &buf[0], needed); + buf = std::move(tmp); + } + 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, ¤t.first, sizeof(in6_addr)) == 0 && + memcmp(&prefixAndNetmask.second, ¤t.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; +} + +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) { + // This 'addition' could potentially be a fixed number from the + // profile or something. + nsAutoCString addition("local-rubbish"); + nsAutoCString output; + sha1.update(addition.get(), addition.Length()); + 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(); + } +} + +void nsNetworkLinkService::DNSConfigChanged() { + LOG(("nsNetworkLinkService::DNSConfigChanged")); + nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + if (target) { + RefPtr<nsNetworkLinkService> self = this; + MOZ_ALWAYS_SUCCEEDS( + target->Dispatch(NS_NewRunnableFunction("nsNetworkLinkService::GetDnsSuffixListInternal", + [self]() { 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(); + + 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; + } + + 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(); +} diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build new file mode 100644 index 0000000000..4e9e29204a --- /dev/null +++ b/netwerk/system/moz.build @@ -0,0 +1,17 @@ +# -*- 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"] diff --git a/netwerk/system/netlink/NetlinkService.cpp b/netwerk/system/netlink/NetlinkService.cpp new file mode 100644 index 0000000000..7c2c7d7e67 --- /dev/null +++ b/netwerk/system/netlink/NetlinkService.cpp @@ -0,0 +1,1838 @@ +/* -*- 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 <linux/rtnetlink.h> + +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "NetlinkService.h" +#include "nsIThread.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "mozilla/Logging.h" +#include "../../base/IPv6Utils.h" +#include "../NetworkLinkServiceDefines.h" + +#include "mozilla/Base64.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Services.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/DebugOnly.h" + +#if defined(HAVE_RES_NINIT) +# include <netinet/in.h> +# include <arpa/nameser.h> +# include <resolv.h> +#endif + +/* a shorter name that better explains what it does */ +#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x) + +namespace mozilla::net { + +// 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) + +typedef union { + struct in_addr addr4; + struct in6_addr addr6; +} in_common_addr; + +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() : mHasMAC(false) {} + + 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; + 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))) { + 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))) { + return false; + } + if ((mHasDstAddr != aOther.mHasDstAddr) || + (mHasDstAddr && memcmp(&mDstAddr, &(aOther.mDstAddr), addrSize))) { + return false; + } + if ((mHasPrefSrcAddr != aOther.mHasPrefSrcAddr) || + (mHasPrefSrcAddr && + memcmp(&mPrefSrcAddr, &(aOther.mPrefSrcAddr), addrSize))) { + 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() : mIsPending(false) {} + 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; +}; + +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() + : mMutex("NetlinkService::mMutex"), + mInitialScanFinished(false), + mMsgId(0), + mLinkUp(true), + mRecalculateNetworkId(false), + mSendNetworkChangeEvent(false) { + mPid = getpid(); + mShutdownPipe[0] = -1; + mShutdownPipe[1] = -1; +} + +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; + } + + uint32_t linkIndex = link->GetIndex(); + nsAutoCString linkName; + link->GetName(linkName); + + LinkInfo* linkInfo = nullptr; + mLinks.Get(linkIndex, &linkInfo); + + if (aNlh->nlmsg_type == RTM_NEWLINK) { + if (!linkInfo) { + LOG(("Creating new link [index=%u, name=%s, flags=%u, type=%u]", + linkIndex, linkName.get(), link->GetFlags(), link->GetType())); + linkInfo = new LinkInfo(std::move(link)); + mLinks.Put(linkIndex, linkInfo); + } else { + LOG(("Updating link [index=%u, name=%s, flags=%u, type=%u]", linkIndex, + linkName.get(), link->GetFlags(), link->GetType())); + + // 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 (!linkInfo) { + // 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())); + mLinks.Remove(linkIndex); + } + } +} + +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.Put(key, neigh.release()); + } 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(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 (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) { + LinkInfo* linkInfo = iter.UserData(); + 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 (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) { + LinkInfo* linkInfo = iter.UserData(); + 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::ComputeDNSSuffixList() { + MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread"); + nsTArray<nsCString> suffixList; +#if defined(HAVE_RES_NINIT) + struct __res_state res; + if (res_ninit(&res) == 0) { + for (int i = 0; i < MAXDNSRCH; i++) { + if (!res.dnsrch[i]) { + break; + } + suffixList.AppendElement(nsCString(res.dnsrch[i])); + } + res_nclose(&res); + } +#endif + RefPtr<NetlinkServiceListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + mDNSSuffixList = std::move(suffixList); + } + 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(); + ComputeDNSSuffixList(); + + bool idChanged = false; + bool found4 = CalculateIDForFamily(AF_INET, &sha1); + bool found6 = CalculateIDForFamily(AF_INET6, &sha1); + + if (found4 || found6) { + // This 'addition' could potentially be a fixed number from the + // profile or something. + nsAutoCString addition("local-rubbish"); + nsAutoCString output; + sha1.update(addition.get(), addition.Length()); + 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 +} + +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..85796eb508 --- /dev/null +++ b/netwerk/system/netlink/NetlinkService.h @@ -0,0 +1,166 @@ +/* -*- 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 <linux/netlink.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" + +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); + + private: + void EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily); + void EnqueueRtMsg(uint8_t aFamily, void* aAddress); + void RemovePendingMsg(); + + mozilla::Mutex 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 ComputeDNSSuffixList(); + + nsCOMPtr<nsIThread> mThread; + + bool mInitialScanFinished; + + // A pipe to signal shutdown with. + int mShutdownPipe[2]; + + // 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; + + bool mLinkUp; + + // Flag indicating that network ID could change and should be recalculated. + // Calculation is postponed until we receive responses to all enqueued + // messages. + bool mRecalculateNetworkId; + + // Flag indicating that network change event needs to be sent even if + // network ID hasn't changed. + bool mSendNetworkChangeEvent; + + // Time stamp of setting mRecalculateNetworkId to true + mozilla::TimeStamp mTriggerTime; + + nsCString mNetworkId; + nsTArray<nsCString> mDNSSuffixList; + + 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..0f4a2c2c96 --- /dev/null +++ b/netwerk/system/win32/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "nsNotifyAddrListener.cpp", + ] + +FINAL_LIBRARY = "xul" diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp new file mode 100644 index 0000000000..ce47ca99c1 --- /dev/null +++ b/netwerk/system/win32/nsNotifyAddrListener.cpp @@ -0,0 +1,670 @@ +/* -*- 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 "plstr.h" +#include "mozilla/Logging.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsIWindowsRegKey.h" +#include "nsServiceManagerUtils.h" +#include "nsNotifyAddrListener.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "mozilla/Services.h" +#include "nsCRT.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/SHA1.h" +#include "mozilla/Base64.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Telemetry.h" +#include <iptypes.h> +#include <iphlpapi.h> + +using namespace mozilla; + +static LazyLogModule gNotifyAddrLog("nsNotifyAddr"); +#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args) +#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::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%lX", + 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: %X", hr)); + return; + } + RefPtr<IEnumNetworks> enumNetworks; + hr = nlm->GetNetworks(NLM_ENUM_NETWORK_CONNECTED, + getter_AddRefs(enumNetworks)); + if (NS_WARN_IF(FAILED(hr))) { + LOG(("GetNetworks error: %X", 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); + + 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", 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_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_ANYCAST; + + DWORD ret = + GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len); + if (ret == ERROR_BUFFER_OVERFLOW) { + free(adapterList); + adapterList = static_cast<PIP_ADAPTER_ADDRESSES>(moz_xmalloc(len)); + + ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len); + } + + if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { + free(adapterList); + return ERROR_NOT_SUPPORTED; + } + + // + // Since NotifyIpInterfaceChange() signals a change more often than we + // think is a worthy change, we checksum the entire state of all interfaces + // that are UP. If the checksum is the same as previous check, nothing + // of interest changed! + // + ULONG sumAll = 0; + + nsTArray<nsCString> dnsSuffixList; + 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; + } + + if (adapter->IfType == IF_TYPE_PPP) { + 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]; + } + } + + 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); + 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..8a39186bd3 --- /dev/null +++ b/netwerk/system/win32/nsNotifyAddrListener.h @@ -0,0 +1,93 @@ +/* -*- 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 "nsThreadUtils.h" +#include "nsThreadPool.h" +#include "nsCOMPtr.h" +#include "mozilla/Atomics.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Mutex.h" +#include "mozilla/SHA1.h" + +class nsNotifyAddrListener : public nsINetworkLinkService, + public nsIRunnable, + public nsIObserver { + virtual ~nsNotifyAddrListener(); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINETWORKLINKSERVICE + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + + nsNotifyAddrListener(); + + nsresult Init(void); + void CheckLinkStatus(void); + 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; + nsCString mNetworkId; + nsTArray<nsCString> mDnsSuffixList; + + 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_ */ |