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