From 390918caab29d037e7cb28ba15f2e0800adfcad6 Mon Sep 17 00:00:00 2001 From: Michal Novotny Date: Mon, 26 Aug 2019 21:46:32 +0000 Subject: [PATCH] Bug 1567616 - network id based on default gateway is wrong when VPN overrides default gateway by more specific routes r=dragana This patch implements NetlinkService which communicates with kernel via netlink socket. It keeps track of addresses, default routes, interfaces and neighbors and uses it to calculate network ID. Differential Revision: https://phabricator.services.mozilla.com/D43175 --HG-- rename : netwerk/system/linux/nsNotifyAddrListener_Linux.cpp => netwerk/system/linux/nsNetworkLinkService.cpp rename : netwerk/system/linux/nsNotifyAddrListener_Linux.h => netwerk/system/linux/nsNetworkLinkService.h extra : moz-landing-system : lando --- modules/libpref/init/StaticPrefList.yaml | 31 + modules/libpref/init/all.js | 11 - netwerk/base/nsIOService.cpp | 14 +- netwerk/base/nsIOService.h | 2 - netwerk/build/components.conf | 4 +- netwerk/system/linux/moz.build | 2 +- netwerk/system/linux/nsNetworkLinkService.cpp | 150 ++ netwerk/system/linux/nsNetworkLinkService.h | 45 + .../linux/nsNotifyAddrListener_Linux.cpp | 617 ------- .../system/linux/nsNotifyAddrListener_Linux.h | 104 -- netwerk/system/mac/nsNetworkLinkService.h | 3 - netwerk/system/mac/nsNetworkLinkService.mm | 7 +- netwerk/system/moz.build | 5 +- netwerk/system/netlink/NetlinkService.cpp | 1449 +++++++++++++++++ netwerk/system/netlink/NetlinkService.h | 136 ++ netwerk/system/netlink/moz.build | 12 + netwerk/system/win32/nsNotifyAddrListener.cpp | 14 +- netwerk/system/win32/nsNotifyAddrListener.h | 6 - 18 files changed, 1839 insertions(+), 773 deletions(-) create mode 100644 netwerk/system/linux/nsNetworkLinkService.cpp create mode 100644 netwerk/system/linux/nsNetworkLinkService.h delete mode 100644 netwerk/system/linux/nsNotifyAddrListener_Linux.cpp delete mode 100644 netwerk/system/linux/nsNotifyAddrListener_Linux.h create mode 100644 netwerk/system/netlink/NetlinkService.cpp create mode 100644 netwerk/system/netlink/NetlinkService.h create mode 100644 netwerk/system/netlink/moz.build diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 9931de1439ce..6283eddbd551 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -6076,6 +6076,37 @@ value: 30000 mirror: always +# Allow the network changed event to get sent when a network topology or setup +# change is noticed while running. +- name: network.notify.changed + type: RelaxedAtomicBool + value: true + mirror: always + +# Allow network detection of IPv6 related changes (bug 1245059) +- name: network.notify.IPv6 + type: RelaxedAtomicBool +# ifdef XP_WIN + value: false +# else + value: true +# endif + mirror: always + +# IP addresses that are used by netlink service to check whether default route +# is used for outgoing 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. +- name: network.netlink.route.check.IPv4 + type: String + value: "23.219.91.27" + mirror: never +- name: network.netlink.route.check.IPv6 + type: String + value: "2a02:26f0:40::17db:5b1b" + mirror: never + #--------------------------------------------------------------------------- # Prefs starting with "nglayout." #--------------------------------------------------------------------------- diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 190162c25910..32b2143e796a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1489,17 +1489,6 @@ pref("logging.config.clear_on_startup", true); // prevents necko connecting to ports 1-5 unless the protocol // overrides. -// Allow the network changed event to get sent when a network topology or -// setup change is noticed while running. -pref("network.notify.changed", true); - -// Allow network detection of IPv6 related changes (bug 1245059) -#if defined(XP_WIN) - pref("network.notify.IPv6", false); -#else - pref("network.notify.IPv6", true); -#endif - // Transmit UDP busy-work to the LAN when anticipating low latency // network reads and on wifi to mitigate 802.11 Power Save Polling delays pref("network.tickle-wifi.enabled", false); diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp index 2ca1ea1a423a..4a20055599f0 100644 --- a/netwerk/base/nsIOService.cpp +++ b/netwerk/base/nsIOService.cpp @@ -61,6 +61,7 @@ #include "nsContentSecurityManager.h" #include "nsContentUtils.h" #include "nsExceptionHandler.h" +#include "mozilla/StaticPrefs_network.h" #ifdef MOZ_WIDGET_GTK # include "nsGIOProtocolHandler.h" @@ -83,7 +84,6 @@ using mozilla::dom::ServiceWorkerDescriptor; // but the old names are still used to preserve backward compatibility. #define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count" #define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size" -#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed" #define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled" #define WEBRTC_PREF_PREFIX "media.peerconnection." #define NETWORK_DNS_PREF "network.dns." @@ -202,7 +202,6 @@ nsIOService::nsIOService() mHttpHandlerAlreadyShutingDown(false), mNetworkLinkServiceInitialized(false), mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY), - mNetworkNotifyChanged(true), mTotalRequests(0), mCacheWon(0), mNetWon(0), @@ -217,7 +216,6 @@ static const char* gCallbackPrefs[] = { MANAGE_OFFLINE_STATUS_PREF, NECKO_BUFFER_CACHE_COUNT_PREF, NECKO_BUFFER_CACHE_SIZE_PREF, - NETWORK_NOTIFY_CHANGED_PREF, NETWORK_CAPTIVE_PORTAL_PREF, nullptr, }; @@ -1267,14 +1265,6 @@ void nsIOService::PrefsChanged(const char* pref) { "network segment size is not a power of 2!"); } - if (!pref || strcmp(pref, NETWORK_NOTIFY_CHANGED_PREF) == 0) { - bool allow; - nsresult rv = Preferences::GetBool(NETWORK_NOTIFY_CHANGED_PREF, &allow); - if (NS_SUCCEEDED(rv)) { - mNetworkNotifyChanged = allow; - } - } - if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) { nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF, &gCaptivePortalEnabled); @@ -1346,7 +1336,7 @@ nsIOService::NotifyWakeup() { NS_ASSERTION(observerService, "The observer service should not be null"); - if (observerService && mNetworkNotifyChanged) { + if (observerService && StaticPrefs::network_notify_changed()) { (void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC, (u"" NS_NETWORK_LINK_DATA_CHANGED)); } diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h index 9a4ad2f4ce26..add3cc2c2987 100644 --- a/netwerk/base/nsIOService.h +++ b/netwerk/base/nsIOService.h @@ -216,8 +216,6 @@ class nsIOService final : public nsIIOService, nsTArray mRestrictedPortList; - bool mNetworkNotifyChanged; - static bool sIsDataURIUniqueOpaqueOrigin; static bool sBlockToplevelDataUriNavigations; diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index bfd27e3336bb..1e02460dc340 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -607,8 +607,8 @@ elif toolkit == 'android': } elif buildconfig.substs['OS_ARCH'] == 'Linux': link_service = { - 'type': 'nsNotifyAddrListener', - 'headers': ['/netwerk/system/linux/nsNotifyAddrListener_Linux.h'], + 'type': 'nsNetworkLinkService', + 'headers': ['/netwerk/system/linux/nsNetworkLinkService.h'], 'init_method': 'Init', } diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build index 21fc5e2d20ab..fee7432098f4 100644 --- a/netwerk/system/linux/moz.build +++ b/netwerk/system/linux/moz.build @@ -6,7 +6,7 @@ if CONFIG['OS_ARCH'] == 'Linux': SOURCES += [ - 'nsNotifyAddrListener_Linux.cpp', + 'nsNetworkLinkService.cpp', ] FINAL_LIBRARY = 'xul' diff --git a/netwerk/system/linux/nsNetworkLinkService.cpp b/netwerk/system/linux/nsNetworkLinkService.cpp new file mode 100644 index 000000000000..b19aaf6fb75a --- /dev/null +++ b/netwerk/system/linux/nsNetworkLinkService.cpp @@ -0,0 +1,150 @@ +/* -*- 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::Observe(nsISupports* subject, const char* topic, + const char16_t* data) { + if (!strcmp("xpcom-shutdown-threads", topic)) { + Shutdown(); + } + + return NS_OK; +} + +nsresult nsNetworkLinkService::Init() { + nsCOMPtr 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(rv))); + return rv; + } + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult nsNetworkLinkService::Shutdown() { + // remove xpcom shutdown observer + nsCOMPtr 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 self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsNetworkLinkService::OnNetworkChanged", + [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_CHANGED); })); + } +} + +void nsNetworkLinkService::OnLinkUp() { + RefPtr self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsNetworkLinkService::OnLinkUp", + [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_UP); })); +} + +void nsNetworkLinkService::OnLinkDown() { + RefPtr self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsNetworkLinkService::OnLinkDown", + [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_DOWN); })); +} + +void nsNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; } + +/* Sends the given event. Assumes aEventID never goes out of scope (static + * strings are ideal). + */ +void nsNetworkLinkService::SendEvent(const char* aEventID) { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("SendEvent: %s\n", aEventID)); + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + + if (observerService) { + observerService->NotifyObservers(static_cast(this), + NS_NETWORK_LINK_TOPIC, + NS_ConvertASCIItoUTF16(aEventID).get()); + } +} diff --git a/netwerk/system/linux/nsNetworkLinkService.h b/netwerk/system/linux/nsNetworkLinkService.h new file mode 100644 index 000000000000..a3237582ae3f --- /dev/null +++ b/netwerk/system/linux/nsNetworkLinkService.h @@ -0,0 +1,45 @@ +/* -*- 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 OnLinkUp() override; + void OnLinkDown() override; + void OnLinkStatusKnown() override; + + private: + virtual ~nsNetworkLinkService() = default; + + // Called when xpcom-shutdown-threads is received. + nsresult Shutdown(); + + // Sends the network event. + void SendEvent(const char* aEventID); + + mozilla::Atomic mStatusIsKnown; + + RefPtr mNetlinkSvc; +}; + +#endif /* NSNETWORKLINKSERVICE_LINUX_H_ */ diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp deleted file mode 100644 index 2e81115cdee7..000000000000 --- a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp +++ /dev/null @@ -1,617 +0,0 @@ -/* -*- 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 -#include -#include -#include -#include -#include - -#include "nsThreadUtils.h" -#include "nsIObserverService.h" -#include "nsServiceManagerUtils.h" -#include "nsNotifyAddrListener_Linux.h" -#include "nsString.h" -#include "mozilla/Logging.h" - -#include "mozilla/Base64.h" -#include "mozilla/FileUtils.h" -#include "mozilla/Preferences.h" -#include "mozilla/Services.h" -#include "mozilla/SHA1.h" -#include "mozilla/Sprintf.h" -#include "mozilla/Telemetry.h" -#include "../../base/IPv6Utils.h" - -/* a shorter name that better explains what it does */ -#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x) - -// period during which to absorb subsequent network change events, in -// milliseconds -static const unsigned int kNetworkChangeCoalescingPeriod = 1000; - -using namespace mozilla; - -static LazyLogModule gNotifyAddrLog("nsNotifyAddr"); -#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args) - -#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed" - -NS_IMPL_ISUPPORTS(nsNotifyAddrListener, nsINetworkLinkService, nsIRunnable, - nsIObserver) - -nsNotifyAddrListener::nsNotifyAddrListener() - : mMutex("nsNotifyAddrListener::mMutex"), - mLinkUp(true), // assume true by default - mStatusKnown(false), - mAllowChangedEvent(true), - mCoalescingActive(false) { - mShutdownPipe[0] = -1; - mShutdownPipe[1] = -1; -} - -nsNotifyAddrListener::~nsNotifyAddrListener() { - MOZ_ASSERT(!mThread, "nsNotifyAddrListener thread shutdown failed"); - - if (mShutdownPipe[0] != -1) { - EINTR_RETRY(close(mShutdownPipe[0])); - } - if (mShutdownPipe[1] != -1) { - EINTR_RETRY(close(mShutdownPipe[1])); - } -} - -NS_IMETHODIMP -nsNotifyAddrListener::GetIsLinkUp(bool* aIsUp) { - // XXX This function has not yet been implemented for this platform - *aIsUp = mLinkUp; - return NS_OK; -} - -NS_IMETHODIMP -nsNotifyAddrListener::GetLinkStatusKnown(bool* aIsUp) { - // XXX This function has not yet been implemented for this platform - *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; -} - -// -// Figure out the current IPv4 "network identification" string. -// -// It detects the IP of the default gateway in the routing table, then the MAC -// address of that IP in the ARP table before it hashes that string (to avoid -// information leakage). -// -static bool ipv4NetworkId(SHA1Sum* sha1) { - const char* kProcRoute = "/proc/net/route"; /* IPv4 routes */ - const char* kProcArp = "/proc/net/arp"; - bool found = false; - - FILE* froute = fopen(kProcRoute, "r"); - if (froute) { - char buffer[512]; - uint32_t gw = 0; - char* l = fgets(buffer, sizeof(buffer), froute); - if (l) { - /* skip the title line */ - while (l) { - char interf[32]; - uint32_t dest; - uint32_t gateway; - l = fgets(buffer, sizeof(buffer), froute); - if (l) { - buffer[511] = 0; /* as a precaution */ - int val = sscanf(buffer, "%31s %x %x", interf, &dest, &gateway); - if ((3 == val) && !dest) { - gw = gateway; - break; - } - } - } - } - fclose(froute); - - if (gw) { - /* create a string to search for in the arp table */ - char searchfor[16]; - SprintfLiteral(searchfor, "%d.%d.%d.%d", gw & 0xff, (gw >> 8) & 0xff, - (gw >> 16) & 0xff, gw >> 24); - - FILE* farp = fopen(kProcArp, "r"); - if (farp) { - l = fgets(buffer, sizeof(buffer), farp); - while (l) { - /* skip the title line */ - l = fgets(buffer, sizeof(buffer), farp); - if (l) { - buffer[511] = 0; /* as a precaution */ - int p[4]; - char type[16]; - char flags[16]; - char hw[32]; - if (7 == sscanf(buffer, "%u.%u.%u.%u %15s %15s %31s", &p[0], &p[1], - &p[2], &p[3], type, flags, hw)) { - uint32_t searchip = - p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); - if (gw == searchip) { - LOG(("networkid: MAC %s\n", hw)); - nsAutoCString mac(hw); - sha1->update(mac.get(), mac.Length()); - found = true; - break; - } - } - } - } - fclose(farp); - } /* if (farp) */ - } /* if (gw) */ - } /* if (froute) */ - return found; -} - -// Figure out the current IPv6 "network identification" string. -// -static bool ipv6NetworkId(SHA1Sum* sha1) { - bool found = false; - FILE* ifs = fopen("/proc/net/if_inet6", "r"); - if (ifs) { - char buffer[512]; - char ip6[40]; - int devnum; - int preflen; - int scope; - int flags; - char name[40]; - - char* l = fgets(buffer, sizeof(buffer), ifs); - // 2a001a28120000090000000000000002 02 40 00 80 eth0 - // +------------------------------+ ++ ++ ++ ++ ++ - // | | | | | | - // 1 2 3 4 5 6 - // - // 1. IPv6 address displayed in 32 hexadecimal chars without colons as - // separator - // - // 2. Netlink device number (interface index) in hexadecimal. - // - // 3. Prefix length in hexadecimal number of bits - // - // 4. Scope value (see kernel source include/net/ipv6.h and - // net/ipv6/addrconf.c for more) - // - // 5. Interface flags (see include/linux/rtnetlink.h and net/ipv6/addrconf.c - // for more) - // - // 6. Device name - // - while (l) { - memset(ip6, 0, sizeof(ip6)); - if (6 == sscanf(buffer, "%32[0-9a-f] %02x %02x %02x %02x %31s", ip6, - &devnum, &preflen, &scope, &flags, name)) { - unsigned char id6[16]; - memset(id6, 0, sizeof(id6)); - - for (int i = 0; i < 16; i++) { - char buf[3]; - buf[0] = ip6[i * 2]; - buf[1] = ip6[i * 2 + 1]; - buf[2] = 0; - // convert from hex - id6[i] = (unsigned char)strtol(buf, nullptr, 16); - } - - if (net::utils::ipv6_scope(id6) == IPV6_SCOPE_GLOBAL) { - unsigned char prefix[16]; - memset(prefix, 0, sizeof(prefix)); - uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe}; - int bits = preflen; - for (int i = 0; i < 16; i++) { - uint8_t mask = (bits >= 8) ? 0xff : maskit[bits]; - prefix[i] = id6[i] & mask; - bits -= 8; - if (bits <= 0) { - break; - } - } - // We hash the IPv6 prefix and prefix length in order to - // differentiate between networks with a different prefix length - // For example: 2a00:/16 and 2a00:0/32 - sha1->update(prefix, 16); - sha1->update(&preflen, sizeof(preflen)); - found = true; - LOG(("networkid: found global IPv6 address %s/%d\n", ip6, preflen)); - } - } - l = fgets(buffer, sizeof(buffer), ifs); - } - fclose(ifs); - } - return found; -} - -// Figure out the "network identification". -// -void nsNotifyAddrListener::calculateNetworkId(void) { - MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread"); - SHA1Sum sha1; - 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(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; - } else { - // same id - LOG(("Same network id")); - Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2); - } - } else { - // no id - LOG(("No network id")); - MutexAutoLock lock(mMutex); - mNetworkId.Truncate(); - Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0); - } -} - -// -// Check if there's a network interface available to do networking on. -// -void nsNotifyAddrListener::checkLink(void) { - struct ifaddrs* list; - struct ifaddrs* ifa; - bool link = false; - bool prevLinkUp = mLinkUp; - - if (getifaddrs(&list)) return; - - // Walk through the linked list, maintaining head pointer so we can free - // list later - - for (ifa = list; ifa != nullptr; ifa = ifa->ifa_next) { - int family; - if (ifa->ifa_addr == nullptr) continue; - - family = ifa->ifa_addr->sa_family; - - if ((family == AF_INET || family == AF_INET6) && - (ifa->ifa_flags & IFF_RUNNING) && !(ifa->ifa_flags & IFF_LOOPBACK)) { - // An interface that is UP and not loopback - link = true; - break; - } - } - mLinkUp = link; - freeifaddrs(list); - - if (prevLinkUp != mLinkUp) { - // UP/DOWN status changed, send appropriate UP/DOWN event - SendEvent(mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN); - } -} - -void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket) { - struct nlmsghdr* nlh; - - // The buffer size below, (4095) was chosen partly based on testing and - // partly on existing sample source code using this size. It needs to be - // large enough to hold the netlink messages from the kernel. - char buffer[4095]; - struct rtattr* attr; - int attr_len; - const struct ifaddrmsg* newifam; - - ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0)); - if (rc < 0) { - return; - } - size_t netlink_bytes = rc; - - nlh = reinterpret_cast(buffer); - - bool networkChange = false; - - for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) { - char prefixaddr[INET6_ADDRSTRLEN]; - char localaddr[INET6_ADDRSTRLEN]; - char* addr = nullptr; - prefixaddr[0] = localaddr[0] = '\0'; - - if (NLMSG_DONE == nlh->nlmsg_type) { - break; - } - - LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n")); - newifam = reinterpret_cast(NLMSG_DATA(nlh)); - - if ((newifam->ifa_family != AF_INET) && (newifam->ifa_family != AF_INET6)) { - continue; - } - - attr = IFA_RTA(newifam); - attr_len = IFA_PAYLOAD(nlh); - for (; attr_len && RTA_OK(attr, attr_len); - attr = RTA_NEXT(attr, attr_len)) { - if (attr->rta_type == IFA_ADDRESS) { - if (newifam->ifa_family == AF_INET) { - struct in_addr* in = (struct in_addr*)RTA_DATA(attr); - inet_ntop(AF_INET, in, prefixaddr, INET_ADDRSTRLEN); - } else { - struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr); - inet_ntop(AF_INET6, in, prefixaddr, INET6_ADDRSTRLEN); - } - } else if (attr->rta_type == IFA_LOCAL) { - if (newifam->ifa_family == AF_INET) { - struct in_addr* in = (struct in_addr*)RTA_DATA(attr); - inet_ntop(AF_INET, in, localaddr, INET_ADDRSTRLEN); - } else { - struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr); - inet_ntop(AF_INET6, in, localaddr, INET6_ADDRSTRLEN); - } - } - } - if (localaddr[0]) { - addr = localaddr; - } else if (prefixaddr[0]) { - addr = prefixaddr; - } else { - continue; - } - if (nlh->nlmsg_type == RTM_NEWADDR) { - LOG( - ("nsNotifyAddrListener::OnNetlinkMessage: a new address " - "- %s.", - addr)); - struct ifaddrmsg* ifam; - nsCString addrStr; - addrStr.Assign(addr); - if (auto entry = mAddressInfo.LookupForAdd(addrStr)) { - ifam = entry.Data(); - LOG( - ("nsNotifyAddrListener::OnNetlinkMessage: the address " - "already known.")); - if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) { - LOG( - ("nsNotifyAddrListener::OnNetlinkMessage: but " - "the address info has been changed.")); - networkChange = true; - memcpy(ifam, newifam, sizeof(struct ifaddrmsg)); - } - } else { - networkChange = true; - ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg)); - memcpy(ifam, newifam, sizeof(struct ifaddrmsg)); - entry.OrInsert([ifam]() { return ifam; }); - } - } else { - LOG( - ("nsNotifyAddrListener::OnNetlinkMessage: an address " - "has been deleted - %s.", - addr)); - networkChange = true; - nsCString addrStr; - addrStr.Assign(addr); - mAddressInfo.Remove(addrStr); - } - } - - if (networkChange && mAllowChangedEvent) { - NetworkChanged(); - } - - if (networkChange) { - checkLink(); - } -} - -NS_IMETHODIMP -nsNotifyAddrListener::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)); // clear addr - - addr.nl_family = AF_NETLINK; - addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; - - if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - // failure! - EINTR_RETRY(close(netlinkSocket)); - return NS_ERROR_FAILURE; - } - - // switch the socket into non-blocking - int flags = fcntl(netlinkSocket, F_GETFL, 0); - (void)fcntl(netlinkSocket, F_SETFL, flags | O_NONBLOCK); - - 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; - - calculateNetworkId(); - - nsresult rv = NS_OK; - bool shutdown = false; - int pollWait = -1; - while (!shutdown) { - int rc = EINTR_RETRY(poll(fds, 2, pollWait)); - - 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; - } - if (mCoalescingActive) { - // check if coalescing period should continue - double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds(); - if (period >= kNetworkChangeCoalescingPeriod) { - SendEvent(NS_NETWORK_LINK_DATA_CHANGED); - mCoalescingActive = false; - pollWait = -1; // restore to default - } else { - // wait no longer than to the end of the period - pollWait = static_cast(kNetworkChangeCoalescingPeriod - period); - } - } - } - - EINTR_RETRY(close(netlinkSocket)); - - return rv; -} - -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 observerService = - mozilla::services::GetObserverService(); - if (!observerService) return NS_ERROR_FAILURE; - - nsresult rv = - observerService->AddObserver(this, "xpcom-shutdown-threads", false); - NS_ENSURE_SUCCESS(rv, rv); - - Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF, - true); - - if (-1 == pipe(mShutdownPipe)) { - return NS_ERROR_FAILURE; - } - - rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -nsresult nsNotifyAddrListener::Shutdown(void) { - // remove xpcom shutdown observer - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) - observerService->RemoveObserver(this, "xpcom-shutdown-threads"); - - LOG(("write() to signal thread shutdown\n")); - - // 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 nsNotifyAddrListener holds - // onto the thread and the thread holds onto the nsNotifyAddrListener - // via its mRunnable - mThread = 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; - LOG(("NetworkChanged: coalescing period started\n")); - } - return NS_OK; -} - -/* Sends the given event. Assumes aEventID never goes out of scope (static - * strings are ideal). - */ -nsresult nsNotifyAddrListener::SendEvent(const char* aEventID) { - if (!aEventID) return NS_ERROR_NULL_POINTER; - - LOG(("SendEvent: %s\n", aEventID)); - nsresult rv = NS_OK; - calculateNetworkId(); - nsCOMPtr event = new ChangeEvent(this, aEventID); - if (NS_FAILED(rv = NS_DispatchToMainThread(event))) - NS_WARNING("Failed to dispatch ChangeEvent"); - return rv; -} - -NS_IMETHODIMP -nsNotifyAddrListener::ChangeEvent::Run() { - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) - observerService->NotifyObservers(mService, NS_NETWORK_LINK_TOPIC, - NS_ConvertASCIItoUTF16(mEventID).get()); - return NS_OK; -} diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.h b/netwerk/system/linux/nsNotifyAddrListener_Linux.h deleted file mode 100644 index 5888ecd8a8f7..000000000000 --- a/netwerk/system/linux/nsNotifyAddrListener_Linux.h +++ /dev/null @@ -1,104 +0,0 @@ -/* -*- 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_LINUX_H_ -#define NSNOTIFYADDRLISTENER_LINUX_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "nsINetworkLinkService.h" -#include "nsIRunnable.h" -#include "nsIObserver.h" -#include "nsThreadUtils.h" -#include "nsCOMPtr.h" -#include "mozilla/Atomics.h" -#include "mozilla/Mutex.h" -#include "mozilla/TimeStamp.h" -#include "nsITimer.h" -#include "nsClassHashtable.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); - - private: - class ChangeEvent : public mozilla::Runnable { - public: - NS_DECL_NSIRUNNABLE - ChangeEvent(nsINetworkLinkService* aService, const char* aEventID) - : mozilla::Runnable("nsNotifyAddrListener::ChangeEvent"), - mService(aService), - mEventID(aEventID) {} - - private: - nsCOMPtr mService; - const char* mEventID; - }; - - // Called when xpcom-shutdown-threads is received. - nsresult Shutdown(void); - - // Called when a network change was detected - nsresult NetworkChanged(); - - // Sends the network event. - nsresult SendEvent(const char* aEventID); - - // Figure out the current "network identification" - void calculateNetworkId(void); - - mozilla::Mutex mMutex; - nsCString mNetworkId; - - // Checks if there's a network "link" - void checkLink(void); - - // Deals with incoming NETLINK messages. - void OnNetlinkMessage(int NetlinkSocket); - - nsCOMPtr mThread; - - // The network is up. - bool mLinkUp; - - // The network's up/down status is known. - bool mStatusKnown; - - // A pipe to signal shutdown with. - int mShutdownPipe[2]; - - // Network changed events are enabled - bool mAllowChangedEvent; - - // Flag set while coalescing change events - bool mCoalescingActive; - - // Time stamp for first event during coalescing - mozilla::TimeStamp mChangeTime; - - // Seen Ip addresses. For Ipv6 addresses some time router renews their - // lifetime and we should not detect this as a network link change, so we - // keep info about all seen addresses. - nsClassHashtable mAddressInfo; -}; - -#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */ diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h index fccd65f3a978..9634f9965a04 100644 --- a/netwerk/system/mac/nsNetworkLinkService.h +++ b/netwerk/system/mac/nsNetworkLinkService.h @@ -30,9 +30,6 @@ class nsNetworkLinkService : public nsINetworkLinkService, public nsIObserver { bool mLinkUp; bool mStatusKnown; - // Toggles allowing the sending of network-changed event. - bool mAllowChangedEvent; - SCNetworkReachabilityRef mReachability; CFRunLoopRef mCFRunLoop; CFRunLoopSourceRef mRunLoopSource; diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm index 4a7e7756acb9..50008c49d0c5 100644 --- a/netwerk/system/mac/nsNetworkLinkService.mm +++ b/netwerk/system/mac/nsNetworkLinkService.mm @@ -23,7 +23,7 @@ #include "nsNetCID.h" #include "nsThreadUtils.h" #include "mozilla/Logging.h" -#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_network.h" #include "mozilla/SHA1.h" #include "mozilla/Base64.h" #include "mozilla/Telemetry.h" @@ -72,7 +72,6 @@ NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver) nsNetworkLinkService::nsNetworkLinkService() : mLinkUp(true), mStatusKnown(false), - mAllowChangedEvent(true), mReachability(nullptr), mCFRunLoop(nullptr), mRunLoopSource(nullptr), @@ -407,8 +406,6 @@ nsresult nsNetworkLinkService::Init(void) { rv = observerService->AddObserver(this, "xpcom-shutdown", false); NS_ENSURE_SUCCESS(rv, rv); - Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF, true); - // 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; @@ -553,7 +550,7 @@ void nsNetworkLinkService::SendEvent(bool aNetworkChanged) { const char* event; if (aNetworkChanged) { - if (!mAllowChangedEvent) { + if (!StaticPrefs::network_notify_changed()) { return; } event = NS_NETWORK_LINK_DATA_CHANGED; diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build index 08c41342bdb1..ba56571e1c68 100644 --- a/netwerk/system/moz.build +++ b/netwerk/system/moz.build @@ -14,4 +14,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': DIRS += ['android'] elif CONFIG['OS_ARCH'] == 'Linux': - DIRS += ['linux'] + DIRS += [ + 'linux', + 'netlink' + ] diff --git a/netwerk/system/netlink/NetlinkService.cpp b/netwerk/system/netlink/NetlinkService.cpp new file mode 100644 index 000000000000..82e795d7a45d --- /dev/null +++ b/netwerk/system/netlink/NetlinkService.cpp @@ -0,0 +1,1449 @@ +/* -*- 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 +#include +#include +#include +#include + +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "NetlinkService.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "mozilla/Logging.h" + +#include "mozilla/Base64.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/DebugOnly.h" + +/* a shorter name that better explains what it does */ +#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x) + +namespace mozilla { +namespace 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) + +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() {} + + uint8_t Family() const { return mIfam.ifa_family; } + uint32_t GetIndex() const { return mIfam.ifa_index; } + uint8_t GetPrefixLen() const { return mIfam.ifa_prefixlen; } + const in_common_addr* GetAddrPtr() const { return &mAddr; } + + 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 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; } + const in_common_addr* GetAddrPtr() const { return &mAddr; } + const uint8_t* GetMACPtr() const { return mMAC; } + bool HasMAC() const { return mHasMAC; }; + +#ifdef NL_DEBUG_LOG + 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])); + } + } +#endif + + 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() {} + + bool IsUp() const { + return (mIface.ifi_flags & IFF_RUNNING) && + !(mIface.ifi_flags & IFF_LOOPBACK); + } + + void GetName(nsACString& _retval) const { _retval = mName; } + + uint32_t GetIndex() const { return mIface.ifi_index; } + + 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) {} + + bool IsUnicast() const { return mRtm.rtm_type == RTN_UNICAST; } + 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 ((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; + } + +#ifdef NL_DEBUG_LOG + void GetAsString(nsACString& _retval) const { + nsAutoCString addrStr; + _retval.Assign("table="); + _retval.AppendInt(mRtm.rtm_table); + _retval.Append(" type="); + _retval.AppendInt(mRtm.rtm_type); + 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); + } + } +#endif + + 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; + } + } + + memcpy(&mRtm, (rtmsg*)NLMSG_DATA(aNlh), sizeof(mRtm)); + return true; + } + + private: + bool mHasGWAddr : 1; + bool mHasPrefSrcAddr : 1; + bool mHasDstAddr : 1; + bool mHasOif : 1; + + in_common_addr mGWAddr; + in_common_addr mDstAddr; + in_common_addr mPrefSrcAddr; + + uint32_t mOif; + 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; +}; + +NS_IMPL_ISUPPORTS(NetlinkService, nsIRunnable) + +NetlinkService::NetlinkService() + : mMutex("NetlinkService::mMutex"), + mInitialScanFinished(false), + mDoRouteCheckIPv4(false), + mDoRouteCheckIPv6(false), + mMsgId(1), + mLinkUp(true), + mRecalculateNetworkId(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(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 */ + MOZ_ASSERT( + isResponse); // Could broadcasted message be reply to NLM_F_DUMP? + if (isResponse) { + RemovePendingMsg(); + } + break; + case 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")); + nsAutoPtr link(new NetlinkLink()); + if (!link->Init(aNlh)) { + return; + } + + uint32_t linkIndex = link->GetIndex(); + nsAutoCString linkName; + link->GetName(linkName); + if (aNlh->nlmsg_type == RTM_NEWLINK) { + LOG(("Adding new link [index=%u, name=%s]", linkIndex, linkName.get())); + mLinks.Put(linkIndex, link.forget()); + } else { + LOG(("Removing link [index=%u, name=%s]", linkIndex, linkName.get())); + mLinks.Remove(linkIndex); + } + + CheckLinks(); +} + +void NetlinkService::CheckLinks() { + if (!mInitialScanFinished) { + // Wait until we get all links via netlink + return; + } + + bool newLinkUp = false; + for (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data()->IsUp()) { + newLinkUp = true; + break; + } + } + + if (mLinkUp != newLinkUp) { + RefPtr listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + mLinkUp = newLinkUp; + } + if (mLinkUp) { + if (listener) { + listener->OnLinkUp(); + } + } else { + if (listener) { + listener->OnLinkDown(); + } + } + } +} + +void NetlinkService::OnAddrMessage(struct nlmsghdr* aNlh) { + LOG(("NetlinkService::OnAddrMessage")); + nsAutoPtr address(new NetlinkAddress()); + if (!address->Init(aNlh)) { + return; + } + + uint32_t ifIdx = address->GetIndex(); + + nsAutoCString addrStr; + GetAddrStr(address->GetAddrPtr(), address->Family(), addrStr); + + // 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. 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 < mAddresses.Length(); ++i) { + if (mAddresses[i]->Equals(address)) { + LOG(("Removing address [ifidx=%u, addr=%s/%u]", mAddresses[i]->GetIndex(), + addrStr.get(), mAddresses[i]->GetPrefixLen())); + mAddresses.RemoveElementAt(i); + break; + } + } + + if (aNlh->nlmsg_type == RTM_NEWADDR) { + LOG(("Adding address [ifidx=%u, addr=%s/%u]", address->GetIndex(), + addrStr.get(), address->GetPrefixLen())); + mAddresses.AppendElement(address.forget()); + } + + NetlinkLink* link; + if (mLinks.Get(ifIdx, &link)) { + if (link->IsUp()) { + // Address changed on a link that is up. This might change network ID. + TriggerNetworkIDCalculation(); + } + } +} + +void NetlinkService::OnRouteMessage(struct nlmsghdr* aNlh) { + LOG(("NetlinkService::OnRouteMessage")); + nsAutoPtr route(new NetlinkRoute()); + if (!route->Init(aNlh)) { + return; + } + +#ifdef NL_DEBUG_LOG + nsAutoCString routeDbgStr; + route->GetAsString(routeDbgStr); +#endif + + if (!route->IsUnicast()) { + // Use only unicast routes +#ifdef NL_DEBUG_LOG + LOG(("Ignoring non-unicast route: %s", routeDbgStr.get())); +#else + LOG(("Ignoring non-unicast route")); +#endif + return; + } + + // Adding/removing any unicast route might change network ID + TriggerNetworkIDCalculation(); + + if (!route->IsDefault()) { + // Store only default routes +#ifdef NL_DEBUG_LOG + LOG(("Not storing non-unicast route: %s", routeDbgStr.get())); +#else + LOG(("Not storing non-unicast route")); +#endif + return; + } + + nsTArray >* routesPtr; + if (route->Family() == AF_INET) { + routesPtr = &mIPv4Routes; + } else { + routesPtr = &mIPv6Routes; + } + + for (uint32_t i = 0; i < (*routesPtr).Length(); ++i) { + if ((*routesPtr)[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. +#ifdef NL_DEBUG_LOG + LOG(("Removing default route: %s", routeDbgStr.get())); +#else + LOG(("Removing default route")); +#endif + (*routesPtr).RemoveElementAt(i); + break; + } + } + + if (aNlh->nlmsg_type == RTM_NEWROUTE) { +#ifdef NL_DEBUG_LOG + LOG(("Adding default route: %s", routeDbgStr.get())); +#else + LOG(("Adding default route")); +#endif + (*routesPtr).AppendElement(route.forget()); + } +} + +void NetlinkService::OnNeighborMessage(struct nlmsghdr* aNlh) { + LOG(("NetlinkService::OnNeighborMessage")); + nsAutoPtr neigh(new NetlinkNeighbor()); + if (!neigh->Init(aNlh)) { + return; + } + + nsAutoCString addrStr; + GetAddrStr(neigh->GetAddrPtr(), neigh->Family(), addrStr); + + nsTArray >* routesPtr; + nsAutoPtr* routeCheckResultPtr; + if (neigh->Family() == AF_INET) { + routesPtr = &mIPv4Routes; + routeCheckResultPtr = &mIPv4RouteCheckResult; + } else { + routesPtr = &mIPv6Routes; + routeCheckResultPtr = &mIPv6RouteCheckResult; + } + + if (aNlh->nlmsg_type == RTM_NEWNEIGH) { + if (!mRecalculateNetworkId && neigh->HasMAC()) { + NetlinkNeighbor* oldNeigh = nullptr; + mNeighbors.Get(addrStr, &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 < (*routesPtr).Length(); ++i) { + if ((*routesPtr)[i]->GatewayEquals(neigh)) { + TriggerNetworkIDCalculation(); + break; + } + } + if (!mRecalculateNetworkId && (*routeCheckResultPtr) && + (*routeCheckResultPtr)->GatewayEquals(neigh)) { + TriggerNetworkIDCalculation(); + } + } + } + +#ifdef NL_DEBUG_LOG + nsAutoCString neighDbgStr; + neigh->GetAsString(neighDbgStr); + LOG(("Adding neighbor: %s", neighDbgStr.get())); +#else + LOG(("Adding neighbor %s", addrStr.get())); +#endif + mNeighbors.Put(addrStr, neigh.forget()); + } else { +#ifdef NL_DEBUG_LOG + LOG(("Removing neighbor %s", addrStr.get())); +#endif + mNeighbors.Remove(addrStr); + } +} + +void NetlinkService::OnRouteCheckResult(struct nlmsghdr* aNlh) { + LOG(("NetlinkService::OnRouteCheckResult")); + nsAutoPtr route; + + if (aNlh) { + route = new NetlinkRoute(); + if (!route->Init(aNlh)) { + route = nullptr; + } else if (!route->IsUnicast()) { +#ifdef NL_DEBUG_LOG + nsAutoCString routeDbgStr; + route->GetAsString(routeDbgStr); + LOG(("Ignoring non-unicast route: %s", routeDbgStr.get())); +#else + LOG(("Ignoring non-unicast route")); +#endif + route = nullptr; + } + } + + nsAutoPtr* routeCheckResultPtr; + if (mOutgoingMessages[0]->Family() == AF_INET) { + routeCheckResultPtr = &mIPv4RouteCheckResult; + } else { + routeCheckResultPtr = &mIPv6RouteCheckResult; + } + + if (route) { +#ifdef NL_DEBUG_LOG + nsAutoCString routeDbgStr; + route->GetAsString(routeDbgStr); + LOG(("Storing route: %s", routeDbgStr.get())); +#else + LOG(("Storing result for the check")); +#endif + } else { + LOG(("Clearing result for the checkd")); + } + + (*routeCheckResultPtr) = route.forget(); +} + +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() { + MOZ_ASSERT(mOutgoingMessages[0]->IsPending()); + + DebugOnly 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; + + CheckLinks(); + TriggerNetworkIDCalculation(); + + // Link status should be known by now. + RefPtr 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; + + nsAutoCString routecheckIP; + + rv = + Preferences::GetCString("network.netlink.route.check.IPv4", routecheckIP); + if (NS_SUCCEEDED(rv)) { + if (inet_pton(AF_INET, routecheckIP.get(), &mRouteCheckIPv4) == 1) { + mDoRouteCheckIPv4 = true; + } + } + + rv = + Preferences::GetCString("network.netlink.route.check.IPv6", routecheckIP); + if (NS_SUCCEEDED(rv)) { + if (inet_pton(AF_INET6, routecheckIP.get(), &mRouteCheckIPv6) == 1) { + mDoRouteCheckIPv6 = true; + } + } + + if (pipe(mShutdownPipe) == -1) { + 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, do route check + if (!mDoRouteCheckIPv4 && !mDoRouteCheckIPv6) { + // If route checking is disabled for whatever reason, calculate ID now + CalculateNetworkID(); + return -1; + } + + // Otherwise send route check messages and calculate network ID after the + // response is received + if (mDoRouteCheckIPv4) { + EnqueueRtMsg(AF_INET, &mRouteCheckIPv4); + } + if (mDoRouteCheckIPv6) { + EnqueueRtMsg(AF_INET6, &mRouteCheckIPv6); + } + + // Return 0 to make sure we start sending enqueued messages immediately + return 0; + } + + return static_cast(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); + } +}; + +bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) { + LOG(("NetlinkService::CalculateIDForFamily [family=%s]", + aFamily == AF_INET ? "AF_INET" : "AF_INET6")); + + bool retval = false; + + nsTArray >* routesPtr; + nsAutoPtr* routeCheckResultPtr; + if (aFamily == AF_INET) { + routesPtr = &mIPv4Routes; + routeCheckResultPtr = &mIPv4RouteCheckResult; + } else { + routesPtr = &mIPv6Routes; + routeCheckResultPtr = &mIPv6RouteCheckResult; + } + + // All known GW neighbors + nsTArray gwNeighbors; + + // Check all default routes and try to get MAC of the gateway + for (uint32_t i = 0; i < (*routesPtr).Length(); ++i) { +#ifdef NL_DEBUG_LOG + nsAutoCString routeDbgStr; + (*routesPtr)[i]->GetAsString(routeDbgStr); + LOG(("Checking default route: %s", routeDbgStr.get())); +#endif + nsAutoCString addrStr; + const in_common_addr* addrPtr = (*routesPtr)[i]->GetGWAddrPtr(); + if (!addrPtr) { + LOG(("There is no GW address in default route.")); + continue; + } + + GetAddrStr(addrPtr, (*routesPtr)[i]->Family(), addrStr); + NetlinkNeighbor* neigh = nullptr; + if (!mNeighbors.Get(addrStr, &neigh)) { + LOG(("Neighbor %s not found in hashtable.", addrStr.get())); + continue; + } + + if (!neigh->HasMAC()) { + // We don't know MAC address + LOG(("We have no MAC for neighbor %s.", addrStr.get())); + continue; + } + + if (gwNeighbors.IndexOf(neigh) != gwNeighbors.NoIndex) { + // avoid host duplicities + LOG(("Neighbor %s is already selected for hashing.", addrStr.get())); + continue; + } + + LOG(("Neighbor %s will be used for network ID.", addrStr.get())); + gwNeighbors.AppendElement(neigh); + } + + // 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) { +#ifdef NL_DEBUG_LOG + nsAutoCString neighDbgStr; + gwNeighbors[i]->GetAsString(neighDbgStr); + LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get())); +#endif + aSHA1->update(gwNeighbors[i]->GetMACPtr(), ETH_ALEN); + retval = true; + } + + if (!*routeCheckResultPtr) { + LOG(("There is no route check result.")); + return retval; + } + + // Check whether we know next hop for mRouteCheckIPv4/6 host + const in_common_addr* addrPtr = (*routeCheckResultPtr)->GetGWAddrPtr(); + if (addrPtr) { + // 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 addrStr; + GetAddrStr(addrPtr, (*routeCheckResultPtr)->Family(), addrStr); + LOG(("Next hop for the checked host is %s.", addrStr.get())); + + NetlinkNeighbor* neigh = nullptr; + if (!mNeighbors.Get(addrStr, &neigh)) { + LOG(("Neighbor %s not found in hashtable.", addrStr.get())); + return retval; + } + + if (!neigh->HasMAC()) { + LOG(("We have no MAC for neighbor %s.", addrStr.get())); + return retval; + } + +#ifdef NL_DEBUG_LOG + nsAutoCString neighDbgStr; + neigh->GetAsString(neighDbgStr); + LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get())); +#else + LOG(("Hashing MAC address of neighbor %s", addrStr.get())); +#endif + aSHA1->update(neigh->GetMACPtr(), ETH_ALEN); + retval = true; + } else if ((*routeCheckResultPtr)->HasOif()) { + // The traffic is routed directly via an interface. It's likely VPN tun + // device. Probably the best we can do is to hash name of the interface + // (e.g. "tun1") and network address. Using host address would cause that + // network ID would be different every time the VPN give us a different IP + // address. + nsAutoCString linkName; + NetlinkLink* link = nullptr; + uint32_t ifIdx = (*routeCheckResultPtr)->Oif(); + if (!mLinks.Get(ifIdx, &link)) { + LOG(("Cannot find link with index %u ??", ifIdx)); + return retval; + } + link->GetName(linkName); + + bool hasSrcAddr = (*routeCheckResultPtr)->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 < mAddresses.Length(); ++i) { + if (mAddresses[i]->GetIndex() != ifIdx) { + continue; + } + if (!hasSrcAddr) { + // there is no preferred src, match just the family + if (mAddresses[i]->Family() != aFamily) { + continue; + } + } else if (!(*routeCheckResultPtr)->PrefSrcAddrEquals(mAddresses[i])) { + continue; + } + + if (!linkAddress || + linkAddress->GetPrefixLen() > mAddresses[i]->GetPrefixLen()) { + // We have no address yet or this one has smaller prefix length, use it. + linkAddress = mAddresses[i]; + } + } + + if (!linkAddress) { + // There is no address in our array? + nsAutoCString dbgStr; +#ifdef NL_DEBUG_LOG + (*routeCheckResultPtr)->GetAsString(dbgStr); + LOG(("No address found for preferred source address in route: %s", + dbgStr.get())); +#else + GetAddrStr((*routeCheckResultPtr)->GetPrefSrcAddrPtr(), aFamily, dbgStr); + LOG(("No address found for preferred source address %s", dbgStr.get())); +#endif + 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", linkName.get(), + addrStr.get(), linkAddress->GetPrefixLen())); + aSHA1->update(linkName.BeginReading(), linkName.Length()); + aSHA1->update(&prefix, prefixSize); + aSHA1->update(&bits, sizeof(bits)); + retval = true; + } else { + // This is strange, there is neither next hop nor output interface. +#ifdef NL_DEBUG_LOG + nsAutoCString routeDbgStr; + (*routeCheckResultPtr)->GetAsString(routeDbgStr); + LOG(("Neither GW address nor output interface found in route: %s", + routeDbgStr.get())); +#else + LOG(("Neither GW address nor output interface found in route")); +#endif + } + + return retval; +} + +// Figure out the "network identification". +void NetlinkService::CalculateNetworkID() { + MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread"); + MOZ_ASSERT(mRecalculateNetworkId); + + mRecalculateNetworkId = false; + + SHA1Sum sha1; + + 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(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; + + if (idChanged && !initialIDCalculation) { + RefPtr listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + if (listener) { + listener->OnNetworkChanged(); + } + } + + initialIDCalculation = false; +} + +void NetlinkService::GetNetworkID(nsACString& aNetworkID) { + MutexAutoLock lock(mMutex); + aNetworkID = mNetworkId; +} + +void NetlinkService::GetIsLinkUp(bool* aIsUp) { + MutexAutoLock lock(mMutex); + *aIsUp = mLinkUp; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/system/netlink/NetlinkService.h b/netwerk/system/netlink/NetlinkService.h new file mode 100644 index 000000000000..1deb7d25dc63 --- /dev/null +++ b/netwerk/system/netlink/NetlinkService.h @@ -0,0 +1,136 @@ +/* -*- 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 +#include + +#include "nsINetworkLinkService.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "nsITimer.h" +#include "nsClassHashtable.h" +#include "mozilla/SHA1.h" + +namespace mozilla { +namespace net { + +#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG) +# define NL_DEBUG_LOG +#endif + +class NetlinkAddress; +class NetlinkNeighbor; +class NetlinkLink; +class NetlinkRoute; +class NetlinkMsg; + +class NetlinkServiceListener : public nsISupports { + public: + virtual void OnNetworkChanged() = 0; + virtual void OnLinkUp() = 0; + virtual void OnLinkDown() = 0; + virtual void OnLinkStatusKnown() = 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); + + 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 CheckLinks(); + + void TriggerNetworkIDCalculation(); + int GetPollWait(); + bool CalculateIDForFamily(uint8_t aFamily, mozilla::SHA1Sum* aSHA1); + void CalculateNetworkID(); + + nsCOMPtr mThread; + + bool mInitialScanFinished; + + // A pipe to signal shutdown with. + int mShutdownPipe[2]; + + // Is true if preference network.netlink.route.check.IPv4 was successfully + // parsed and stored to mRouteCheckIPv4 + bool mDoRouteCheckIPv4; + struct in_addr mRouteCheckIPv4; + + // Is true if preference network.netlink.route.check.IPv6 was successfully + // parsed and stored to mRouteCheckIPv6 + bool mDoRouteCheckIPv6; + 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; + + // Time stamp of setting mRecalculateNetworkId to true + mozilla::TimeStamp mTriggerTime; + + nsCString mNetworkId; + + // All IPv4 and IPv6 addresses received via netlink + nsTArray > mAddresses; + // All neighbors, key is an address + nsClassHashtable mNeighbors; + // All interfaces keyed by interface index + nsClassHashtable mLinks; + // Default IPv4 routes + nsTArray > mIPv4Routes; + // Default IPv6 routes + nsTArray > mIPv6Routes; + + // Route for mRouteCheckIPv4 address + nsAutoPtr mIPv4RouteCheckResult; + // Route for mRouteCheckIPv6 address + nsAutoPtr mIPv6RouteCheckResult; + + nsTArray > mOutgoingMessages; + + RefPtr 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 000000000000..8ee3afd526bd --- /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/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp index 4df3d32a50a5..45fda8bf33f4 100644 --- a/netwerk/system/win32/nsNotifyAddrListener.cpp +++ b/netwerk/system/win32/nsNotifyAddrListener.cpp @@ -34,7 +34,7 @@ #include "nsAutoPtr.h" #include "mozilla/Services.h" #include "nsCRT.h" -#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_network.h" #include "mozilla/SHA1.h" #include "mozilla/Base64.h" #include "mozilla/ScopeExit.h" @@ -115,8 +115,6 @@ nsNotifyAddrListener::nsNotifyAddrListener() mCheckEvent(nullptr), mShutdown(false), mIPInterfaceChecksum(0), - mAllowChangedEvent(true), - mIPv6Changes(false), mCoalescingActive(false) { InitIphlpapi(); } @@ -276,7 +274,8 @@ nsNotifyAddrListener::Run() { DWORD waitTime = INFINITE; - if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || !mIPv6Changes) { + if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || + !StaticPrefs::network_notify_IPv6()) { // For Windows versions which are older than Vista which lack // NotifyIpInterfaceChange. Note this means no IPv6 support. HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); @@ -350,10 +349,6 @@ nsresult nsNotifyAddrListener::Init(void) { observerService->AddObserver(this, "xpcom-shutdown-threads", false); NS_ENSURE_SUCCESS(rv, rv); - Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF, - true); - Preferences::AddBoolVarCache(&mIPv6Changes, NETWORK_NOTIFY_IPV6_PREF, false); - mCheckEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY); @@ -649,7 +644,8 @@ void nsNotifyAddrListener::CheckLinkStatus(void) { // Network is online. Topology has changed. Always send CHANGED // before UP - if allowed to and having cooled down. - if (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) { + if (StaticPrefs::network_notify_changed() && + (since.ToMilliseconds() > 2000)) { NetworkChanged(); } } diff --git a/netwerk/system/win32/nsNotifyAddrListener.h b/netwerk/system/win32/nsNotifyAddrListener.h index 62f3445d2801..a4a07ddb1e25 100644 --- a/netwerk/system/win32/nsNotifyAddrListener.h +++ b/netwerk/system/win32/nsNotifyAddrListener.h @@ -88,12 +88,6 @@ class nsNotifyAddrListener : public nsINetworkLinkService, // start time of the checking mozilla::TimeStamp mStartTime; - // Network changed events are enabled - bool mAllowChangedEvent; - - // Check for IPv6 network changes - bool mIPv6Changes; - // Flag set while coalescing change events bool mCoalescingActive;