Bug 1567616 - network id based on default gateway is wrong when VPN overrides default gateway by more specific routes r=dragana
☠☠ backed out by 4f44ce28f9ea ☠ ☠
authorMichal Novotny <michal.novotny@gmail.com>
Mon, 26 Aug 2019 20:03:33 +0000
changeset 550450 22469090e93e0eaefe2018462f8d06dded92ba45
parent 550449 06f92dd77fed7349973bdc55ce280813238068c1
child 550451 8e22b66d70f9c11f4ac03ff23805dec47e811cf4
push id11858
push userrmaries@mozilla.com
push dateThu, 29 Aug 2019 15:29:30 +0000
treeherdermozilla-beta@e9268d2f3233 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs1567616
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
modules/libpref/init/StaticPrefList.yaml
modules/libpref/init/all.js
netwerk/base/nsIOService.cpp
netwerk/base/nsIOService.h
netwerk/build/components.conf
netwerk/system/linux/moz.build
netwerk/system/linux/nsNetworkLinkService.cpp
netwerk/system/linux/nsNetworkLinkService.h
netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
netwerk/system/linux/nsNotifyAddrListener_Linux.h
netwerk/system/mac/nsNetworkLinkService.h
netwerk/system/mac/nsNetworkLinkService.mm
netwerk/system/moz.build
netwerk/system/netlink/NetlinkService.cpp
netwerk/system/netlink/NetlinkService.h
netwerk/system/netlink/moz.build
netwerk/system/win32/nsNotifyAddrListener.cpp
netwerk/system/win32/nsNotifyAddrListener.h
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -6071,16 +6071,47 @@
   mirror: always
 
 # Single TRR request timeout, in milliseconds for mode 3
 - name: network.trr.request_timeout_mode_trronly_ms
   type: uint32_t
   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."
 #---------------------------------------------------------------------------
 
 # Enable/disable display list invalidation logging --- useful for debugging.
 - name: nglayout.debug.invalidation
   type: bool
   value: false
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1484,27 +1484,16 @@ pref("logging.config.clear_on_startup", 
 // If there is ever a security firedrill that requires
 // us to block certian ports global, this is the pref
 // to use.  Is is a comma delimited list of port numbers
 // for example:
 //   pref("network.security.ports.banned", "1,2,3,4,5");
 // 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);
 pref("network.tickle-wifi.duration", 400);
 pref("network.tickle-wifi.delay", 16);
 
 // Turn off interprocess security checks. Needed to run xpcshell tests.
 pref("network.disable.ipc.security", true);
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -56,16 +56,17 @@
 #include "mozilla/net/NetworkConnectivityService.h"
 #include "mozilla/net/SocketProcessHost.h"
 #include "mozilla/net/SocketProcessParent.h"
 #include "mozilla/net/SSLTokensCache.h"
 #include "mozilla/Unused.h"
 #include "nsContentSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsExceptionHandler.h"
+#include "mozilla/StaticPrefs_network.h"
 
 #ifdef MOZ_WIDGET_GTK
 #  include "nsGIOProtocolHandler.h"
 #endif
 
 namespace mozilla {
 namespace net {
 
@@ -78,17 +79,16 @@ using mozilla::dom::ServiceWorkerDescrip
 #define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
 #define OFFLINE_MIRRORS_CONNECTIVITY "network.offline-mirrors-connectivity"
 
 // Nb: these have been misnomers since bug 715770 removed the buffer cache.
 // "network.segment.count" and "network.segment.size" would be better names,
 // 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."
 
 #define MAX_RECURSION_COUNT 50
 
 nsIOService* gIOService;
 static bool gHasWarnedUploadChannel2;
@@ -197,32 +197,30 @@ nsIOService::nsIOService()
       mOfflineMirrorsConnectivity(true),
       mSettingOffline(false),
       mSetOfflineValue(false),
       mSocketProcessLaunchComplete(false),
       mShutdown(false),
       mHttpHandlerAlreadyShutingDown(false),
       mNetworkLinkServiceInitialized(false),
       mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY),
-      mNetworkNotifyChanged(true),
       mTotalRequests(0),
       mCacheWon(0),
       mNetWon(0),
       mLastOfflineStateChange(PR_IntervalNow()),
       mLastConnectivityChange(PR_IntervalNow()),
       mLastNetworkLinkChange(PR_IntervalNow()),
       mNetTearingDownStarted(0),
       mSocketProcess(nullptr) {}
 
 static const char* gCallbackPrefs[] = {
     PORT_PREF_PREFIX,
     MANAGE_OFFLINE_STATUS_PREF,
     NECKO_BUFFER_CACHE_COUNT_PREF,
     NECKO_BUFFER_CACHE_SIZE_PREF,
-    NETWORK_NOTIFY_CHANGED_PREF,
     NETWORK_CAPTIVE_PORTAL_PREF,
     nullptr,
 };
 
 static const char* gCallbackPrefsForSocketProcess[] = {
     WEBRTC_PREF_PREFIX,
     NETWORK_DNS_PREF,
     nullptr,
@@ -1262,24 +1260,16 @@ void nsIOService::PrefsChanged(const cha
        * is pretty crazy.  if you remove this, consider adding some
        * integer rollover test.
        */
       if (size > 0 && size < 1024 * 1024) gDefaultSegmentSize = size;
     NS_WARNING_ASSERTION(!(size & (size - 1)),
                          "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);
     if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
       if (gCaptivePortalEnabled) {
         static_cast<CaptivePortalService*>(mCaptivePortalService.get())
             ->Start();
       } else {
@@ -1341,17 +1331,17 @@ class nsWakeupNotifier : public Runnable
 };
 
 NS_IMETHODIMP
 nsIOService::NotifyWakeup() {
   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
 
   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));
   }
 
   RecheckCaptivePortal();
 
   return NS_OK;
 }
--- a/netwerk/base/nsIOService.h
+++ b/netwerk/base/nsIOService.h
@@ -211,18 +211,16 @@ class nsIOService final : public nsIIOSe
   // Cached protocol handlers, only accessed on the main thread
   nsWeakPtr mWeakHandler[NS_N(gScheme)];
 
   // cached categories
   nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
 
   nsTArray<int32_t> mRestrictedPortList;
 
-  bool mNetworkNotifyChanged;
-
   static bool sIsDataURIUniqueOpaqueOrigin;
   static bool sBlockToplevelDataUriNavigations;
 
   uint32_t mTotalRequests;
   uint32_t mCacheWon;
   uint32_t mNetWon;
 
   // These timestamps are needed for collecting telemetry on PR_Connect,
--- a/netwerk/build/components.conf
+++ b/netwerk/build/components.conf
@@ -602,18 +602,18 @@ elif toolkit == 'cocoa':
     }
 elif toolkit == 'android':
     link_service = {
         'type': 'nsAndroidNetworkLinkService',
         'headers': ['/netwerk/system/android/nsAndroidNetworkLinkService.h'],
     }
 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',
     }
 
 if link_service:
     Classes += [
         dict({
             'cid': '{75a500a2-0030-40f7-86f8-63f225b940ae}',
             'contract_ids': ['@mozilla.org/network/network-link-service;1'],
--- a/netwerk/system/linux/moz.build
+++ b/netwerk/system/linux/moz.build
@@ -1,12 +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 += [
-        'nsNotifyAddrListener_Linux.cpp',
+        'nsNetworkLinkService.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
rename from netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
rename to netwerk/system/linux/nsNetworkLinkService.cpp
--- a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
+++ b/netwerk/system/linux/nsNetworkLinkService.cpp
@@ -1,617 +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 <stdarg.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <errno.h>
-#include <ifaddrs.h>
-#include <net/if.h>
-
-#include "nsThreadUtils.h"
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
-#include "nsNotifyAddrListener_Linux.h"
+#include "nsNetworkLinkService.h"
 #include "nsString.h"
 #include "mozilla/Logging.h"
 
-#include "mozilla/Base64.h"
-#include "mozilla/FileUtils.h"
-#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.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");
+static LazyLogModule gNotifyAddrLog("nsNetworkLinkService");
 #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)
+NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, 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]));
-  }
-}
+nsNetworkLinkService::nsNetworkLinkService() : mStatusIsKnown(false) {}
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetIsLinkUp(bool* aIsUp) {
-  // XXX This function has not yet been implemented for this platform
-  *aIsUp = mLinkUp;
+nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+  if (!mNetlinkSvc) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mNetlinkSvc->GetIsLinkUp(aIsUp);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetLinkStatusKnown(bool* aIsUp) {
-  // XXX This function has not yet been implemented for this platform
-  *aIsUp = mStatusKnown;
+nsNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+  *aIsKnown = mStatusIsKnown;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetLinkType(uint32_t* aLinkType) {
+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
-nsNotifyAddrListener::GetNetworkID(nsACString& aNetworkID) {
-  MutexAutoLock lock(mMutex);
-  aNetworkID = mNetworkId;
+nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+  if (!mNetlinkSvc) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mNetlinkSvc->GetNetworkID(aNetworkID);
   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<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;
-    } 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<struct nlmsghdr*>(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<struct ifaddrmsg*>(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<int>(kNetworkChangeCoalescingPeriod - period);
-      }
-    }
-  }
-
-  EINTR_RETRY(close(netlinkSocket));
-
-  return rv;
-}
-
-NS_IMETHODIMP
-nsNotifyAddrListener::Observe(nsISupports* subject, const char* topic,
+nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
                               const char16_t* data) {
   if (!strcmp("xpcom-shutdown-threads", topic)) {
     Shutdown();
   }
 
   return NS_OK;
 }
 
-nsresult nsNotifyAddrListener::Init(void) {
+nsresult nsNetworkLinkService::Init() {
   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);
-
-  Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF,
-                               true);
-
-  if (-1 == pipe(mShutdownPipe)) {
+  if (!observerService) {
     return NS_ERROR_FAILURE;
   }
 
-  rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
+  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 nsNotifyAddrListener::Shutdown(void) {
+nsresult nsNetworkLinkService::Shutdown() {
   // remove xpcom shutdown observer
   nsCOMPtr<nsIObserverService> 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;
-}
+  if (mNetlinkSvc) {
+    mNetlinkSvc->Shutdown();
+    mNetlinkSvc = nullptr;
+  }
 
-/*
- * 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
+void nsNetworkLinkService::OnNetworkChanged() {
+  if (StaticPrefs::network_notify_changed()) {
+    RefPtr<nsNetworkLinkService> self = this;
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "nsNetworkLinkService::OnNetworkChanged",
+        [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_CHANGED); }));
+  }
+}
+
+void nsNetworkLinkService::OnLinkUp() {
+  RefPtr<nsNetworkLinkService> self = this;
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "nsNetworkLinkService::OnLinkUp",
+      [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_UP); }));
+}
+
+void nsNetworkLinkService::OnLinkDown() {
+  RefPtr<nsNetworkLinkService> 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).
  */
-nsresult nsNotifyAddrListener::SendEvent(const char* aEventID) {
-  if (!aEventID) return NS_ERROR_NULL_POINTER;
+void nsNetworkLinkService::SendEvent(const char* aEventID) {
+  MOZ_ASSERT(NS_IsMainThread());
 
   LOG(("SendEvent: %s\n", aEventID));
-  nsresult rv = NS_OK;
-  calculateNetworkId();
-  nsCOMPtr<nsIRunnable> 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<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
-  if (observerService)
-    observerService->NotifyObservers(mService, NS_NETWORK_LINK_TOPIC,
-                                     NS_ConvertASCIItoUTF16(mEventID).get());
-  return NS_OK;
+
+  if (observerService) {
+    observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
+                                     NS_NETWORK_LINK_TOPIC,
+                                     NS_ConvertASCIItoUTF16(aEventID).get());
+  }
 }
rename from netwerk/system/linux/nsNotifyAddrListener_Linux.h
rename to netwerk/system/linux/nsNetworkLinkService.h
--- a/netwerk/system/linux/nsNotifyAddrListener_Linux.h
+++ b/netwerk/system/linux/nsNetworkLinkService.h
@@ -1,104 +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 NSNOTIFYADDRLISTENER_LINUX_H_
-#define NSNOTIFYADDRLISTENER_LINUX_H_
-
-#include <sys/socket.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <arpa/inet.h>
-#include <unistd.h>
+#ifndef NSNETWORKLINKSERVICE_LINUX_H_
+#define NSNETWORKLINKSERVICE_LINUX_H_
 
 #include "nsINetworkLinkService.h"
-#include "nsIRunnable.h"
 #include "nsIObserver.h"
-#include "nsThreadUtils.h"
-#include "nsCOMPtr.h"
+#include "../netlink/NetlinkService.h"
+#include "mozilla/RefPtr.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();
-
+class nsNetworkLinkService : public nsINetworkLinkService,
+                             public nsIObserver,
+                             public mozilla::net::NetlinkServiceListener {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSINETWORKLINKSERVICE
-  NS_DECL_NSIRUNNABLE
   NS_DECL_NSIOBSERVER
 
-  nsNotifyAddrListener();
-  nsresult Init(void);
+  nsNetworkLinkService();
+  nsresult Init();
+
+  void OnNetworkChanged() override;
+  void OnLinkUp() override;
+  void OnLinkDown() override;
+  void OnLinkStatusKnown() override;
 
  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<nsINetworkLinkService> mService;
-    const char* mEventID;
-  };
+  virtual ~nsNetworkLinkService() = default;
 
   // Called when xpcom-shutdown-threads is received.
-  nsresult Shutdown(void);
-
-  // Called when a network change was detected
-  nsresult NetworkChanged();
+  nsresult Shutdown();
 
   // 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<nsIThread> mThread;
-
-  // The network is up.
-  bool mLinkUp;
+  void SendEvent(const char* aEventID);
 
-  // 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;
+  mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
 
-  // 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<nsCStringHashKey, struct ifaddrmsg> mAddressInfo;
+  RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
 };
 
-#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */
+#endif /* NSNETWORKLINKSERVICE_LINUX_H_ */
--- a/netwerk/system/mac/nsNetworkLinkService.h
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -25,19 +25,16 @@ class nsNetworkLinkService : public nsIN
 
  protected:
   virtual ~nsNetworkLinkService();
 
  private:
   bool mLinkUp;
   bool mStatusKnown;
 
-  // Toggles allowing the sending of network-changed event.
-  bool mAllowChangedEvent;
-
   SCNetworkReachabilityRef mReachability;
   CFRunLoopRef mCFRunLoop;
   CFRunLoopSourceRef mRunLoopSource;
   SCDynamicStoreRef mStoreRef;
 
   void UpdateReachability();
   void SendEvent(bool aNetworkChanged);
   static void ReachabilityChanged(SCNetworkReachabilityRef target,
--- a/netwerk/system/mac/nsNetworkLinkService.mm
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -18,17 +18,17 @@
 #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/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Telemetry.h"
 #include "nsNetworkLinkService.h"
 #include "../../base/IPv6Utils.h"
 
 #import <Cocoa/Cocoa.h>
 #import <netinet/in.h>
@@ -67,17 +67,16 @@ static void CFReleaseSafe(CFTypeRef cf) 
   }
 }
 
 NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
 
 nsNetworkLinkService::nsNetworkLinkService()
     : mLinkUp(true),
       mStatusKnown(false),
-      mAllowChangedEvent(true),
       mReachability(nullptr),
       mCFRunLoop(nullptr),
       mRunLoopSource(nullptr),
       mStoreRef(nullptr),
       mMutex("nsNetworkLinkService::mMutex") {}
 
 nsNetworkLinkService::~nsNetworkLinkService() = default;
 
@@ -402,18 +401,16 @@ nsresult nsNetworkLinkService::Init(void
 
   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);
 
-  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;
   bzero(&addr, sizeof(addr));
   addr.sin_len = sizeof(addr);
   addr.sin_family = AF_INET;
   mReachability = ::SCNetworkReachabilityCreateWithAddress(nullptr, (struct sockaddr*)&addr);
   if (!mReachability) {
@@ -548,17 +545,17 @@ void nsNetworkLinkService::UpdateReachab
 void nsNetworkLinkService::SendEvent(bool aNetworkChanged) {
   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   if (!observerService) {
     return;
   }
 
   const char* event;
   if (aNetworkChanged) {
-    if (!mAllowChangedEvent) {
+    if (!StaticPrefs::network_notify_changed()) {
       return;
     }
     event = NS_NETWORK_LINK_DATA_CHANGED;
   } else if (!mStatusKnown) {
     event = NS_NETWORK_LINK_DATA_UNKNOWN;
   } else {
     event = mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN;
   }
--- a/netwerk/system/moz.build
+++ b/netwerk/system/moz.build
@@ -9,9 +9,12 @@ if CONFIG['OS_ARCH'] == 'WINNT':
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     DIRS += ['mac']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['android']
 
 elif CONFIG['OS_ARCH'] == 'Linux':
-    DIRS += ['linux']
+    DIRS += [
+        'linux',
+        'netlink'
+    ]
new file mode 100644
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.cpp
@@ -0,0 +1,1450 @@
+/* -*- 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 <linux/if_ether.h>
+#include <net/if.h>
+#include <poll.h>
+#include <linux/rtnetlink.h>
+
+#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<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 */
+        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<NetlinkLink> link(new NetlinkLink());
+  if (!link->Init(aNlh)) {
+    return;
+  }
+
+  nsAutoCString linkName;
+  link->GetName(linkName);
+  if (aNlh->nlmsg_type == RTM_NEWLINK) {
+    LOG(("Adding new link [index=%u, name=%s]", link->GetIndex(),
+         linkName.get()));
+    mLinks.Put(link->GetIndex(), link.forget());
+  } else {
+    LOG(("Removing link [index=%u, name=%s]", link->GetIndex(),
+         linkName.get()));
+    mLinks.Remove(link->GetIndex());
+  }
+
+  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<NetlinkServiceListener> 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<NetlinkAddress> 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<NetlinkRoute> 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<nsAutoPtr<NetlinkRoute> >* 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<NetlinkNeighbor> neigh(new NetlinkNeighbor());
+  if (!neigh->Init(aNlh)) {
+    return;
+  }
+
+  nsAutoCString addrStr;
+  GetAddrStr(neigh->GetAddrPtr(), neigh->Family(), addrStr);
+
+  nsTArray<nsAutoPtr<NetlinkRoute> >* routesPtr;
+  nsAutoPtr<NetlinkRoute>* 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<NetlinkRoute> 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<NetlinkRoute>* 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<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;
+
+      CheckLinks();
+      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;
+
+  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<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);
+  }
+};
+
+bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) {
+  LOG(("NetlinkService::CalculateIDForFamily [family=%s]",
+       aFamily == AF_INET ? "AF_INET" : "AF_INET6"));
+
+  bool retval = false;
+
+  nsTArray<nsAutoPtr<NetlinkRoute> >* routesPtr;
+  nsAutoPtr<NetlinkRoute>* routeCheckResultPtr;
+  if (aFamily == AF_INET) {
+    routesPtr = &mIPv4Routes;
+    routeCheckResultPtr = &mIPv4RouteCheckResult;
+  } else {
+    routesPtr = &mIPv6Routes;
+    routeCheckResultPtr = &mIPv6RouteCheckResult;
+  }
+
+  // All known GW neighbors
+  nsTArray<NetlinkNeighbor*> 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<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;
+
+  if (idChanged && !initialIDCalculation) {
+    RefPtr<NetlinkServiceListener> 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
new file mode 100644
--- /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 <netinet/in.h>
+#include <linux/netlink.h>
+
+#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<nsIThread> 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<nsAutoPtr<NetlinkAddress> > mAddresses;
+  // All neighbors, key is an address
+  nsClassHashtable<nsCStringHashKey, NetlinkNeighbor> mNeighbors;
+  // All interfaces keyed by interface index
+  nsClassHashtable<nsUint32HashKey, NetlinkLink> mLinks;
+  // Default IPv4 routes
+  nsTArray<nsAutoPtr<NetlinkRoute> > mIPv4Routes;
+  // Default IPv6 routes
+  nsTArray<nsAutoPtr<NetlinkRoute> > mIPv6Routes;
+
+  // Route for mRouteCheckIPv4 address
+  nsAutoPtr<NetlinkRoute> mIPv4RouteCheckResult;
+  // Route for mRouteCheckIPv6 address
+  nsAutoPtr<NetlinkRoute> mIPv6RouteCheckResult;
+
+  nsTArray<nsAutoPtr<NetlinkMsg> > mOutgoingMessages;
+
+  RefPtr<NetlinkServiceListener> mListener;
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif /* NETLINKSERVICE_H_ */
new file mode 100644
--- /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'
--- a/netwerk/system/win32/nsNotifyAddrListener.cpp
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -29,17 +29,17 @@
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsNotifyAddrListener.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #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"
 #include "mozilla/Telemetry.h"
 #include <iptypes.h>
 #include <iphlpapi.h>
 
 using namespace mozilla;
@@ -110,18 +110,16 @@ NS_IMPL_ISUPPORTS(nsNotifyAddrListener, 
 nsNotifyAddrListener::nsNotifyAddrListener()
     : mLinkUp(true),  // assume true by default
       mStatusKnown(false),
       mCheckAttempted(false),
       mMutex("nsNotifyAddrListener::mMutex"),
       mCheckEvent(nullptr),
       mShutdown(false),
       mIPInterfaceChecksum(0),
-      mAllowChangedEvent(true),
-      mIPv6Changes(false),
       mCoalescingActive(false) {
   InitIphlpapi();
 }
 
 nsNotifyAddrListener::~nsNotifyAddrListener() {
   NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
   FreeDynamicLibraries();
 }
@@ -271,17 +269,18 @@ nsNotifyAddrListener::nextCoalesceWaitTi
 NS_IMETHODIMP
 nsNotifyAddrListener::Run() {
   mStartTime = TimeStamp::Now();
 
   calculateNetworkId();
 
   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);
     NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY);
 
     HANDLE handles[2] = {ev, mCheckEvent};
     OVERLAPPED overlapped = {0};
     bool shuttingDown = false;
@@ -345,20 +344,16 @@ 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);
 
-  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);
 
   rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
@@ -644,17 +639,18 @@ void nsNotifyAddrListener::CheckLinkStat
       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 (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) {
+      if (StaticPrefs::network_notify_changed() &&
+          (since.ToMilliseconds() > 2000)) {
         NetworkChanged();
       }
     }
     if (prevLinkUp != mLinkUp) {
       // UP/DOWN status changed, send appropriate UP/DOWN event
       SendEvent(mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
     }
   }
--- a/netwerk/system/win32/nsNotifyAddrListener.h
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -83,22 +83,16 @@ class nsNotifyAddrListener : public nsIN
 
   // 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;
 
-  // Network changed events are enabled
-  bool mAllowChangedEvent;
-
-  // Check for IPv6 network changes
-  bool mIPv6Changes;
-
   // Flag set while coalescing change events
   bool mCoalescingActive;
 
   // Time stamp for first event during coalescing
   mozilla::TimeStamp mChangeTime;
 };
 
 #endif /* NSNOTIFYADDRLISTENER_H_ */