tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsNetworkLinkService.mm (31073B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 #include <vector>
      6 #include <algorithm>
      7 
      8 #include <sys/socket.h>
      9 #include <sys/sysctl.h>
     10 
     11 #include <net/if.h>
     12 #include <net/if_dl.h>
     13 #include <net/if_types.h>
     14 #include <net/route.h>
     15 #include <netinet/in.h>
     16 #include <netinet/if_ether.h>
     17 #include <arpa/inet.h>
     18 #include <ifaddrs.h>
     19 #include <resolv.h>
     20 
     21 #include "nsCOMPtr.h"
     22 #include "nsIObserverService.h"
     23 #include "nsServiceManagerUtils.h"
     24 #include "nsString.h"
     25 #include "nsCRT.h"
     26 #include "nsNetCID.h"
     27 #include "nsThreadUtils.h"
     28 #include "mozilla/AppShutdown.h"
     29 #include "mozilla/Components.h"
     30 #include "mozilla/Logging.h"
     31 #include "mozilla/StaticPrefs_network.h"
     32 #include "mozilla/SHA1.h"
     33 #include "mozilla/Base64.h"
     34 #include "mozilla/ScopeExit.h"
     35 #include "mozilla/Services.h"
     36 #include "mozilla/glean/NetwerkMetrics.h"
     37 #include "nsNetworkLinkService.h"
     38 #include "../../base/IPv6Utils.h"
     39 #include "../LinkServiceCommon.h"
     40 #include "../NetworkLinkServiceDefines.h"
     41 
     42 #import <Cocoa/Cocoa.h>
     43 #import <netinet/in.h>
     44 
     45 #define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
     46 
     47 using namespace mozilla;
     48 
     49 static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
     50 #define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
     51 
     52 // See bug 1584165. Sometimes the ARP table is empty or doesn't have
     53 // the entry of gateway after the network change, so we'd like to delay
     54 // the calaulation of network id.
     55 static const uint32_t kNetworkIdDelayAfterChange = 3000;
     56 
     57 // When you remove search domains from the settings page and hit Apply a
     58 // network change event is generated, but res.dnsrch is not updated to the
     59 // correct values. Thus, after a network change, we add a small delay to
     60 // the runnable so the OS has the chance to update the values.
     61 static const uint32_t kDNSSuffixDelayAfterChange = 50;
     62 
     63 // If non-successful, extract the error code and return it.  This
     64 // error code dance is inspired by
     65 // http://developer.apple.com/technotes/tn/tn1145.html
     66 static OSStatus getErrorCodeBool(Boolean success) {
     67  OSStatus err = noErr;
     68  if (!success) {
     69    int scErr = ::SCError();
     70    if (scErr == kSCStatusOK) {
     71      scErr = kSCStatusFailed;
     72    }
     73    err = scErr;
     74  }
     75  return err;
     76 }
     77 
     78 // If given a NULL pointer, return the error code.
     79 static OSStatus getErrorCodePtr(const void* value) {
     80  return getErrorCodeBool(value != nullptr);
     81 }
     82 
     83 // Convenience function to allow NULL input.
     84 static void CFReleaseSafe(CFTypeRef cf) {
     85  if (cf) {
     86    // "If cf is NULL, this will cause a runtime error and your
     87    // application will crash." / Apple docs
     88    ::CFRelease(cf);
     89  }
     90 }
     91 
     92 mozilla::Atomic<bool, mozilla::MemoryOrdering::Relaxed> sHasNonLocalIPv6{true};
     93 
     94 NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver,
     95                  nsITimerCallback, nsINamed)
     96 
     97 nsNetworkLinkService::nsNetworkLinkService()
     98    : mLinkUp(true),
     99      mStatusKnown(false),
    100      mReachability(nullptr),
    101      mCFRunLoop(nullptr),
    102      mRunLoopSource(nullptr),
    103      mStoreRef(nullptr),
    104      mMutex("nsNetworkLinkService::mMutex") {}
    105 
    106 nsNetworkLinkService::~nsNetworkLinkService() = default;
    107 
    108 NS_IMETHODIMP
    109 nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
    110  *aIsUp = mLinkUp;
    111  return NS_OK;
    112 }
    113 
    114 NS_IMETHODIMP
    115 nsNetworkLinkService::GetLinkStatusKnown(bool* aIsUp) {
    116  *aIsUp = mStatusKnown;
    117  return NS_OK;
    118 }
    119 
    120 NS_IMETHODIMP
    121 nsNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
    122  NS_ENSURE_ARG_POINTER(aLinkType);
    123 
    124  // XXX This function has not yet been implemented for this platform
    125  *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
    126  return NS_OK;
    127 }
    128 
    129 NS_IMETHODIMP
    130 nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
    131 #ifdef BASE_BROWSER_VERSION
    132  aNetworkID.Truncate();
    133 #else
    134  MutexAutoLock lock(mMutex);
    135  aNetworkID = mNetworkId;
    136 #endif
    137  return NS_OK;
    138 }
    139 
    140 NS_IMETHODIMP
    141 nsNetworkLinkService::GetPlatformDNSIndications(
    142    uint32_t* aPlatformDNSIndications) {
    143  return NS_ERROR_NOT_IMPLEMENTED;
    144 }
    145 
    146 void nsNetworkLinkService::GetDnsSuffixListInternal() {
    147  MOZ_ASSERT(!NS_IsMainThread());
    148  LOG(("GetDnsSuffixListInternal"));
    149 
    150  auto sendNotification = mozilla::MakeScopeExit([self = RefPtr{this}] {
    151    NS_DispatchToMainThread(NS_NewRunnableFunction(
    152        "nsNetworkLinkService::GetDnsSuffixListInternal", [self]() {
    153          self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr);
    154        }));
    155  });
    156 
    157  nsTArray<nsCString> result;
    158 
    159  struct __res_state res;
    160  if (res_ninit(&res) == 0) {
    161    for (int i = 0; i < MAXDNSRCH; i++) {
    162      if (!res.dnsrch[i]) {
    163        break;
    164      }
    165      LOG(("DNS search domain from [%s]\n", res.dnsrch[i]));
    166      result.AppendElement(nsCString(res.dnsrch[i]));
    167    }
    168    res_nclose(&res);
    169  }
    170 
    171  MutexAutoLock lock(mMutex);
    172  mDNSSuffixList = std::move(result);
    173 }
    174 
    175 NS_IMETHODIMP
    176 nsNetworkLinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
    177  aDnsSuffixList.Clear();
    178 
    179  MutexAutoLock lock(mMutex);
    180  aDnsSuffixList.AppendElements(mDNSSuffixList);
    181  return NS_OK;
    182 }
    183 
    184 NS_IMETHODIMP
    185 nsNetworkLinkService::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
    186  return NS_ERROR_NOT_IMPLEMENTED;
    187 }
    188 
    189 NS_IMETHODIMP
    190 nsNetworkLinkService::GetNativeResolvers(
    191    nsTArray<mozilla::net::NetAddr>& aResolvers) {
    192  return NS_ERROR_NOT_IMPLEMENTED;
    193 }
    194 
    195 #ifndef SA_SIZE
    196 #  define SA_SIZE(sa)                                    \
    197    ((!(sa) || ((struct sockaddr*)(sa))->sa_len == 0)    \
    198         ? sizeof(uint32_t)                              \
    199         : 1 + ((((struct sockaddr*)(sa))->sa_len - 1) | \
    200                (sizeof(uint32_t) - 1)))
    201 #endif
    202 
    203 static bool getMac(struct sockaddr_dl* sdl, char* buf, size_t bufsize) {
    204  unsigned char* mac;
    205  mac = (unsigned char*)LLADDR(sdl);
    206 
    207  if (sdl->sdl_alen != 6) {
    208    LOG(("networkid: unexpected MAC size %u", sdl->sdl_alen));
    209    return false;
    210  }
    211 
    212  snprintf(buf, bufsize, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1],
    213           mac[2], mac[3], mac[4], mac[5]);
    214  return true;
    215 }
    216 
    217 /* If the IP matches, get the MAC and return true */
    218 static bool matchIp(struct sockaddr_dl* sdl, struct sockaddr_inarp* addr,
    219                    char* ip, char* buf, size_t bufsize) {
    220  if (sdl->sdl_alen) {
    221    if (!strcmp(inet_ntoa(addr->sin_addr), ip)) {
    222      if (getMac(sdl, buf, bufsize)) {
    223        return true; /* done! */
    224      }
    225    }
    226  }
    227  return false; /* continue */
    228 }
    229 
    230 /*
    231 * Scan for the 'IP' address in the ARP table and store the corresponding MAC
    232 * address in 'mac'. The output buffer is 'maclen' bytes big.
    233 *
    234 * Returns 'true' if it found the IP and returns a MAC.
    235 */
    236 static bool scanArp(char* ip, char* mac, size_t maclen) {
    237  int mib[6];
    238  char *lim, *next;
    239  int st;
    240 
    241  mib[0] = CTL_NET;
    242  mib[1] = PF_ROUTE;
    243  mib[2] = 0;
    244  mib[3] = AF_INET;
    245  mib[4] = NET_RT_FLAGS;
    246  mib[5] = RTF_LLINFO;
    247 
    248  size_t needed;
    249  auto allocateBuf = [&]() -> UniquePtr<char[]> {
    250    // calling sysctl with a null buffer to get the minimum buffer size
    251    if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
    252      return nullptr;
    253    }
    254 
    255    if (needed == 0) {
    256      LOG(("scanArp: empty table"));
    257      return nullptr;
    258    }
    259 
    260    return MakeUnique<char[]>(needed);
    261  };
    262 
    263  UniquePtr<char[]> buf = allocateBuf();
    264  if (!buf) {
    265    return false;
    266  }
    267 
    268  st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
    269  // If errno is ENOMEM, try to allocate a new buffer and try again.
    270  if (st != 0) {
    271    if (errno != ENOMEM) {
    272      return false;
    273    }
    274 
    275    buf = allocateBuf();
    276    if (!buf) {
    277      return false;
    278    }
    279 
    280    st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
    281    if (st == -1) {
    282      return false;
    283    }
    284  }
    285 
    286  lim = &buf[needed];
    287 
    288  struct rt_msghdr* rtm;
    289  for (next = &buf[0]; next < lim; next += rtm->rtm_msglen) {
    290    rtm = reinterpret_cast<struct rt_msghdr*>(next);
    291    struct sockaddr_inarp* sin2 =
    292        reinterpret_cast<struct sockaddr_inarp*>(rtm + 1);
    293    struct sockaddr_dl* sdl =
    294        reinterpret_cast<struct sockaddr_dl*>((char*)sin2 + SA_SIZE(sin2));
    295    if (matchIp(sdl, sin2, ip, mac, maclen)) {
    296      return true;
    297    }
    298  }
    299 
    300  return false;
    301 }
    302 
    303 // Append the mac address of rtm to `stringsToHash`. If it's not in arp table,
    304 // append ifname and IP address.
    305 static bool parseHashKey(struct rt_msghdr* rtm,
    306                         nsTArray<nsCString>& stringsToHash,
    307                         bool skipDstCheck) {
    308  struct sockaddr* sa;
    309  struct sockaddr_in* sockin;
    310  char ip[INET_ADDRSTRLEN];
    311 
    312  // Ignore the routing table message without destination/gateway sockaddr.
    313  // Destination address is needed to check if the gateway is default or
    314  // overwritten by VPN. If yes, append the mac address or IP/interface name to
    315  // `stringsToHash`.
    316  if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) != (RTA_DST | RTA_GATEWAY)) {
    317    return false;
    318  }
    319 
    320  sa = reinterpret_cast<struct sockaddr*>(rtm + 1);
    321 
    322  struct sockaddr* destination =
    323      reinterpret_cast<struct sockaddr*>((char*)sa + RTAX_DST * SA_SIZE(sa));
    324  if (!destination || destination->sa_family != AF_INET) {
    325    return false;
    326  }
    327 
    328  sockin = reinterpret_cast<struct sockaddr_in*>(destination);
    329 
    330  inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
    331 
    332  if (!skipDstCheck && strcmp("0.0.0.0", ip)) {
    333    return false;
    334  }
    335 
    336  struct sockaddr* gateway = reinterpret_cast<struct sockaddr*>(
    337      (char*)sa + RTAX_GATEWAY * SA_SIZE(sa));
    338 
    339  if (!gateway) {
    340    return false;
    341  }
    342  if (gateway->sa_family == AF_INET) {
    343    sockin = reinterpret_cast<struct sockaddr_in*>(gateway);
    344    inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
    345    char mac[18];
    346 
    347    // TODO: cache the arp table instead of multiple system call.
    348    if (scanArp(ip, mac, sizeof(mac))) {
    349      stringsToHash.AppendElement(nsCString(mac));
    350    } else {
    351      // Can't find a real MAC address. This might be a VPN gateway.
    352      char buf[IFNAMSIZ] = {0};
    353      char* ifName = if_indextoname(rtm->rtm_index, buf);
    354      if (!ifName) {
    355        LOG(("parseHashKey: AF_INET if_indextoname failed"));
    356        return false;
    357      }
    358 
    359      stringsToHash.AppendElement(nsCString(ifName));
    360      stringsToHash.AppendElement(nsCString(ip));
    361    }
    362  } else if (gateway->sa_family == AF_LINK) {
    363    char buf[64];
    364    struct sockaddr_dl* sockdl = reinterpret_cast<struct sockaddr_dl*>(gateway);
    365    if (getMac(sockdl, buf, sizeof(buf))) {
    366      stringsToHash.AppendElement(nsCString(buf));
    367    } else {
    368      char buf[IFNAMSIZ] = {0};
    369      char* ifName = if_indextoname(rtm->rtm_index, buf);
    370      if (!ifName) {
    371        LOG(("parseHashKey: AF_LINK if_indextoname failed"));
    372        return false;
    373      }
    374 
    375      stringsToHash.AppendElement(nsCString(ifName));
    376    }
    377  }
    378  return true;
    379 }
    380 
    381 // It detects the IP of the default gateways in the routing table, then the MAC
    382 // address of that IP in the ARP table before it hashes that string (to avoid
    383 // information leakage).
    384 bool nsNetworkLinkService::RoutingTable(nsTArray<nsCString>& aHash) {
    385  size_t needed;
    386  int mib[6];
    387  struct rt_msghdr* rtm;
    388 
    389  mib[0] = CTL_NET;
    390  mib[1] = PF_ROUTE;
    391  mib[2] = 0;
    392  mib[3] = 0;
    393  mib[4] = NET_RT_DUMP;
    394  mib[5] = 0;
    395 
    396  if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
    397    return false;
    398  }
    399 
    400  UniquePtr<char[]> buf(new char[needed]);
    401 
    402  if (sysctl(mib, 6, &buf[0], &needed, nullptr, 0) < 0) {
    403    return false;
    404  }
    405 
    406  char* lim = &buf[0] + needed;
    407  bool rv = false;
    408 
    409  // `next + 1 < lim` ensures we have valid `rtm->rtm_msglen` which is an
    410  // unsigned short at the beginning of `rt_msghdr`.
    411  for (char* next = &buf[0]; next + 1 < lim; next += rtm->rtm_msglen) {
    412    rtm = reinterpret_cast<struct rt_msghdr*>(next);
    413 
    414    if (next + rtm->rtm_msglen > lim) {
    415      LOG(("Rt msg is truncated..."));
    416      break;
    417    }
    418 
    419    if (parseHashKey(rtm, aHash, false)) {
    420      rv = true;
    421    }
    422  }
    423  return rv;
    424 }
    425 
    426 // Detect the routing of network.netlink.route.check.IPv4
    427 bool nsNetworkLinkService::RoutingFromKernel(nsTArray<nsCString>& aHash) {
    428  int sockfd;
    429  if ((sockfd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) {
    430    LOG(("RoutingFromKernel: Can create a socket for network id"));
    431    return false;
    432  }
    433  auto sockfd_guard = mozilla::MakeScopeExit([sockfd] { close(sockfd); });
    434 
    435  MOZ_ASSERT(!NS_IsMainThread());
    436 
    437  size_t needed = 1024;
    438  struct rt_msghdr* rtm;
    439  struct sockaddr_in* sin;
    440  UniquePtr<char[]> buf(new char[needed]);
    441  pid_t pid;
    442  int seq;
    443 
    444  rtm = reinterpret_cast<struct rt_msghdr*>(&buf[0]);
    445  memset(rtm, 0, sizeof(struct rt_msghdr));
    446  rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);
    447  rtm->rtm_version = RTM_VERSION;
    448  rtm->rtm_type = RTM_GET;
    449  rtm->rtm_addrs = RTA_DST;
    450  rtm->rtm_pid = (pid = getpid());
    451  rtm->rtm_seq = (seq = random());
    452 
    453  sin = reinterpret_cast<struct sockaddr_in*>(rtm + 1);
    454  memset(sin, 0, sizeof(struct sockaddr_in));
    455  sin->sin_len = sizeof(struct sockaddr_in);
    456  sin->sin_family = AF_INET;
    457  sin->sin_addr = mRouteCheckIPv4;
    458 
    459  if (write(sockfd, rtm, rtm->rtm_msglen) == -1) {
    460    LOG(("RoutingFromKernel: write() failed. No route to the predefine "
    461         "destincation"));
    462    return false;
    463  }
    464 
    465  do {
    466    ssize_t r;
    467    if ((r = read(sockfd, rtm, needed)) < 0) {
    468      LOG(("RoutingFromKernel: read() failed."));
    469      return false;
    470    }
    471 
    472    LOG(("RoutingFromKernel: read() rtm_type: %d (%d), rtm_pid: %d (%d), "
    473         "rtm_seq: %d (%d)\n",
    474         rtm->rtm_type, RTM_GET, rtm->rtm_pid, pid, rtm->rtm_seq, seq));
    475  } while (rtm->rtm_type != RTM_GET || rtm->rtm_pid != pid ||
    476           rtm->rtm_seq != seq);
    477 
    478  return parseHashKey(rtm, aHash, true);
    479 }
    480 
    481 // Figure out the current IPv4 "network identification" string.
    482 bool nsNetworkLinkService::IPv4NetworkId(SHA1Sum* aSHA1) {
    483  nsTArray<nsCString> hash;
    484  if (!RoutingTable(hash)) {
    485    NS_WARNING("IPv4NetworkId: No default gateways");
    486  }
    487 
    488  if (!RoutingFromKernel(hash)) {
    489    NS_WARNING("IPv4NetworkId: No route to the predefined destination");
    490  }
    491 
    492  // We didn't get any valid hash key to generate network ID.
    493  if (hash.IsEmpty()) {
    494    LOG(("IPv4NetworkId: No valid hash key"));
    495    return false;
    496  }
    497 
    498  hash.Sort();
    499  for (uint32_t i = 0; i < hash.Length(); ++i) {
    500    LOG(("IPv4NetworkId: Hashing string for network id: %s", hash[i].get()));
    501    aSHA1->update(hash[i].get(), hash[i].Length());
    502  }
    503 
    504  return true;
    505 }
    506 
    507 //
    508 // Sort and hash the prefixes and netmasks
    509 //
    510 void nsNetworkLinkService::HashSortedPrefixesAndNetmasks(
    511    std::vector<prefix_and_netmask> prefixAndNetmaskStore, SHA1Sum* sha1) {
    512  // getifaddrs does not guarantee the interfaces will always be in the same
    513  // order. We want to make sure the hash remains consistent Regardless of the
    514  // interface order.
    515  std::sort(prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
    516            [](prefix_and_netmask a, prefix_and_netmask b) {
    517              // compare prefixStore
    518              int comparedPrefix = memcmp(&a.first, &b.first, sizeof(in6_addr));
    519              if (comparedPrefix == 0) {
    520                // compare netmaskStore
    521                return memcmp(&a.second, &b.second, sizeof(in6_addr)) < 0;
    522              }
    523              return comparedPrefix < 0;
    524            });
    525 
    526  for (const auto& prefixAndNetmask : prefixAndNetmaskStore) {
    527    sha1->update(&prefixAndNetmask.first, sizeof(in6_addr));
    528    sha1->update(&prefixAndNetmask.second, sizeof(in6_addr));
    529  }
    530 }
    531 
    532 bool nsNetworkLinkService::IPv6NetworkId(SHA1Sum* sha1) {
    533  struct ifaddrs* ifap;
    534  std::vector<prefix_and_netmask> prefixAndNetmaskStore;
    535 
    536  if (!getifaddrs(&ifap)) {
    537    bool hasNonLocalIPv6 = false;
    538    struct ifaddrs* ifa;
    539    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
    540      if (ifa->ifa_addr == NULL) {
    541        continue;
    542      }
    543      if ((AF_INET6 == ifa->ifa_addr->sa_family) &&
    544          !(ifa->ifa_flags & (IFF_POINTOPOINT | IFF_LOOPBACK))) {
    545        // only IPv6 interfaces that aren't pointtopoint or loopback
    546        hasNonLocalIPv6 = true;
    547        struct sockaddr_in6* sin_netmask =
    548            (struct sockaddr_in6*)ifa->ifa_netmask;
    549        if (sin_netmask) {
    550          struct sockaddr_in6* sin_addr = (struct sockaddr_in6*)ifa->ifa_addr;
    551          int scope = net::utils::ipv6_scope(sin_addr->sin6_addr.s6_addr);
    552          if (scope == IPV6_SCOPE_GLOBAL) {
    553            struct in6_addr prefix;
    554            memset(&prefix, 0, sizeof(prefix));
    555            // Get the prefix by combining the address and netmask.
    556            for (size_t i = 0; i < sizeof(prefix); ++i) {
    557              prefix.s6_addr[i] = sin_addr->sin6_addr.s6_addr[i] &
    558                                  sin_netmask->sin6_addr.s6_addr[i];
    559            }
    560 
    561            // check if prefix and netmask was already found
    562            auto prefixAndNetmask =
    563                std::make_pair(prefix, sin_netmask->sin6_addr);
    564            auto foundPosition = std::find_if(
    565                prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
    566                [&prefixAndNetmask](prefix_and_netmask current) {
    567                  return memcmp(&prefixAndNetmask.first, &current.first,
    568                                sizeof(in6_addr)) == 0 &&
    569                         memcmp(&prefixAndNetmask.second, &current.second,
    570                                sizeof(in6_addr)) == 0;
    571                });
    572            if (foundPosition != prefixAndNetmaskStore.end()) {
    573              continue;
    574            }
    575            prefixAndNetmaskStore.push_back(prefixAndNetmask);
    576          }
    577        }
    578      }
    579    }
    580    sHasNonLocalIPv6 = hasNonLocalIPv6;
    581    freeifaddrs(ifap);
    582  }
    583  if (prefixAndNetmaskStore.empty()) {
    584    LOG(("IPv6NetworkId failed"));
    585    return false;
    586  }
    587 
    588  nsNetworkLinkService::HashSortedPrefixesAndNetmasks(prefixAndNetmaskStore,
    589                                                      sha1);
    590 
    591  return true;
    592 }
    593 
    594 void nsNetworkLinkService::calculateNetworkIdWithDelay(uint32_t aDelay) {
    595  MOZ_ASSERT(NS_IsMainThread());
    596 
    597  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownNetTeardown)) {
    598    return;
    599  }
    600 
    601  if (aDelay) {
    602    if (mNetworkIdTimer) {
    603      LOG(("Restart the network id timer."));
    604      mNetworkIdTimer->Cancel();
    605    } else {
    606      LOG(("Create the network id timer."));
    607      mNetworkIdTimer = NS_NewTimer();
    608    }
    609    mNetworkIdTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
    610    return;
    611  }
    612 
    613  nsCOMPtr<nsIEventTarget> target;
    614  target = mozilla::components::StreamTransport::Service();
    615  if (!target) {
    616    return;
    617  }
    618 
    619  MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
    620      NewRunnableMethod("nsNetworkLinkService::calculateNetworkIdInternal",
    621                        this,
    622                        &nsNetworkLinkService::calculateNetworkIdInternal),
    623      NS_DISPATCH_NORMAL));
    624 }
    625 
    626 NS_IMETHODIMP
    627 nsNetworkLinkService::Notify(nsITimer* aTimer) {
    628  MOZ_ASSERT(aTimer == mNetworkIdTimer);
    629 
    630  mNetworkIdTimer = nullptr;
    631  calculateNetworkIdWithDelay(0);
    632  return NS_OK;
    633 }
    634 
    635 NS_IMETHODIMP
    636 nsNetworkLinkService::GetName(nsACString& aName) {
    637  aName.AssignLiteral("nsNetworkLinkService");
    638  return NS_OK;
    639 }
    640 
    641 void nsNetworkLinkService::calculateNetworkIdInternal(void) {
    642  MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread");
    643  SHA1Sum sha1;
    644  bool idChanged = false;
    645  bool found4 = IPv4NetworkId(&sha1);
    646  bool found6 = IPv6NetworkId(&sha1);
    647 
    648  if (found4 || found6) {
    649    nsAutoCString output;
    650    SeedNetworkId(sha1);
    651    uint8_t digest[SHA1Sum::kHashSize];
    652    sha1.finish(digest);
    653    nsAutoCString newString(reinterpret_cast<char*>(digest),
    654                            SHA1Sum::kHashSize);
    655    nsresult rv = Base64Encode(newString, output);
    656    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
    657    LOG(("networkid: id %s\n", output.get()));
    658    MutexAutoLock lock(mMutex);
    659    if (mNetworkId != output) {
    660      // new id
    661      if (found4 && !found6) {
    662        glean::network::id.AccumulateSingleSample(1);  // IPv4 only
    663      } else if (!found4 && found6) {
    664        glean::network::id.AccumulateSingleSample(3);  // IPv6 only
    665      } else {
    666        glean::network::id.AccumulateSingleSample(4);  // Both!
    667      }
    668      mNetworkId = output;
    669      idChanged = true;
    670    } else {
    671      // same id
    672      LOG(("Same network id"));
    673      glean::network::id.AccumulateSingleSample(2);
    674    }
    675  } else {
    676    // no id
    677    LOG(("No network id"));
    678    MutexAutoLock lock(mMutex);
    679    if (!mNetworkId.IsEmpty()) {
    680      mNetworkId.Truncate();
    681      idChanged = true;
    682      glean::network::id.AccumulateSingleSample(0);
    683    }
    684  }
    685 
    686  // Don't report network change if this is the first time we calculate the id.
    687  static bool initialIDCalculation = true;
    688  if (idChanged && !initialIDCalculation) {
    689    RefPtr<nsNetworkLinkService> self = this;
    690 
    691    NS_DispatchToMainThread(NS_NewRunnableFunction(
    692        "nsNetworkLinkService::calculateNetworkIdInternal",
    693        [self]() { self->OnNetworkIdChanged(); }));
    694  }
    695 
    696  initialIDCalculation = false;
    697 }
    698 
    699 NS_IMETHODIMP
    700 nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
    701                              const char16_t* data) {
    702  if (!strcmp(topic, "xpcom-shutdown")) {
    703    Shutdown();
    704  }
    705 
    706  return NS_OK;
    707 }
    708 
    709 /* static */
    710 void nsNetworkLinkService::NetworkConfigChanged(SCDynamicStoreRef aStoreREf,
    711                                                CFArrayRef aChangedKeys,
    712                                                void* aInfo) {
    713  LOG(("nsNetworkLinkService::NetworkConfigChanged"));
    714 
    715  bool ipConfigChanged = false;
    716  bool dnsConfigChanged = false;
    717  for (CFIndex i = 0; i < CFArrayGetCount(aChangedKeys); ++i) {
    718    CFStringRef key =
    719        static_cast<CFStringRef>(CFArrayGetValueAtIndex(aChangedKeys, i));
    720    if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
    721        CFStringHasSuffix(key, kSCEntNetIPv6)) {
    722      ipConfigChanged = true;
    723    }
    724    if (CFStringHasSuffix(key, kSCEntNetDNS)) {
    725      dnsConfigChanged = true;
    726    }
    727  }
    728 
    729  nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(aInfo);
    730  if (ipConfigChanged) {
    731    service->OnIPConfigChanged();
    732  }
    733 
    734  if (dnsConfigChanged) {
    735    service->DNSConfigChanged(kDNSSuffixDelayAfterChange);
    736  }
    737 }
    738 
    739 void nsNetworkLinkService::DNSConfigChanged(uint32_t aDelayMs) {
    740  LOG(("nsNetworkLinkService::DNSConfigChanged"));
    741  nsCOMPtr<nsIEventTarget> target;
    742  target = mozilla::components::StreamTransport::Service();
    743  if (!target) {
    744    return;
    745  }
    746  if (aDelayMs) {
    747    MutexAutoLock lock(mMutex);
    748    nsCOMPtr<nsITimer> timer;
    749    MOZ_ALWAYS_SUCCEEDS(NS_NewTimerWithCallback(
    750        getter_AddRefs(timer),
    751        [self = RefPtr{this}](nsITimer* aTimer) {
    752          self->GetDnsSuffixListInternal();
    753 
    754          MutexAutoLock lock(self->mMutex);
    755          self->mDNSConfigChangedTimers.RemoveElement(aTimer);
    756        },
    757        TimeDuration::FromMilliseconds(aDelayMs), nsITimer::TYPE_ONE_SHOT,
    758        "nsNetworkLinkService::GetDnsSuffixListInternal"_ns, target));
    759    mDNSConfigChangedTimers.AppendElement(timer);
    760  } else {
    761    MOZ_ALWAYS_SUCCEEDS(target->Dispatch(NS_NewRunnableFunction(
    762        "nsNetworkLinkService::GetDnsSuffixListInternal",
    763        [self = RefPtr{this}]() { self->GetDnsSuffixListInternal(); })));
    764  }
    765 }
    766 
    767 nsresult nsNetworkLinkService::Init(void) {
    768  nsresult rv;
    769 
    770  nsCOMPtr<nsIObserverService> observerService;
    771  observerService = mozilla::components::Observer::Service(&rv);
    772  NS_ENSURE_SUCCESS(rv, rv);
    773 
    774  rv = observerService->AddObserver(this, "xpcom-shutdown", false);
    775  NS_ENSURE_SUCCESS(rv, rv);
    776 
    777  if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
    778    LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
    779    MOZ_DIAGNOSTIC_CRASH("Cannot parse address " ROUTE_CHECK_IPV4);
    780    return NS_ERROR_UNEXPECTED;
    781  }
    782 
    783  // If the network reachability API can reach 0.0.0.0 without
    784  // requiring a connection, there is a network interface available.
    785  struct sockaddr_in addr;
    786  bzero(&addr, sizeof(addr));
    787  addr.sin_len = sizeof(addr);
    788  addr.sin_family = AF_INET;
    789  mReachability = ::SCNetworkReachabilityCreateWithAddress(
    790      nullptr, (struct sockaddr*)&addr);
    791  if (!mReachability) {
    792    return NS_ERROR_NOT_AVAILABLE;
    793  }
    794 
    795  SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr};
    796  if (!::SCNetworkReachabilitySetCallback(mReachability, ReachabilityChanged,
    797                                          &context)) {
    798    NS_WARNING("SCNetworkReachabilitySetCallback failed.");
    799    ::CFRelease(mReachability);
    800    mReachability = nullptr;
    801    return NS_ERROR_NOT_AVAILABLE;
    802  }
    803 
    804  SCDynamicStoreContext storeContext = {0, this, nullptr, nullptr, nullptr};
    805  mStoreRef =
    806      ::SCDynamicStoreCreate(nullptr, CFSTR("IPAndDNSChangeCallbackSCF"),
    807                             NetworkConfigChanged, &storeContext);
    808 
    809  CFStringRef patterns[4] = {nullptr, nullptr, nullptr, nullptr};
    810  OSStatus err = getErrorCodePtr(mStoreRef);
    811  if (err == noErr) {
    812    // This pattern is "State:/Network/Service/[^/]+/IPv4".
    813    patterns[0] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
    814        nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
    815    // This pattern is "State:/Network/Service/[^/]+/IPv6".
    816    patterns[1] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
    817        nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6);
    818    // This pattern is "State:/Network/Service/[^/]+/DNS".
    819    patterns[2] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
    820        nullptr, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetDNS);
    821    // This pattern is "Setup:/Network/Service/[^/]+/DNS".
    822    patterns[3] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
    823        nullptr, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetDNS);
    824    if (!patterns[0] || !patterns[1] || !patterns[2] || !patterns[3]) {
    825      err = -1;
    826    }
    827  }
    828 
    829  CFArrayRef patternList = nullptr;
    830  // Create a pattern list containing just one pattern,
    831  // then tell SCF that we want to watch changes in keys
    832  // that match that pattern list, then create our run loop
    833  // source.
    834  if (err == noErr) {
    835    patternList = ::CFArrayCreate(nullptr, (const void**)patterns, 4,
    836                                  &kCFTypeArrayCallBacks);
    837    if (!patternList) {
    838      err = -1;
    839    }
    840  }
    841  if (err == noErr) {
    842    err = getErrorCodeBool(
    843        ::SCDynamicStoreSetNotificationKeys(mStoreRef, nullptr, patternList));
    844  }
    845 
    846  if (err == noErr) {
    847    mRunLoopSource = ::SCDynamicStoreCreateRunLoopSource(nullptr, mStoreRef, 0);
    848    err = getErrorCodePtr(mRunLoopSource);
    849  }
    850 
    851  CFReleaseSafe(patterns[0]);
    852  CFReleaseSafe(patterns[1]);
    853  CFReleaseSafe(patterns[2]);
    854  CFReleaseSafe(patterns[3]);
    855  CFReleaseSafe(patternList);
    856 
    857  if (err != noErr) {
    858    CFReleaseSafe(mStoreRef);
    859    return NS_ERROR_NOT_AVAILABLE;
    860  }
    861 
    862  // Get the current run loop.  This service is initialized at startup,
    863  // so we shouldn't run in to any problems with modal dialog run loops.
    864  mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
    865  if (!mCFRunLoop) {
    866    NS_WARNING("Could not get current run loop.");
    867    ::CFRelease(mReachability);
    868    mReachability = nullptr;
    869    return NS_ERROR_NOT_AVAILABLE;
    870  }
    871  ::CFRetain(mCFRunLoop);
    872 
    873  ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
    874 
    875  if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
    876                                                  kCFRunLoopDefaultMode)) {
    877    NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
    878    ::CFRelease(mReachability);
    879    mReachability = nullptr;
    880    ::CFRelease(mCFRunLoop);
    881    mCFRunLoop = nullptr;
    882    return NS_ERROR_NOT_AVAILABLE;
    883  }
    884  UpdateReachability();
    885 
    886  calculateNetworkIdWithDelay(0);
    887 
    888  DNSConfigChanged(0);
    889 
    890  return NS_OK;
    891 }
    892 
    893 nsresult nsNetworkLinkService::Shutdown() {
    894  if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability, mCFRunLoop,
    895                                                    kCFRunLoopDefaultMode)) {
    896    NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
    897  }
    898 
    899  CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
    900 
    901  ::CFRelease(mReachability);
    902  mReachability = nullptr;
    903 
    904  ::CFRelease(mCFRunLoop);
    905  mCFRunLoop = nullptr;
    906 
    907  ::CFRelease(mStoreRef);
    908  mStoreRef = nullptr;
    909 
    910  ::CFRelease(mRunLoopSource);
    911  mRunLoopSource = nullptr;
    912 
    913  if (mNetworkIdTimer) {
    914    mNetworkIdTimer->Cancel();
    915    mNetworkIdTimer = nullptr;
    916  }
    917 
    918  nsTArray<nsCOMPtr<nsITimer>> dnsConfigChangedTimers;
    919  {
    920    MutexAutoLock lock(mMutex);
    921    dnsConfigChangedTimers = std::move(mDNSConfigChangedTimers);
    922    mDNSConfigChangedTimers.Clear();
    923  }
    924  for (const auto& timer : dnsConfigChangedTimers) {
    925    timer->Cancel();
    926  }
    927 
    928  return NS_OK;
    929 }
    930 
    931 void nsNetworkLinkService::UpdateReachability() {
    932  if (!mReachability) {
    933    return;
    934  }
    935 
    936  SCNetworkConnectionFlags flags;
    937  if (!::SCNetworkReachabilityGetFlags(mReachability, &flags)) {
    938    mStatusKnown = false;
    939    return;
    940  }
    941 
    942  bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
    943  bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
    944 
    945  mLinkUp = (reachable && !needsConnection);
    946  mStatusKnown = true;
    947 }
    948 
    949 void nsNetworkLinkService::OnIPConfigChanged() {
    950  MOZ_ASSERT(NS_IsMainThread());
    951 
    952  calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
    953  if (!StaticPrefs::network_notify_changed()) {
    954    return;
    955  }
    956 
    957  NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_CHANGED);
    958 }
    959 
    960 void nsNetworkLinkService::OnNetworkIdChanged() {
    961  MOZ_ASSERT(NS_IsMainThread());
    962 
    963  NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
    964 }
    965 
    966 void nsNetworkLinkService::OnReachabilityChanged() {
    967  MOZ_ASSERT(NS_IsMainThread());
    968 
    969  if (!mStatusKnown) {
    970    NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UNKNOWN);
    971    return;
    972  }
    973 
    974  NotifyObservers(NS_NETWORK_LINK_TOPIC, mLinkUp ? NS_NETWORK_LINK_DATA_UP
    975                                                 : NS_NETWORK_LINK_DATA_DOWN);
    976 }
    977 
    978 void nsNetworkLinkService::NotifyObservers(const char* aTopic,
    979                                           const char* aData) {
    980  MOZ_ASSERT(NS_IsMainThread());
    981 
    982  LOG(("nsNetworkLinkService::NotifyObservers: topic:%s data:%s\n", aTopic,
    983       aData ? aData : ""));
    984 
    985  nsCOMPtr<nsIObserverService> observerService =
    986      mozilla::services::GetObserverService();
    987 
    988  if (observerService) {
    989    observerService->NotifyObservers(
    990        static_cast<nsINetworkLinkService*>(this), aTopic,
    991        aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
    992  }
    993 }
    994 
    995 /* static */
    996 void nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
    997                                               SCNetworkConnectionFlags flags,
    998                                               void* info) {
    999  LOG(("nsNetworkLinkService::ReachabilityChanged"));
   1000  nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(info);
   1001 
   1002  service->UpdateReachability();
   1003  service->OnReachabilityChanged();
   1004  service->calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
   1005  // If a new interface is up or the order of interfaces is changed, we should
   1006  // update the DNS suffix list.
   1007  service->DNSConfigChanged(0);
   1008 }
   1009 
   1010 // static
   1011 bool nsINetworkLinkService::HasNonLocalIPv6Address() {
   1012  return sHasNonLocalIPv6;
   1013 }