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, ¤t.first, sizeof(in6_addr)) == 0 && 528 memcmp(&prefixAndNetmask.second, ¤t.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