/* 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 "DNSUtils.h" #include "NetworkConnectivityService.h" #include "mozilla/net/SocketProcessParent.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "nsIOService.h" #include "xpcpublic.h" #include "nsSocketTransport2.h" #include "nsIHttpChannelInternal.h" #include "nsINetworkLinkService.h" #include "mozilla/StaticPrefs_network.h" static LazyLogModule gNCSLog("NetworkConnectivityService"); #undef LOG #define LOG(args) MOZ_LOG(gNCSLog, mozilla::LogLevel::Debug, args) namespace mozilla { namespace net { NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsIDNSListener, nsIObserver, nsINetworkConnectivityService, nsIStreamListener) static StaticRefPtr gConnService; NetworkConnectivityService::NetworkConnectivityService() : mDNSv4(UNKNOWN), mDNSv6(UNKNOWN), mIPv4(UNKNOWN), mIPv6(UNKNOWN), mNAT64(UNKNOWN), mLock("nat64prefixes") {} // static already_AddRefed NetworkConnectivityService::GetSingleton() { if (gConnService) { return do_AddRef(gConnService); } RefPtr service = new NetworkConnectivityService(); service->Init(); gConnService = std::move(service); ClearOnShutdown(&gConnService); return do_AddRef(gConnService); } nsresult NetworkConnectivityService::Init() { nsCOMPtr observerService = mozilla::services::GetObserverService(); observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); observerService->AddObserver(this, "network:captive-portal-connectivity", false); return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::GetDNSv4(ConnectivityState* aState) { NS_ENSURE_ARG(aState); *aState = mDNSv4; return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::GetDNSv6(ConnectivityState* aState) { NS_ENSURE_ARG(aState); *aState = mDNSv6; return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::GetIPv4(ConnectivityState* aState) { NS_ENSURE_ARG(aState); *aState = mIPv4; return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::GetIPv6(ConnectivityState* aState) { NS_ENSURE_ARG(aState); *aState = mIPv6; return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::GetNAT64(ConnectivityState* aState) { NS_ENSURE_ARG(aState); *aState = mNAT64; return NS_OK; } already_AddRefed NetworkConnectivityService::MapNAT64IPs( AddrInfo* aNewRRSet) { // Add prefixes only if there are no IPv6 addresses. // Expect that if aNewRRSet has IPv6 addresses, they must come // before IPv4 addresses. if (aNewRRSet->Addresses().IsEmpty() || aNewRRSet->Addresses()[0].raw.family == PR_AF_INET6) { return do_AddRef(aNewRRSet); } // Currently we only add prefixes to the first IP's clones. uint32_t ip = aNewRRSet->Addresses()[0].inet.ip; nsTArray addresses = aNewRRSet->Addresses().Clone(); { MutexAutoLock lock(mLock); for (const auto& prefix : mNAT64Prefixes) { NetAddr addr = NetAddr(prefix); // Copy the IPv4 address to the end addr.inet6.ip.u32[3] = ip; // If we have both IPv4 and NAT64, we be could insourcing NAT64 // to avoid double NAT and improve performance. However, this // breaks WebRTC, so we push it to the back. addresses.AppendElement(addr); } } auto builder = aNewRRSet->Build(); builder.SetAddresses(std::move(addresses)); return builder.Finish(); } // Returns true if a prefix was read and saved to the argument static inline bool NAT64PrefixFromPref(NetAddr* prefix) { nsAutoCString nat64PrefixPref; nsresult rv = Preferences::GetCString( "network.connectivity-service.nat64-prefix", nat64PrefixPref); return !(NS_FAILED(rv) || nat64PrefixPref.IsEmpty() || NS_FAILED(prefix->InitFromString(nat64PrefixPref)) || prefix->raw.family != PR_AF_INET6); } static inline bool NAT64PrefixCompare(const NetAddr& prefix1, const NetAddr& prefix2) { // Compare the first 96 bits as 64 + 32 return prefix1.inet6.ip.u64[0] == prefix2.inet6.ip.u64[0] && prefix1.inet6.ip.u32[2] == prefix2.inet6.ip.u32[2]; } void NetworkConnectivityService::PerformChecks() { mDNSv4 = UNKNOWN; mDNSv6 = UNKNOWN; mIPv4 = UNKNOWN; mIPv6 = UNKNOWN; mNAT64 = UNKNOWN; { MutexAutoLock lock(mLock); mNAT64Prefixes.Clear(); // NAT64 checks might be disabled. // Since We can't guarantee a DNS response, we should set up // NAT64 manually now if needed. NetAddr priorityPrefix{}; bool havePrefix = NAT64PrefixFromPref(&priorityPrefix); if (havePrefix) { mNAT64Prefixes.AppendElement(priorityPrefix); mNAT64 = OK; } } RecheckDNS(); RecheckIPConnectivity(); } static inline void NotifyObservers(const char* aTopic) { nsCOMPtr obs = mozilla::services::GetObserverService(); obs->NotifyObservers(nullptr, aTopic, nullptr); } void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) { nsCOMPtr rec = do_QueryInterface(aRecord); MutexAutoLock lock(mLock); mNAT64Prefixes.Clear(); NetAddr priorityPrefix{}; bool havePrefix = NAT64PrefixFromPref(&priorityPrefix); if (havePrefix) { mNAT64 = OK; mNAT64Prefixes.AppendElement(priorityPrefix); } if (!rec) { if (!havePrefix) { mNAT64 = NOT_AVAILABLE; } return; } mNAT64 = UNKNOWN; NetAddr addr{}; // use port 80 as dummy value for NetAddr while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) { if (addr.raw.family != AF_INET6 || addr.IsIPAddrV4Mapped()) { // These are not the kind of addresses we are looking for. continue; } // RFC 7050 does not require the embedded IPv4 to be // at the end of IPv6. In practice, and as we assume, // it is always at the end. // The embedded IP must be 192.0.0.170 or 192.0.0.171 // Clear the last bit to compare with the next one. addr.inet6.ip.u8[15] &= ~(uint32_t)1; if ((addr.inet6.ip.u8[12] != 192) || (addr.inet6.ip.u8[13] != 0) || (addr.inet6.ip.u8[14] != 0) || (addr.inet6.ip.u8[15] != 170)) { continue; } mNAT64Prefixes.AppendElement(addr); } size_t length = mNAT64Prefixes.Length(); if (length == 0) { mNAT64 = NOT_AVAILABLE; return; } // Remove duplicates. Typically a DNS64 resolver sends every // prefix twice with address with different last bits. We want // a list of unique prefixes while reordering is not allowed. // We must not handle the case with an element in-between // two identical ones, which is never the case for a properly // configured DNS64 resolver. NetAddr prev = mNAT64Prefixes[0]; for (size_t i = 1; i < length; i++) { if (NAT64PrefixCompare(prev, mNAT64Prefixes[i])) { mNAT64Prefixes.RemoveElementAt(i); i--; length--; } else { prev = mNAT64Prefixes[i]; } } // The prioritized address might also appear in the record we received. if (havePrefix) { for (size_t i = 1; i < length; i++) { if (NAT64PrefixCompare(priorityPrefix, mNAT64Prefixes[i])) { mNAT64Prefixes.RemoveElementAt(i); // It wouldn't appear more than once. break; } } } mNAT64 = OK; } NS_IMETHODIMP NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, nsresult aStatus) { ConnectivityState state = NS_SUCCEEDED(aStatus) ? OK : NOT_AVAILABLE; if (aRequest == mDNSv4Request) { mDNSv4 = state; mDNSv4Request = nullptr; } else if (aRequest == mDNSv6Request) { mDNSv6 = state; mDNSv6Request = nullptr; } else if (aRequest == mNAT64Request) { mNAT64Request = nullptr; SaveNAT64Prefixes(aRecord); } if (!mDNSv4Request && !mDNSv6Request && !mNAT64Request) { NotifyObservers("network:connectivity-service:dns-checks-complete"); } return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::RecheckDNS() { bool enabled = Preferences::GetBool("network.connectivity-service.enabled", false); if (!enabled) { return NS_OK; } if (nsIOService::UseSocketProcess()) { SocketProcessParent* parent = SocketProcessParent::GetSingleton(); if (parent) { Unused << parent->SendRecheckDNS(); } } nsresult rv; nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); OriginAttributes attrs; nsAutoCString host; Preferences::GetCString("network.connectivity-service.DNSv4.domain", host); rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, nsIDNSService::RESOLVE_DISABLE_IPV6 | nsIDNSService::RESOLVE_TRR_DISABLED_MODE, nullptr, this, NS_GetCurrentThread(), attrs, getter_AddRefs(mDNSv4Request)); NS_ENSURE_SUCCESS(rv, rv); Preferences::GetCString("network.connectivity-service.DNSv6.domain", host); rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, nsIDNSService::RESOLVE_DISABLE_IPV4 | nsIDNSService::RESOLVE_TRR_DISABLED_MODE, nullptr, this, NS_GetCurrentThread(), attrs, getter_AddRefs(mDNSv6Request)); NS_ENSURE_SUCCESS(rv, rv); if (StaticPrefs::network_connectivity_service_nat64_check()) { rv = dns->AsyncResolveNative("ipv4only.arpa"_ns, nsIDNSService::RESOLVE_TYPE_DEFAULT, nsIDNSService::RESOLVE_DISABLE_IPV4 | nsIDNSService::RESOLVE_TRR_DISABLED_MODE, nullptr, this, NS_GetCurrentThread(), attrs, getter_AddRefs(mNAT64Request)); NS_ENSURE_SUCCESS(rv, rv); } return rv; } NS_IMETHODIMP NetworkConnectivityService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, "network:captive-portal-connectivity")) { // Captive portal is cleared, so we redo the checks. PerformChecks(); } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { if (mDNSv4Request) { mDNSv4Request->Cancel(NS_ERROR_ABORT); mDNSv4Request = nullptr; } if (mDNSv6Request) { mDNSv6Request->Cancel(NS_ERROR_ABORT); mDNSv6Request = nullptr; } if (mNAT64Request) { mNAT64Request->Cancel(NS_ERROR_ABORT); mNAT64Request = nullptr; } nsCOMPtr observerService = mozilla::services::GetObserverService(); observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); observerService->RemoveObserver(this, "network:captive-portal-connectivity"); observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); } else if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && !NS_LITERAL_STRING_FROM_CSTRING(NS_NETWORK_LINK_DATA_UNKNOWN) .Equals(aData)) { PerformChecks(); } return NS_OK; } already_AddRefed NetworkConnectivityService::SetupIPCheckChannel( bool ipv4) { nsresult rv; nsAutoCString url; if (ipv4) { rv = Preferences::GetCString("network.connectivity-service.IPv4.url", url); } else { rv = Preferences::GetCString("network.connectivity-service.IPv6.url", url); } NS_ENSURE_SUCCESS(rv, nullptr); nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), url); NS_ENSURE_SUCCESS(rv, nullptr); nsCOMPtr channel; if (XRE_IsSocketProcess()) { rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel)); if (NS_FAILED(rv)) { return nullptr; } channel->SetLoadFlags( nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache nsIRequest::INHIBIT_CACHING | // don't write the response to cache nsIRequest::LOAD_ANONYMOUS); } else { rv = NS_NewChannel( getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_OTHER, nullptr, // nsICookieJarSettings nullptr, // aPerformanceStorage nullptr, // aLoadGroup nullptr, nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache nsIRequest::INHIBIT_CACHING | // don't write the response to cache nsIRequest::LOAD_ANONYMOUS); // prevent privacy leaks NS_ENSURE_SUCCESS(rv, nullptr); { // Prevent HTTPS-Only Mode from upgrading the OCSP request. nsCOMPtr loadInfo = channel->LoadInfo(); uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT; loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); // allow deprecated HTTP request from SystemPrincipal loadInfo->SetAllowDeprecatedSystemRequests(true); } } rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE); NS_ENSURE_SUCCESS(rv, nullptr); nsCOMPtr internalChan = do_QueryInterface(channel); NS_ENSURE_TRUE(internalChan, nullptr); if (ipv4) { internalChan->SetIPv6Disabled(); } else { internalChan->SetIPv4Disabled(); } return channel.forget(); } NS_IMETHODIMP NetworkConnectivityService::RecheckIPConnectivity() { bool enabled = Preferences::GetBool("network.connectivity-service.enabled", false); if (!enabled) { return NS_OK; } if (nsIOService::UseSocketProcess()) { SocketProcessParent* parent = SocketProcessParent::GetSingleton(); if (parent) { Unused << parent->SendRecheckIPConnectivity(); } } if (xpc::AreNonLocalConnectionsDisabled() && !Preferences::GetBool("network.captive-portal-service.testMode", false)) { return NS_OK; } if (mIPv4Channel) { mIPv4Channel->Cancel(NS_ERROR_ABORT); mIPv4Channel = nullptr; } if (mIPv6Channel) { mIPv6Channel->Cancel(NS_ERROR_ABORT); mIPv6Channel = nullptr; } nsresult rv; mHasNetworkId = false; mCheckedNetworkId = false; mIPv4Channel = SetupIPCheckChannel(/* ipv4 = */ true); if (mIPv4Channel) { rv = mIPv4Channel->AsyncOpen(this); NS_ENSURE_SUCCESS(rv, rv); } mIPv6Channel = SetupIPCheckChannel(/* ipv4 = */ false); if (mIPv6Channel) { rv = mIPv6Channel->AsyncOpen(this); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::OnStartRequest(nsIRequest* aRequest) { return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { if (aStatusCode == NS_ERROR_ABORT) { return NS_OK; } ConnectivityState status = NS_FAILED(aStatusCode) ? NOT_AVAILABLE : OK; if (aRequest == mIPv4Channel) { mIPv4 = status; mIPv4Channel = nullptr; if (mIPv4 == nsINetworkConnectivityService::OK) { Telemetry::AccumulateCategorical( mHasNetworkId ? Telemetry::LABELS_NETWORK_ID_ONLINE::present : Telemetry::LABELS_NETWORK_ID_ONLINE::absent); LOG(("mHasNetworkId : %d\n", mHasNetworkId)); } } else if (aRequest == mIPv6Channel) { mIPv6 = status; mIPv6Channel = nullptr; } if (!mIPv6Channel && !mIPv4Channel) { NotifyObservers("network:connectivity-service:ip-checks-complete"); } return NS_OK; } NS_IMETHODIMP NetworkConnectivityService::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, uint64_t aOffset, uint32_t aCount) { nsAutoCString data; // We perform this check here, instead of doing it in OnStopRequest in case // a network down event occurs after the data has arrived but before we fire // OnStopRequest. That would cause us to report a missing networkID, even // though it was not empty while receiving data. if (aRequest == mIPv4Channel && !mCheckedNetworkId) { nsCOMPtr nls = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID); nsAutoCString networkId; if (nls) { nls->GetNetworkID(networkId); } mHasNetworkId = !networkId.IsEmpty(); mCheckedNetworkId = true; } Unused << NS_ReadInputStreamToString(aInputStream, data, aCount); return NS_OK; } } // namespace net } // namespace mozilla