Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <numeric>
#include <vector>
#include <algorithm>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <resolv.h>
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsCRT.h"
#include "nsNetCID.h"
#include "nsThreadUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/SHA1.h"
#include "mozilla/Base64.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "nsNetworkLinkService.h"
#include "../../base/IPv6Utils.h"
#include "../NetworkLinkServiceDefines.h"
#import <Cocoa/Cocoa.h>
#import <netinet/in.h>
#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
using namespace mozilla;
static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
// See bug 1584165. Sometimes the ARP table is empty or doesn't have
// the entry of gateway after the network change, so we'd like to delay
// the calaulation of network id.
static const uint32_t kNetworkIdDelayAfterChange = 3000;
// When you remove search domains from the settings page and hit Apply a
// network change event is generated, but res.dnsrch is not updated to the
// correct values. Thus, after a network change, we add a small delay to
// the runnable so the OS has the chance to update the values.
static const uint32_t kDNSSuffixDelayAfterChange = 50;
// If non-successful, extract the error code and return it. This
// error code dance is inspired by
static OSStatus getErrorCodeBool(Boolean success) {
OSStatus err = noErr;
if (!success) {
int scErr = ::SCError();
if (scErr == kSCStatusOK) {
scErr = kSCStatusFailed;
}
err = scErr;
}
return err;
}
// If given a NULL pointer, return the error code.
static OSStatus getErrorCodePtr(const void* value) { return getErrorCodeBool(value != nullptr); }
// Convenience function to allow NULL input.
static void CFReleaseSafe(CFTypeRef cf) {
if (cf) {
// "If cf is NULL, this will cause a runtime error and your
// application will crash." / Apple docs
::CFRelease(cf);
}
}
NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver, nsITimerCallback,
nsINamed)
nsNetworkLinkService::nsNetworkLinkService()
: mLinkUp(true),
mStatusKnown(false),
mReachability(nullptr),
mCFRunLoop(nullptr),
mRunLoopSource(nullptr),
mStoreRef(nullptr),
mMutex("nsNetworkLinkService::mMutex") {}
nsNetworkLinkService::~nsNetworkLinkService() = default;
NS_IMETHODIMP
nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
*aIsUp = mLinkUp;
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetLinkStatusKnown(bool* aIsUp) {
*aIsUp = mStatusKnown;
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
NS_ENSURE_ARG_POINTER(aLinkType);
// XXX This function has not yet been implemented for this platform
*aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
MutexAutoLock lock(mMutex);
aNetworkID = mNetworkId;
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetPlatformDNSIndications(uint32_t* aPlatformDNSIndications) {
return NS_ERROR_NOT_IMPLEMENTED;
}
void nsNetworkLinkService::GetDnsSuffixListInternal() {
MOZ_ASSERT(!NS_IsMainThread());
LOG(("GetDnsSuffixListInternal"));
auto sendNotification = mozilla::MakeScopeExit([self = RefPtr{this}] {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsNetworkLinkService::GetDnsSuffixListInternal",
[self]() { self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr); }));
});
nsTArray<nsCString> result;
struct __res_state res;
if (res_ninit(&res) == 0) {
for (int i = 0; i < MAXDNSRCH; i++) {
if (!res.dnsrch[i]) {
break;
}
LOG(("DNS search domain from [%s]\n", res.dnsrch[i]));
result.AppendElement(nsCString(res.dnsrch[i]));
}
res_nclose(&res);
}
MutexAutoLock lock(mMutex);
mDNSSuffixList = std::move(result);
}
NS_IMETHODIMP
nsNetworkLinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
aDnsSuffixList.Clear();
MutexAutoLock lock(mMutex);
aDnsSuffixList.AppendElements(mDNSSuffixList);
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetResolvers(nsTArray<RefPtr<nsINetAddr>>& aResolvers) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNetworkLinkService::GetNativeResolvers(nsTArray<mozilla::net::NetAddr>& aResolvers) {
return NS_ERROR_NOT_IMPLEMENTED;
}
#ifndef SA_SIZE
# define SA_SIZE(sa) \
((!(sa) || ((struct sockaddr*)(sa))->sa_len == 0) \
? sizeof(uint32_t) \
: 1 + ((((struct sockaddr*)(sa))->sa_len - 1) | (sizeof(uint32_t) - 1)))
#endif
static bool getMac(struct sockaddr_dl* sdl, char* buf, size_t bufsize) {
unsigned char* mac;
mac = (unsigned char*)LLADDR(sdl);
if (sdl->sdl_alen != 6) {
LOG(("networkid: unexpected MAC size %u", sdl->sdl_alen));
return false;
}
snprintf(buf, bufsize, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4],
mac[5]);
return true;
}
/* If the IP matches, get the MAC and return true */
static bool matchIp(struct sockaddr_dl* sdl, struct sockaddr_inarp* addr, char* ip, char* buf,
size_t bufsize) {
if (sdl->sdl_alen) {
if (!strcmp(inet_ntoa(addr->sin_addr), ip)) {
if (getMac(sdl, buf, bufsize)) {
return true; /* done! */
}
}
}
return false; /* continue */
}
/*
* Scan for the 'IP' address in the ARP table and store the corresponding MAC
* address in 'mac'. The output buffer is 'maclen' bytes big.
*
* Returns 'true' if it found the IP and returns a MAC.
*/
static bool scanArp(char* ip, char* mac, size_t maclen) {
int mib[6];
char *lim, *next;
int st;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_FLAGS;
mib[5] = RTF_LLINFO;
size_t needed;
if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
return false;
}
if (needed == 0) {
LOG(("scanArp: empty table"));
return false;
}
UniquePtr<char[]> buf(new char[needed]);
for (;;) {
st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
if (st == 0 || errno != ENOMEM) {
break;
}
needed += needed / 8;
auto tmp = MakeUnique<char[]>(needed);
memcpy(&tmp[0], &buf[0], needed);
buf = std::move(tmp);
}
if (st == -1) {
return false;
}
lim = &buf[needed];
struct rt_msghdr* rtm;
for (next = &buf[0]; next < lim; next += rtm->rtm_msglen) {
rtm = reinterpret_cast<struct rt_msghdr*>(next);
struct sockaddr_inarp* sin2 = reinterpret_cast<struct sockaddr_inarp*>(rtm + 1);
struct sockaddr_dl* sdl = reinterpret_cast<struct sockaddr_dl*>((char*)sin2 + SA_SIZE(sin2));
if (matchIp(sdl, sin2, ip, mac, maclen)) {
return true;
}
}
return false;
}
// Append the mac address of rtm to `stringsToHash`. If it's not in arp table, append
// ifname and IP address.
static bool parseHashKey(struct rt_msghdr* rtm, nsTArray<nsCString>& stringsToHash,
bool skipDstCheck) {
struct sockaddr* sa;
struct sockaddr_in* sockin;
char ip[INET_ADDRSTRLEN];
// Ignore the routing table message without destination/gateway sockaddr.
// Destination address is needed to check if the gateway is default or
// overwritten by VPN. If yes, append the mac address or IP/interface name to
// `stringsToHash`.
if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) != (RTA_DST | RTA_GATEWAY)) {
return false;
}
sa = reinterpret_cast<struct sockaddr*>(rtm + 1);
struct sockaddr* destination =
reinterpret_cast<struct sockaddr*>((char*)sa + RTAX_DST * SA_SIZE(sa));
if (!destination || destination->sa_family != AF_INET) {
return false;
}
sockin = reinterpret_cast<struct sockaddr_in*>(destination);
inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
if (!skipDstCheck && strcmp("0.0.0.0", ip)) {
return false;
}
struct sockaddr* gateway =
reinterpret_cast<struct sockaddr*>((char*)sa + RTAX_GATEWAY * SA_SIZE(sa));
if (!gateway) {
return false;
}
if (gateway->sa_family == AF_INET) {
sockin = reinterpret_cast<struct sockaddr_in*>(gateway);
inet_ntop(AF_INET, &sockin->sin_addr.s_addr, ip, sizeof(ip) - 1);
char mac[18];
// TODO: cache the arp table instead of multiple system call.
if (scanArp(ip, mac, sizeof(mac))) {
stringsToHash.AppendElement(nsCString(mac));
} else {
// Can't find a real MAC address. This might be a VPN gateway.
char buf[IFNAMSIZ] = {0};
char* ifName = if_indextoname(rtm->rtm_index, buf);
if (!ifName) {
LOG(("parseHashKey: AF_INET if_indextoname failed"));
return false;
}
stringsToHash.AppendElement(nsCString(ifName));
stringsToHash.AppendElement(nsCString(ip));
}
} else if (gateway->sa_family == AF_LINK) {
char buf[64];
struct sockaddr_dl* sockdl = reinterpret_cast<struct sockaddr_dl*>(gateway);
if (getMac(sockdl, buf, sizeof(buf))) {
stringsToHash.AppendElement(nsCString(buf));
} else {
char buf[IFNAMSIZ] = {0};
char* ifName = if_indextoname(rtm->rtm_index, buf);
if (!ifName) {
LOG(("parseHashKey: AF_LINK if_indextoname failed"));
return false;
}
stringsToHash.AppendElement(nsCString(ifName));
}
}
return true;
}
// It detects the IP of the default gateways in the routing table, then the MAC
// address of that IP in the ARP table before it hashes that string (to avoid
// information leakage).
bool nsNetworkLinkService::RoutingTable(nsTArray<nsCString>& aHash) {
size_t needed;
int mib[6];
struct rt_msghdr* rtm;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = 0;
mib[4] = NET_RT_DUMP;
mib[5] = 0;
if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
return false;
}
UniquePtr<char[]> buf(new char[needed]);
if (sysctl(mib, 6, &buf[0], &needed, nullptr, 0) < 0) {
return false;
}
char* lim = &buf[0] + needed;
bool rv = false;
// `next + 1 < lim` ensures we have valid `rtm->rtm_msglen` which is an
// unsigned short at the beginning of `rt_msghdr`.
for (char* next = &buf[0]; next + 1 < lim; next += rtm->rtm_msglen) {
rtm = reinterpret_cast<struct rt_msghdr*>(next);
if (next + rtm->rtm_msglen > lim) {
LOG(("Rt msg is truncated..."));
break;
}
if (parseHashKey(rtm, aHash, false)) {
rv = true;
}
}
return rv;
}
// Detect the routing of network.netlink.route.check.IPv4
bool nsNetworkLinkService::RoutingFromKernel(nsTArray<nsCString>& aHash) {
int sockfd;
if ((sockfd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) {
LOG(("RoutingFromKernel: Can create a socket for network id"));
return false;
}
MOZ_ASSERT(!NS_IsMainThread());
size_t needed = 1024;
struct rt_msghdr* rtm;
struct sockaddr_in* sin;
UniquePtr<char[]> buf(new char[needed]);
pid_t pid;
int seq;
rtm = reinterpret_cast<struct rt_msghdr*>(&buf[0]);
memset(rtm, 0, sizeof(struct rt_msghdr));
rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = RTM_GET;
rtm->rtm_addrs = RTA_DST;
rtm->rtm_pid = (pid = getpid());
rtm->rtm_seq = (seq = random());
sin = reinterpret_cast<struct sockaddr_in*>(rtm + 1);
memset(sin, 0, sizeof(struct sockaddr_in));
sin->sin_len = sizeof(struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_addr = mRouteCheckIPv4;
if (write(sockfd, rtm, rtm->rtm_msglen) == -1) {
LOG(("RoutingFromKernel: write() failed. No route to the predefine destincation"));
return false;
}
do {
ssize_t r;
if ((r = read(sockfd, rtm, needed)) < 0) {
LOG(("RoutingFromKernel: read() failed."));
return false;
}
LOG(("RoutingFromKernel: read() rtm_type: %d (%d), rtm_pid: %d (%d), rtm_seq: %d (%d)\n",
rtm->rtm_type, RTM_GET, rtm->rtm_pid, pid, rtm->rtm_seq, seq));
} while (rtm->rtm_type != RTM_GET || rtm->rtm_pid != pid || rtm->rtm_seq != seq);
return parseHashKey(rtm, aHash, true);
}
// Figure out the current IPv4 "network identification" string.
bool nsNetworkLinkService::IPv4NetworkId(SHA1Sum* aSHA1) {
nsTArray<nsCString> hash;
if (!RoutingTable(hash)) {
NS_WARNING("IPv4NetworkId: No default gateways");
}
if (!RoutingFromKernel(hash)) {
NS_WARNING("IPv4NetworkId: No route to the predefined destination");
}
// We didn't get any valid hash key to generate network ID.
if (hash.IsEmpty()) {
LOG(("IPv4NetworkId: No valid hash key"));
return false;
}
hash.Sort();
for (uint32_t i = 0; i < hash.Length(); ++i) {
LOG(("IPv4NetworkId: Hashing string for network id: %s", hash[i].get()));
aSHA1->update(hash[i].get(), hash[i].Length());
}
return true;
}
//
// Sort and hash the prefixes and netmasks
//
void nsNetworkLinkService::HashSortedPrefixesAndNetmasks(
std::vector<prefix_and_netmask> prefixAndNetmaskStore, SHA1Sum* sha1) {
// getifaddrs does not guarantee the interfaces will always be in the same order.
// We want to make sure the hash remains consistent Regardless of the interface order.
std::sort(prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
[](prefix_and_netmask a, prefix_and_netmask b) {
// compare prefixStore
int comparedPrefix = memcmp(&a.first, &b.first, sizeof(in6_addr));
if (comparedPrefix == 0) {
// compare netmaskStore
return memcmp(&a.second, &b.second, sizeof(in6_addr)) < 0;
}
return comparedPrefix < 0;
});
for (const auto& prefixAndNetmask : prefixAndNetmaskStore) {
sha1->update(&prefixAndNetmask.first, sizeof(in6_addr));
sha1->update(&prefixAndNetmask.second, sizeof(in6_addr));
}
}
bool nsNetworkLinkService::IPv6NetworkId(SHA1Sum* sha1) {
struct ifaddrs* ifap;
std::vector<prefix_and_netmask> prefixAndNetmaskStore;
if (!getifaddrs(&ifap)) {
struct ifaddrs* ifa;
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) {
continue;
}
if ((AF_INET6 == ifa->ifa_addr->sa_family) &&
!(ifa->ifa_flags & (IFF_POINTOPOINT | IFF_LOOPBACK))) {
// only IPv6 interfaces that aren't pointtopoint or loopback
struct sockaddr_in6* sin_netmask = (struct sockaddr_in6*)ifa->ifa_netmask;
if (sin_netmask) {
struct sockaddr_in6* sin_addr = (struct sockaddr_in6*)ifa->ifa_addr;
int scope = net::utils::ipv6_scope(sin_addr->sin6_addr.s6_addr);
if (scope == IPV6_SCOPE_GLOBAL) {
struct in6_addr prefix;
memset(&prefix, 0, sizeof(prefix));
// Get the prefix by combining the address and netmask.
for (size_t i = 0; i < sizeof(prefix); ++i) {
prefix.s6_addr[i] =
sin_addr->sin6_addr.s6_addr[i] & sin_netmask->sin6_addr.s6_addr[i];
}
// check if prefix and netmask was already found
auto prefixAndNetmask = std::make_pair(prefix, sin_netmask->sin6_addr);
auto foundPosition = std::find_if(
prefixAndNetmaskStore.begin(), prefixAndNetmaskStore.end(),
[&prefixAndNetmask](prefix_and_netmask current) {
return memcmp(&prefixAndNetmask.first, &current.first, sizeof(in6_addr)) == 0 &&
memcmp(&prefixAndNetmask.second, &current.second, sizeof(in6_addr)) == 0;
});
if (foundPosition != prefixAndNetmaskStore.end()) {
continue;
}
prefixAndNetmaskStore.push_back(prefixAndNetmask);
}
}
}
}
freeifaddrs(ifap);
}
if (prefixAndNetmaskStore.empty()) {
LOG(("IPv6NetworkId failed"));
return false;
}
nsNetworkLinkService::HashSortedPrefixesAndNetmasks(prefixAndNetmaskStore, sha1);
return true;
}
void nsNetworkLinkService::calculateNetworkIdWithDelay(uint32_t aDelay) {
MOZ_ASSERT(NS_IsMainThread());
if (aDelay) {
if (mNetworkIdTimer) {
LOG(("Restart the network id timer."));
mNetworkIdTimer->Cancel();
} else {
LOG(("Create the network id timer."));
mNetworkIdTimer = NS_NewTimer();
}
mNetworkIdTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
return;
}
nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
if (!target) {
return;
}
MOZ_ALWAYS_SUCCEEDS(
target->Dispatch(NewRunnableMethod("nsNetworkLinkService::calculateNetworkIdInternal", this,
&nsNetworkLinkService::calculateNetworkIdInternal),
NS_DISPATCH_NORMAL));
}
NS_IMETHODIMP
nsNetworkLinkService::Notify(nsITimer* aTimer) {
MOZ_ASSERT(aTimer == mNetworkIdTimer);
mNetworkIdTimer = nullptr;
calculateNetworkIdWithDelay(0);
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetName(nsACString& aName) {
aName.AssignLiteral("nsNetworkLinkService");
return NS_OK;
}
void nsNetworkLinkService::calculateNetworkIdInternal(void) {
MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread");
SHA1Sum sha1;
bool idChanged = false;
bool found4 = IPv4NetworkId(&sha1);
bool found6 = IPv6NetworkId(&sha1);
if (found4 || found6) {
// This 'addition' could potentially be a fixed number from the
// profile or something.
nsAutoCString addition("local-rubbish");
nsAutoCString output;
sha1.update(addition.get(), addition.Length());
uint8_t digest[SHA1Sum::kHashSize];
sha1.finish(digest);
nsAutoCString newString(reinterpret_cast<char*>(digest), SHA1Sum::kHashSize);
nsresult rv = Base64Encode(newString, output);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
LOG(("networkid: id %s\n", output.get()));
MutexAutoLock lock(mMutex);
if (mNetworkId != output) {
// new id
if (found4 && !found6) {
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); // IPv4 only
} else if (!found4 && found6) {
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3); // IPv6 only
} else {
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4); // Both!
}
mNetworkId = output;
idChanged = true;
} else {
// same id
LOG(("Same network id"));
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
}
} else {
// no id
LOG(("No network id"));
MutexAutoLock lock(mMutex);
if (!mNetworkId.IsEmpty()) {
mNetworkId.Truncate();
idChanged = true;
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
}
}
// Don't report network change if this is the first time we calculate the id.
static bool initialIDCalculation = true;
if (idChanged && !initialIDCalculation) {
RefPtr<nsNetworkLinkService> self = this;
NS_DispatchToMainThread(
NS_NewRunnableFunction("nsNetworkLinkService::calculateNetworkIdInternal",
[self]() { self->OnNetworkIdChanged(); }));
}
initialIDCalculation = false;
}
NS_IMETHODIMP
nsNetworkLinkService::Observe(nsISupports* subject, const char* topic, const char16_t* data) {
if (!strcmp(topic, "xpcom-shutdown")) {
Shutdown();
}
return NS_OK;
}
/* static */
void nsNetworkLinkService::NetworkConfigChanged(SCDynamicStoreRef aStoreREf,
CFArrayRef aChangedKeys, void* aInfo) {
LOG(("nsNetworkLinkService::NetworkConfigChanged"));
bool ipConfigChanged = false;
bool dnsConfigChanged = false;
for (CFIndex i = 0; i < CFArrayGetCount(aChangedKeys); ++i) {
CFStringRef key = static_cast<CFStringRef>(CFArrayGetValueAtIndex(aChangedKeys, i));
if (CFStringHasSuffix(key, kSCEntNetIPv4) || CFStringHasSuffix(key, kSCEntNetIPv6)) {
ipConfigChanged = true;
}
if (CFStringHasSuffix(key, kSCEntNetDNS)) {
dnsConfigChanged = true;
}
}
nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(aInfo);
if (ipConfigChanged) {
service->OnIPConfigChanged();
}
if (dnsConfigChanged) {
service->DNSConfigChanged(kDNSSuffixDelayAfterChange);
}
}
void nsNetworkLinkService::DNSConfigChanged(uint32_t aDelayMs) {
LOG(("nsNetworkLinkService::DNSConfigChanged"));
nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
if (!target) {
return;
}
if (aDelayMs) {
MutexAutoLock lock(mMutex);
nsCOMPtr<nsITimer> timer;
MOZ_ALWAYS_SUCCEEDS(NS_NewTimerWithCallback(
getter_AddRefs(timer),
[self = RefPtr{this}](nsITimer* aTimer) {
self->GetDnsSuffixListInternal();
MutexAutoLock lock(self->mMutex);
self->mDNSConfigChangedTimers.RemoveElement(aTimer);
},
TimeDuration::FromMilliseconds(aDelayMs), nsITimer::TYPE_ONE_SHOT,
"nsNetworkLinkService::GetDnsSuffixListInternal", target));
mDNSConfigChangedTimers.AppendElement(timer);
} else {
MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
NS_NewRunnableFunction("nsNetworkLinkService::GetDnsSuffixListInternal",
[self = RefPtr{this}]() { self->GetDnsSuffixListInternal(); })));
}
}
nsresult nsNetworkLinkService::Init(void) {
nsresult rv;
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = observerService->AddObserver(this, "xpcom-shutdown", false);
NS_ENSURE_SUCCESS(rv, rv);
if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV4);
return NS_ERROR_UNEXPECTED;
}
// If the network reachability API can reach 0.0.0.0 without
// requiring a connection, there is a network interface available.
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
mReachability = ::SCNetworkReachabilityCreateWithAddress(nullptr, (struct sockaddr*)&addr);
if (!mReachability) {
return NS_ERROR_NOT_AVAILABLE;
}
SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr};
if (!::SCNetworkReachabilitySetCallback(mReachability, ReachabilityChanged, &context)) {
NS_WARNING("SCNetworkReachabilitySetCallback failed.");
::CFRelease(mReachability);
mReachability = nullptr;
return NS_ERROR_NOT_AVAILABLE;
}
SCDynamicStoreContext storeContext = {0, this, nullptr, nullptr, nullptr};
mStoreRef = ::SCDynamicStoreCreate(nullptr, CFSTR("IPAndDNSChangeCallbackSCF"),
NetworkConfigChanged, &storeContext);
CFStringRef patterns[4] = {nullptr, nullptr, nullptr, nullptr};
OSStatus err = getErrorCodePtr(mStoreRef);
if (err == noErr) {
// This pattern is "State:/Network/Service/[^/]+/IPv4".
patterns[0] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(nullptr, kSCDynamicStoreDomainState,
kSCCompAnyRegex, kSCEntNetIPv4);
// This pattern is "State:/Network/Service/[^/]+/IPv6".
patterns[1] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(nullptr, kSCDynamicStoreDomainState,
kSCCompAnyRegex, kSCEntNetIPv6);
// This pattern is "State:/Network/Service/[^/]+/DNS".
patterns[2] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(nullptr, kSCDynamicStoreDomainState,
kSCCompAnyRegex, kSCEntNetDNS);
// This pattern is "Setup:/Network/Service/[^/]+/DNS".
patterns[3] = ::SCDynamicStoreKeyCreateNetworkServiceEntity(nullptr, kSCDynamicStoreDomainSetup,
kSCCompAnyRegex, kSCEntNetDNS);
if (!patterns[0] || !patterns[1] || !patterns[2] || !patterns[3]) {
err = -1;
}
}
CFArrayRef patternList = nullptr;
// Create a pattern list containing just one pattern,
// then tell SCF that we want to watch changes in keys
// that match that pattern list, then create our run loop
// source.
if (err == noErr) {
patternList = ::CFArrayCreate(nullptr, (const void**)patterns, 4, &kCFTypeArrayCallBacks);
if (!patternList) {
err = -1;
}
}
if (err == noErr) {
err = getErrorCodeBool(::SCDynamicStoreSetNotificationKeys(mStoreRef, nullptr, patternList));
}
if (err == noErr) {
mRunLoopSource = ::SCDynamicStoreCreateRunLoopSource(nullptr, mStoreRef, 0);
err = getErrorCodePtr(mRunLoopSource);
}
CFReleaseSafe(patterns[0]);
CFReleaseSafe(patterns[1]);
CFReleaseSafe(patterns[2]);
CFReleaseSafe(patterns[3]);
CFReleaseSafe(patternList);
if (err != noErr) {
CFReleaseSafe(mStoreRef);
return NS_ERROR_NOT_AVAILABLE;
}
// Get the current run loop. This service is initialized at startup,
// so we shouldn't run in to any problems with modal dialog run loops.
mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
if (!mCFRunLoop) {
NS_WARNING("Could not get current run loop.");
::CFRelease(mReachability);
mReachability = nullptr;
return NS_ERROR_NOT_AVAILABLE;
}
::CFRetain(mCFRunLoop);
::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
kCFRunLoopDefaultMode)) {
NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
::CFRelease(mReachability);
mReachability = nullptr;
::CFRelease(mCFRunLoop);
mCFRunLoop = nullptr;
return NS_ERROR_NOT_AVAILABLE;
}
UpdateReachability();
calculateNetworkIdWithDelay(0);
DNSConfigChanged(0);
return NS_OK;
}
nsresult nsNetworkLinkService::Shutdown() {
if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability, mCFRunLoop,
kCFRunLoopDefaultMode)) {
NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
}
CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
::CFRelease(mReachability);
mReachability = nullptr;
::CFRelease(mCFRunLoop);
mCFRunLoop = nullptr;
::CFRelease(mStoreRef);
mStoreRef = nullptr;
::CFRelease(mRunLoopSource);
mRunLoopSource = nullptr;
if (mNetworkIdTimer) {
mNetworkIdTimer->Cancel();
mNetworkIdTimer = nullptr;
}
nsTArray<nsCOMPtr<nsITimer>> dnsConfigChangedTimers;
{
MutexAutoLock lock(mMutex);
dnsConfigChangedTimers = std::move(mDNSConfigChangedTimers);
mDNSConfigChangedTimers.Clear();
}
for (const auto& timer : dnsConfigChangedTimers) {
timer->Cancel();
}
return NS_OK;
}
void nsNetworkLinkService::UpdateReachability() {
if (!mReachability) {
return;
}
SCNetworkConnectionFlags flags;
if (!::SCNetworkReachabilityGetFlags(mReachability, &flags)) {
mStatusKnown = false;
return;
}
bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
mLinkUp = (reachable && !needsConnection);
mStatusKnown = true;
}
void nsNetworkLinkService::OnIPConfigChanged() {
MOZ_ASSERT(NS_IsMainThread());
calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
if (!StaticPrefs::network_notify_changed()) {
return;
}
NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_CHANGED);
}
void nsNetworkLinkService::OnNetworkIdChanged() {
MOZ_ASSERT(NS_IsMainThread());
NotifyObservers(NS_NETWORK_ID_CHANGED_TOPIC, nullptr);
}
void nsNetworkLinkService::OnReachabilityChanged() {
MOZ_ASSERT(NS_IsMainThread());
if (!mStatusKnown) {
NotifyObservers(NS_NETWORK_LINK_TOPIC, NS_NETWORK_LINK_DATA_UNKNOWN);
return;
}
NotifyObservers(NS_NETWORK_LINK_TOPIC,
mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
}
void nsNetworkLinkService::NotifyObservers(const char* aTopic, const char* aData) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("nsNetworkLinkService::NotifyObservers: topic:%s data:%s\n", aTopic, aData ? aData : ""));
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this), aTopic,
aData ? NS_ConvertASCIItoUTF16(aData).get() : nullptr);
}
}
/* static */
void nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags, void* info) {
LOG(("nsNetworkLinkService::ReachabilityChanged"));
nsNetworkLinkService* service = static_cast<nsNetworkLinkService*>(info);
service->UpdateReachability();
service->OnReachabilityChanged();
service->calculateNetworkIdWithDelay(kNetworkIdDelayAfterChange);
// If a new interface is up or the order of interfaces is changed, we should
// update the DNS suffix list.
service->DNSConfigChanged(0);
}